Repository: conjure-up/conjure-up

Issue:
conjure-up uses petname for model name
conjure-up apparently uses single-word petnames for Juju models it
creates, e.g.:
```
| james@malefic:~$ juju models
| Controller: lxd
|
| Model Cloud/Region Status Machines Cores Access Last connection
| controller localhost/localhost available 1 - admin just now
| default localhost/localhost available 0 - admin 2017-01-02
| dane* localhost/localhost available 4 - admin 6 minutes ago
|
| james@malefic:~$
```
A bare pet name is pretty confusing and would only get more so if you
conjured up multiple spells. At a minimum, I think the spell should
be indicated in some way in the model name (perhaps combined with a
petname, if you need to avoid clashes). It might also be useful to
indicate in some way that this is a conjure-up model?

Discussion:
mikemccracken: A couple minor comments:

I think maybe the model name should include 'conjure-up' also, so when you conjure-up and use a preexisting controller, you still know that the model came from conjure-up.

It might also be nice to include cloud type in the controller name, like 'conjure-up-aws'. 

Also I don't think we need more than 3-4 digits of random UUID string. You get 8 from the code you wrote, maybe just truncate it? Someone's going to end up typing the whole thing and get mad..
battlemidget: @mikemccracken updated based on your suggestions
battlemidget: @mikemccracken should be ready for review and merge, test deploys reflects both the updated controller and models

Patch:
diff --git a/README.md b/README.md
index f2f872f..7521e7c 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,15 @@ $ sudo apt update
 $ sudo apt install conjure-up
 ```
 
+## alternate (soon to be recommended)
+
+We will be switching over to a snap based installation in the very near future.
+Please help us test this out by installing via `snap` command:
+
+```
+$ sudo snap install conjure-up --classic --edge
+```
+
 # how to use
 
 ## Run the installer interactively
diff --git a/conjureup/controllers/controllerpicker/gui.py b/conjureup/controllers/controllerpicker/gui.py
index 3ace007..0469bd5 100644
--- a/conjureup/controllers/controllerpicker/gui.py
+++ b/conjureup/controllers/controllerpicker/gui.py
@@ -1,6 +1,4 @@
-import petname
-
-from conjureup import async, controllers, juju
+from conjureup import async, controllers, juju, utils
 from conjureup.app_config import app
 from conjureup.telemetry import track_exception, track_screen
 from conjureup.ui.views.ControllerListView import ControllerListView
@@ -23,7 +21,9 @@ class ControllerPicker:
             return controllers.use('clouds').render()
 
         app.current_controller = controller
-        app.current_model = petname.Name()
+        app.current_model = "conjure-up-{}-{}".format(
+            app.env['CONJURE_UP_SPELL'],
+            utils.gen_hash())
         async.submit(self.__add_model,
                      self.__handle_exception,
                      queue_name=juju.JUJU_ASYNC_QUEUE)
diff --git a/conjureup/controllers/newcloud/gui.py b/conjureup/controllers/newcloud/gui.py
index 17b0fcf..ab118c3 100644
--- a/conjureup/controllers/newcloud/gui.py
+++ b/conjureup/controllers/newcloud/gui.py
@@ -4,8 +4,6 @@ import os.path as path
 from functools import partial
 from subprocess import check_output
 
-import petname
-
 from conjureup import async, controllers, juju, utils
 from conjureup.api.models import model_info
 from conjureup.app_config import app
@@ -57,6 +55,7 @@ class NewCloudController:
         future = juju.bootstrap_async(
             controller=app.current_controller,
             cloud=cloud,
+            model=app.current_model,
             credential=credential,
             exc_cb=self.__handle_exception)
         app.bootstrap.running = future
@@ -136,10 +135,14 @@ class NewCloudController:
         track_screen("Cloud Creation")
 
         if app.current_controller is None:
-            app.current_controller = petname.Name()
+            app.current_controller = "conjure-up-{}-{}".format(
+                app.current_cloud,
+                utils.gen_hash())
 
         if app.current_model is None:
-            app.current_model = 'conjure-up'
+            app.current_model = "conjure-up-{}-{}".format(
+                app.env['CONJURE_UP_SPELL'],
+                utils.gen_hash())
 
         # LXD is a special case as we want to make sure a bridge
         # is configured. If not we'll bring up a new view to allow
diff --git a/conjureup/controllers/newcloud/tui.py b/conjureup/controllers/newcloud/tui.py
index fb0f3c9..540a7c7 100644
--- a/conjureup/controllers/newcloud/tui.py
+++ b/conjureup/controllers/newcloud/tui.py
@@ -3,8 +3,6 @@ import os
 import sys
 from subprocess import PIPE
 
-import petname
-
 from conjureup import controllers, juju, utils
 from conjureup.api.models import model_info
 from conjureup.app_config import app
@@ -45,12 +43,15 @@ class NewCloudController:
         return controllers.use('deploy').render()
 
     def render(self):
-
         if app.current_controller is None:
-            app.current_controller = petname.Name()
+            app.current_controller = "conjure-up-{}-{}".format(
+                app.current_cloud,
+                utils.gen_hash())
 
         if app.current_model is None:
-            app.current_model = 'conjure-up'
+            app.current_model = "conjure-up-{}-{}".format(
+                app.env['CONJURE_UP_SPELL'],
+                utils.gen_hash())
 
         if app.current_cloud != 'localhost':
             if not common.try_get_creds(app.current_cloud):
@@ -72,6 +73,7 @@ class NewCloudController:
         utils.info("Bootstrapping Juju controller")
         p = juju.bootstrap(controller=app.current_controller,
                            cloud=app.current_cloud,
+                           model=app.current_model,
                            credential=common.try_get_creds(app.current_cloud))
         if p.returncode != 0:
             pathbase = os.path.join(
diff --git a/conjureup/juju.py b/conjureup/juju.py
index 784ef24..8fbd18f 100644
--- a/conjureup/juju.py
+++ b/conjureup/juju.py
@@ -139,7 +139,8 @@ def login(force=False):
     this.IS_AUTHENTICATED = True  # noqa
 
 
-def bootstrap(controller, cloud, series="xenial", credential=None):
+def bootstrap(controller, cloud, model='conjure-up', series="xenial",
+              credential=None):
     """ Performs juju bootstrap
 
     If not LXD pass along the newly defined credentials
@@ -155,7 +156,7 @@ def bootstrap(controller, cloud, series="xenial", credential=None):
           "--config image-stream=daily ".format(
               cloud, controller)
     cmd += "--config enable-os-upgrade=false "
-    cmd += "--default-model conjure-up "
+    cmd += "--default-model {} ".format(model)
     if app.argv.http_proxy:
         cmd += "--config http-proxy={} ".format(app.argv.http_proxy)
     if app.argv.https_proxy:
@@ -204,12 +205,14 @@ def bootstrap(controller, cloud, series="xenial", credential=None):
         raise e
 
 
-def bootstrap_async(controller, cloud, credential=None, exc_cb=None):
+def bootstrap_async(controller, cloud, model='conjure-up', credential=None,
+                    exc_cb=None):
     """ Performs a bootstrap asynchronously
     """
     return async.submit(partial(bootstrap,
                                 controller=controller,
                                 cloud=cloud,
+                                model=model,
                                 credential=credential), exc_cb,
                         queue_name=JUJU_ASYNC_QUEUE)
 
diff --git a/conjureup/ui/views/bootstrapwait.py b/conjureup/ui/views/bootstrapwait.py
index 15a9d7f..f6277ff 100644
--- a/conjureup/ui/views/bootstrapwait.py
+++ b/conjureup/ui/views/bootstrapwait.py
@@ -33,7 +33,7 @@ class BootstrapWaitView(WidgetWrap):
             sanitize = "".join(ch for ch
                                in t if unicodedata.category(ch)[0] != "C")
             if sanitize.endswith("%"):
-                new_out.append(sanitize.split("%")[0])
+                new_out.append("{}%".format(sanitize.split("%")[0]))
             else:
                 new_out.append(sanitize)
         if len(new_out) >= 10:
@@ -66,7 +66,7 @@ class BootstrapWaitView(WidgetWrap):
         text = [Padding.line_break(""),
                 self.message,
                 Padding.line_break(""),
-                self.output,
+                Padding.center_90(self.output),
                 Padding.line_break("")]
 
         _boxes = []
diff --git a/conjureup/utils.py b/conjureup/utils.py
index 2197707..df24e3a 100644
--- a/conjureup/utils.py
+++ b/conjureup/utils.py
@@ -4,6 +4,7 @@ import errno
 import os
 import pty
 import shutil
+import uuid
 from subprocess import (
     DEVNULL,
     PIPE,
@@ -355,3 +356,9 @@ def get_options_whitelist(service_name):
     svc_opts_whitelist = options_whitelist.get(service_name, [])
 
     return svc_opts_whitelist
+
+
+def gen_hash():
+    """ generates a UUID
+    """
+    return str(uuid.uuid4()).split('-')[0][:3]


Test patch:
diff --git a/test/test_controllers_newcloud_tui.py b/test/test_controllers_newcloud_tui.py
index f570b25..941177f 100644
--- a/test/test_controllers_newcloud_tui.py
+++ b/test/test_controllers_newcloud_tui.py
@@ -63,11 +63,13 @@ class NewCloudTUIRenderTestCase(unittest.TestCase):
         self.mock_common.try_get_creds.return_value = True
         self.mock_app.current_controller = sentinel.controllername
         self.mock_app.current_cloud = sentinel.cloudname
+        self.mock_app.current_model = sentinel.modelname
         self.mock_juju.bootstrap.return_value.returncode = 0
         self.controller.render()
         self.mock_juju.bootstrap.assert_called_once_with(
             controller=sentinel.controllername,
             cloud=sentinel.cloudname,
+            model=sentinel.modelname,
             credential=True)
 
         self.controller.do_post_bootstrap.assert_called_once_with()