Repository: conjure-up/conjure-up

Issue:
handle error if ipv6 is enabled and localhost was selected
http://paste.ubuntu.com/23345012/

```
conjure-up/_unspecified_spell: [ERROR] conjure-up/_unspecified_spell: ERROR creating LXD client: /etc/default/lxd-bridge has IPv6 enabled.

Juju doesn't currently support IPv6.



IPv6 can be disabled by running:



       sudo dpkg-reconfigure -p medium lxd



and then bootstrap again.

conjure-up/_unspecified_spell: [ERROR] conjure-up/_unspecified_spell: Showing dialog for exception:
NoneType
conjure-up/_unspecified_spell: [ERROR] conjure-up/_unspecified_spell: Showing dialog for exception:
Traceback (most recent call last):
  File "/usr/lib/python3.5/concurrent/futures/thread.py", line 55, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/lib/python3/dist-packages/conjureup/controllers/deploy/gui.py", line 30, in _pre_deploy_exec
    app.current_model)['provider-type']
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 32, in _decorator
    login(force=True)
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 102, in login
    account = get_account(app.current_controller)
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 525, in get_account
    return get_accounts().get(controller, {})
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 537, in get_accounts
    "Unable to find: {}".format(env))
Exception: Unable to find: /home/ubuntu/.local/share/juju/accounts.yaml
conjure-up/_unspecified_spell: [ERROR] conjure-up/_unspecified_spell: Showing dialog for exception:
Traceback (most recent call last):
  File "/usr/lib/python3.5/concurrent/futures/thread.py", line 55, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 32, in _decorator
    login(force=True)
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 102, in login
    account = get_account(app.current_controller)
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 525, in get_account
    return get_accounts().get(controller, {})
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 537, in get_accounts
    "Unable to find: {}".format(env))
Exception: Unable to find: /home/ubuntu/.local/share/juju/accounts.yaml
conjure-up/_unspecified_spell: [ERROR] conjure-up/_unspecified_spell: Showing dialog for exception:
Traceback (most recent call last):
  File "/usr/lib/python3.5/concurrent/futures/thread.py", line 55, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/lib/python3/dist-packages/conjureup/controllers/deploy/gui.py", line 30, in _pre_deploy_exec
    app.current_model)['provider-type']
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 32, in _decorator
    login(force=True)
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 102, in login
    account = get_account(app.current_controller)
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 525, in get_account
    return get_accounts().get(controller, {})
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 537, in get_accounts
    "Unable to find: {}".format(env))
Exception: Unable to find: /home/ubuntu/.local/share/juju/accounts.yaml
conjure-up/_unspecified_spell: [ERROR] conjure-up/_unspecified_spell: Showing dialog for exception:
Traceback (most recent call last):
  File "/usr/lib/python3.5/concurrent/futures/thread.py", line 55, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 32, in _decorator
    login(force=True)
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 102, in login
    account = get_account(app.current_controller)
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 525, in get_account
    return get_accounts().get(controller, {})
  File "/usr/lib/python3/dist-packages/conjureup/juju.py", line 537, in get_accounts
    "Unable to find: {}".format(env))
Exception: Unable to find: /home/ubuntu/.local/share/juju/accounts.yaml
```


Discussion:

Patch:
diff --git a/README.md b/README.md
index 305c191..f2f872f 100644
--- a/README.md
+++ b/README.md
@@ -17,30 +17,6 @@ solutions up and going with as little hindrance as possible.
 
 ## recommended
 
-```
-$ sudo snap install conjure-up --edge --classic
-```
-
-## alternative installation
-
-### pre-reqs
-
-If you plan to use conjure-up to deploy onto LXD containers on your local
-machine (aka the _localhost_ cloud type), you will need to set up LXD
-beforehand, as this requires **sudo**. If you haven't done that, you can configure
-storage and networking with:
-```
-$ sudo dpkg-reconfigure -p medium lxd
-```
-
-> Note: Make sure that you select **NO** when asked to setup IPv6 as this not currently supported.
-
-..and wake up the lxd daemon with:
-
-```
-$ lxc finger
-```
-
 Install the packages from the below PPA's
 
 ```
diff --git a/conjureup/controllers/lxdsetup/gui.py b/conjureup/controllers/lxdsetup/gui.py
index 196ced0..65f55dd 100644
--- a/conjureup/controllers/lxdsetup/gui.py
+++ b/conjureup/controllers/lxdsetup/gui.py
@@ -80,12 +80,12 @@ class LXDSetupController:
         app.current_cloud = 'localhost'
         controllers.use('newcloud').render(bootstrap=True)
 
-    def render(self):
+    def render(self, msg):
         """ Render
         """
         track_screen("LXD Setup")
-        self.view = LXDSetupView(app,
-                                 self.finish)
+        self.view = LXDSetupView(app, msg=msg,
+                                 cb=self.finish)
 
         app.ui.set_header(
             title="Setup LXD Bridge",
diff --git a/conjureup/controllers/newcloud/gui.py b/conjureup/controllers/newcloud/gui.py
index 4564d1b..17b0fcf 100644
--- a/conjureup/controllers/newcloud/gui.py
+++ b/conjureup/controllers/newcloud/gui.py
@@ -147,9 +147,26 @@ class NewCloudController:
         # information.
 
         if app.current_cloud == 'localhost':
-            if not utils.check_bridge_exists() or \
-               not utils.check_user_in_group('lxd'):
-                return controllers.use('lxdsetup').render()
+            if not utils.check_bridge_exists():
+                return controllers.use('lxdsetup').render(
+                    "Unable to determine an existing LXD network bridge, "
+                    "please make sure you've run `sudo lxd init` to configure "
+                    "LXD."
+                )
+            if not utils.check_user_in_group('lxd'):
+                return controllers.use('lxdsetup').render(
+                    "{} is not part of the LXD group. You will need "
+                    "to exit conjure-up and do one of the following: "
+                    " 1: Run `newgrp lxd` and re-launch conjure-up\n"
+                    " 2: Log out completely, Log in and "
+                    "re-launch conjure-up".format(os.environ['USER']))
+            if utils.lxd_has_ipv6():
+                return controllers.use('lxdsetup').render(
+                    "The LXD bridge has IPv6 enabled. Currently this is "
+                    "unsupported by conjure-up. Please disable IPv6 and "
+                    "re-launch conjure-up\n\n"
+                    "Visit http://conjure-up.io/docs/en/users/#_lxd for "
+                    "information on how to disable IPv6.")
 
             app.log.debug("Found an IPv4 address, "
                           "assuming LXD is configured.")
diff --git a/conjureup/ui/__init__.py b/conjureup/ui/__init__.py
index 0cc2b71..7cbe6e1 100644
--- a/conjureup/ui/__init__.py
+++ b/conjureup/ui/__init__.py
@@ -23,7 +23,7 @@ class ConjureUI(Frame):
         errmsg += ("\n\n"
                    "Review log messages at ~/.cache/conjure-up/conjure-up.log "
                    "If appropriate, please submit a bug here: "
-                   "https://bugs.launchpad.net/conjure-up/+filebug")
+                   "https://github.com/conjure-up/conjure-up/issues/new")
 
         self.frame.body = ErrorView(errmsg)
         app.log.exception("Showing dialog for exception:")
diff --git a/conjureup/ui/views/lxdsetup.py b/conjureup/ui/views/lxdsetup.py
index 60bb8b3..74e1bf7 100644
--- a/conjureup/ui/views/lxdsetup.py
+++ b/conjureup/ui/views/lxdsetup.py
@@ -1,78 +1,18 @@
-from collections import OrderedDict
-
-from urwid import Columns, Filler, Pile, Text, WidgetWrap
+from urwid import Filler, Pile, Text, WidgetWrap
 
 from ubuntui.utils import Color, Padding
 from ubuntui.widgets.buttons import cancel_btn, confirm_btn
 from ubuntui.widgets.hr import HR
-from ubuntui.widgets.input import StringEditor, YesNo
 from ubuntui.widgets.text import Instruction
 
-# import os
-
-# Network format
-#
-# { key: (Widget, Help Text)}
-
-NETWORK = OrderedDict([
-    ('_USE_LXD_BRIDGE',
-     (YesNo(),
-      'Use a new bridge')),
-    ('LXD_BRIDGE',
-     (StringEditor(default='lxdbr0'),
-      'Bridge name')),
-    # ('_LXD_CONFILE',
-    #  (StringEditor(),
-    #   None)),
-    ('LXD_DOMAIN',
-     (StringEditor(default='lxd'),
-      'DNS domain for the bridge')),
-    ('LXD_IPV4_ADDR',
-     (StringEditor(default='10.10.0.1'),
-      'IPv4 Address (e.g. 10.10.0.1)')),
-    ('LXD_IPV4_NETMASK',
-     (StringEditor(default='255.255.255.0'),
-      'IPv4 netmask (e.g. 255.255.255.0)')),
-    ('LXD_IPV4_NETWORK',
-     (StringEditor(default='10.10.0.1/24'),
-      'IPv4 network (e.g. 10.10.0.1/24)')),
-    ('LXD_IPV4_DHCP_RANGE',
-     (StringEditor(default='10.10.0.2,10.10.0.254'),
-      'IPv4 DHCP range (e.g. 10.10.0.2,10.10.0.254)')),
-    ('LXD_IPV4_DHCP_MAX',
-     (StringEditor(default='250'),
-      'IPv4 DHCP number of hosts (e.g. 250)')),
-    ('LXD_IPV4_NAT',
-     (YesNo(),
-      'NAT IPv4 traffic'))
-    # Enable this at some point
-    # ('_LXD_IPV6_ADDR',
-    #  (StringEditor(),
-    #   None)),
-    # ('_LXD_IPV6_MASK',
-    #  (StringEditor(),
-    #   None)),
-    # ('_LXD_IPV6_NETWORK',
-    #  (StringEditor(),
-    #   None)),
-    # ('_LXD_IPV6_NAT',
-    #  (YesNo(),
-    #   None)),
-    # ('_LXD_IPV6_PROXY',
-    #  (YesNo(),
-    #   None))
-])
-
 
 class LXDSetupView(WidgetWrap):
 
-    def __init__(self, app, cb):
+    def __init__(self, app, msg, cb):
         self.app = app
-        self.input_items = NETWORK
+        self.msg = msg
         self.cb = cb
         _pile = [
-            # Padding.center_60(Instruction(
-            #     "Enter LXD information:")),
             Padding.center_60(Instruction(
                 "Please configure networking for LXD"
             )),
@@ -106,50 +46,12 @@ class LXDSetupView(WidgetWrap):
 
     def build_info(self):
         items = [
-            Text("There was no LXD bridge found on your system or "
-                 "not part of the 'lxd' group which usually "
-                 "means this is your first time running LXD."),
-            Padding.line_break(""),
-            Text("If you wish to do so now pressing confirm will drop you out "
-                 "of the installer and you will be required to run the "
-                 "following: \n\n"
-                 "  $ sudo lxd init\n"
-                 "  $ newgrp lxd\n"
-                 "  $ lxc finger\n\n"
-                 "If `lxc finger` does not fail with an error you are ready "
-                 "to re-run conjure-up and continue the installation.\n\n"
-                 "NOTE: Do NOT setup a IPv6 subnet when asked, "
-                 "only IPv4 subnets are supported.")
+            Text(self.msg)
         ]
         return Pile(items)
 
-    def build_inputs(self):
-        # FIXME: Skip this for now as we're shelling out to
-        # dpkg-reconfigure lxd :\ yay and such.
-        items = []
-        for k in self.input_items.keys():
-            widget, help_text = self.input_items[k]
-            if isinstance(widget.value, bool):
-                widget.set_default('Yes', True)
-            if k.startswith('_'):
-                # Don't treat 'private' keys as input
-                continue
-            col = Columns(
-                [
-                    ('weight', 0.5, Text(help_text, align='right')),
-                    Color.string_input(widget,
-                                       focus_map='string_input focus')
-                ], dividechars=1
-            )
-            items.append(col)
-            items.append(Padding.line_break(""))
-        return Pile(items)
-
     def cancel(self, btn):
         self.cb(back=True)
 
     def submit(self, result):
-        # os.execl("/usr/share/conjure-up/run-lxd-config",
-        #          "/usr/share/conjure-up/run-lxd-config",
-        #          self.app.config['spell'])
         self.cb(needs_lxd_setup=True)
diff --git a/conjureup/utils.py b/conjureup/utils.py
index 1f44695..2197707 100644
--- a/conjureup/utils.py
+++ b/conjureup/utils.py
@@ -13,7 +13,6 @@ from subprocess import (
     check_output
 )
 
-import requests_unixsocket
 import yaml
 from termcolor import colored
 
@@ -25,16 +24,6 @@ from conjureup.app_config import app
 from conjureup.telemetry import track_event
 
 
-def is_snap_package_installed(pkg):
-    # snapd is not idempotent so we need to query first
-    snap_query = 'http+unix://%2Frun%2Fsnapd.socket/v2/snaps/{}'.format(
-        pkg)
-    with requests_unixsocket.Session() as session:
-        if session.get(snap_query).ok:
-            return True
-    return False
-
-
 def run(cmd, **kwargs):
     """ Compatibility function to support python 3.4
     """
@@ -117,23 +106,41 @@ def run_attach(cmd, output_cb=None):
                                          subproc.returncode))
 
 
-def check_bridge_exists():
-    """ Checks that an LXD network bridge exists
+def lxd_version():
+    """ Get current LXD version
     """
     cmd = run_script('lxc version')
     if cmd.returncode == 0:
-        lxd_version = cmd.stdout.decode().strip()
+        return cmd.stdout.decode().strip()
     else:
         raise Exception("Could not determine LXD version.")
 
-    if lxd_version >= "2.4.0":
+
+def lxd_has_ipv6():
+    """ Checks whether LXD bridge has IPv6 enabled
+    """
+    if check_bridge_exists():
+        if lxd_version() >= "2.4.0":
+            cmd = run_script('lxc network get lxdbr0 ipv6.nat')
+        else:
+            cmd = run_script('debconf-show lxd|grep bridge-ipv6-nat')
+        out = cmd.stdout.decode().strip()
+        if "true" in out:
+            return True
+    return False
+
+
+def check_bridge_exists():
+    """ Checks that an LXD network bridge exists
+    """
+    if lxd_version() >= "2.4.0":
         try:
             run('lxc network list|grep -q bridge',
                 shell=True, check=True)
         except CalledProcessError:
             return False
         return True
-    elif lxd_version < "2.4.0":
+    elif lxd_version() < "2.4.0":
         if os.path.isfile('/etc/default/lxd-bridge'):
             config_string = "[dummy]\n"
             with open('/etc/default/lxd-bridge') as f:
diff --git a/debian/conjure-up.postinst b/debian/conjure-up.postinst
index c08cb8e..9ceea86 100644
--- a/debian/conjure-up.postinst
+++ b/debian/conjure-up.postinst
@@ -1,9 +1,9 @@
 #!/bin/sh
 
-set -e
+set -eu
 
 if [ ! -d /var/log/conjure-up ]; then
-        mkdir /var/log/conjure-up
+    mkdir /var/log/conjure-up
 fi
 chown syslog:syslog /var/log/conjure-up
 
@@ -11,4 +11,38 @@ ln -sf /usr/share/conjure-up/conjure-up-rsyslog.conf /etc/rsyslog.d/99-conjure-u
 
 invoke-rc.d rsyslog restart
 
-#DEBHELPER#
\ No newline at end of file
+# Do the manual steps a user has to run on a fresh system to get an lxd
+# bridge so the juju lxd provider can function. Taken from changes made
+# to cloud-init to do approximately this.
+
+VERSION=$(lxd --version)
+
+# LXD 2.3+ needs lxdbr0 setup via lxc.
+if dpkg --compare-versions "$VERSION" gt "2.2"; then
+    if ! lxc network list | grep -q lxdbr0; then
+        # Configure a known address ranges for lxdbr0.
+        lxc network create lxdbr0 \
+            ipv4.address=10.0.8.1/24 ipv4.nat=true \
+            ipv6.address=none ipv6.nat=false
+    fi
+else
+    if ! debconf-show lxd | grep -q 'bridge-ipv4:\strue'; then
+        debconf-communicate << EOF > /dev/null
+set lxd/setup-bridge true
+set lxd/bridge-domain lxd
+set lxd/bridge-name lxdbr0
+set lxd/bridge-ipv4 true
+set lxd/bridge-ipv4-address 10.0.8.1
+set lxd/bridge-ipv4-dhcp-first 10.0.8.3
+set lxd/bridge-ipv4-dhcp-last 10.0.8.254
+set lxd/bridge-ipv4-dhcp-leases 252
+set lxd/bridge-ipv4-netmask 24
+set lxd/bridge-ipv4-nat true
+set lxd/bridge-ipv6 false
+EOF
+        rm -rf /etc/default/lxd-bridge
+        dpkg-reconfigure lxd --frontend=noninteractive
+    fi
+fi
+
+#DEBHELPER#


Test patch:
diff --git a/test/test_controllers_lxdsetup_gui.py b/test/test_controllers_lxdsetup_gui.py
index c429aa4..80b7e1d 100644
--- a/test/test_controllers_lxdsetup_gui.py
+++ b/test/test_controllers_lxdsetup_gui.py
@@ -45,7 +45,7 @@ class LXDSetupGUIRenderTestCase(unittest.TestCase):
 
     def test_render(self):
         "call render"
-        self.controller.render()
+        self.controller.render("A message")
 
 
 class LXDSetupGUIFinishTestCase(unittest.TestCase):