← Back to team overview

cf-charmers team mailing list archive

[Merge] lp:~johnsca/charms/trusty/cloudfoundry/adminpass into lp:~cf-charmers/charms/trusty/cloudfoundry/trunk

 

Cory Johns has proposed merging lp:~johnsca/charms/trusty/cloudfoundry/adminpass into lp:~cf-charmers/charms/trusty/cloudfoundry/trunk.

Requested reviews:
  Cloud Foundry Charmers (cf-charmers)

For more details, see:
https://code.launchpad.net/~johnsca/charms/trusty/cloudfoundry/adminpass/+merge/240606

Dynamically generate password for default CF admin user, with config option to override.

Also, correctly populate the request-uri value in the uaa.clients.servicesmgmt block, although the package isn't available yet.
-- 
https://code.launchpad.net/~johnsca/charms/trusty/cloudfoundry/adminpass/+merge/240606
Your team Cloud Foundry Charmers is requested to review the proposed merge of lp:~johnsca/charms/trusty/cloudfoundry/adminpass into lp:~cf-charmers/charms/trusty/cloudfoundry/trunk.
=== modified file 'README.rst'
--- README.rst	2014-10-16 15:01:08 +0000
+++ README.rst	2014-11-04 16:50:28 +0000
@@ -61,6 +61,19 @@
     cf push
 
 
+Default Admin User
+------------------
+
+By default, an admin user is created with the username of "admin" and a
+randomly generated password.  The password can be retrived using the helper:
+
+    . helpers.sh
+    cfadminpass
+
+You can use the `admin_password` config option to override the generated
+password.
+
+
 Development
 -----------
 

=== modified file 'cloudfoundry/contexts.py'
--- cloudfoundry/contexts.py	2014-10-31 16:11:51 +0000
+++ cloudfoundry/contexts.py	2014-11-04 16:50:28 +0000
@@ -32,6 +32,8 @@
         return list(units)
 
     def get_first(self, key=None):
+        if not self.get(self.name, []):
+            return None if key is not None else {}
         data = self[self.name][0]
         return data[key] if key is not None else data
 
@@ -144,6 +146,20 @@
 
     def erb_mapping(self):
         data = self[self.name][0]
+        creds = CloudFoundryCredentials()
+        users = []
+        if creds.is_ready():
+            users.append(
+                '%s|%s|scim.write,scim.read,openid,cloud_controller.admin' % (
+                    creds.get_first('admin-user'),
+                    creds.get_first('admin-password')
+                )
+            )
+        orch = OrchestratorRelation()
+        sru = None
+        if orch.is_ready():
+            sru = 'http://servicesmgmt.{}/auth/cloudfoundry/callback'.format(
+                orch.get_first('domain'))
         return {
             'uaa.login.client_secret': data['login_client_secret'],
             'uaa.admin.client_secret': data['admin_client_secret'],
@@ -152,9 +168,7 @@
             'uaa.port': data['port'],
             'uaa.require_https': False,  # FIXME: Add SSL as an option; requires cert
             'uaa.no_ssl': True,
-            'uaa.scim.users': [
-                'admin|admin|scim.write,scim.read,openid,cloud_controller.admin',  # FIXME: Don't hard-code
-            ],
+            'uaa.scim.users': users,
             'uaa.clients': {
                 'cc_service_broker_client': {
                     'secret': data['service_broker_client_secret'],
@@ -167,7 +181,7 @@
                     'authorized-grant-types': 'authorization_code,client_credentials,password,implicit',
                     'autoapprove': True,
                     'override': True,
-                    'redirect-uri': 'http://servicesmgmt.10.244.0.34.xip.io/auth/cloudfoundry/callback',
+                    'redirect-uri': sru,
                     'scope': 'openid,cloud_controller.read,cloud_controller.write',
                     'secret': data['servicesmgmt_client_secret'],
                 },
@@ -422,13 +436,22 @@
     """
     name = "credentials"
     interface = "cloudfoundry-credentials"
+    required_keys = ['admin-user', 'admin-password']
+
+    def get_admin_password(self):
+        config = hookenv.config()
+        if config['admin_password']:
+            return config['admin_password']
+        else:
+            secret_context = StoredContext('cf-secrets.yml', {
+                'admin_password': host.pwgen(20),
+            })
+            return secret_context['admin_password']
 
     def provide_data(self):
-        # TODO: this must come from generated or a UAA related identity
-        # provider
         return {
             'admin-user': 'admin',
-            'admin-password': 'admin'
+            'admin-password': self.get_admin_password(),
         }
 
 

=== modified file 'cloudfoundry/services.py'
--- cloudfoundry/services.py	2014-10-31 15:34:27 +0000
+++ cloudfoundry/services.py	2014-11-04 16:50:28 +0000
@@ -270,6 +270,7 @@
                                contexts.UAADBRelation],
              'required_data': [contexts.MysqlRelation,
                                contexts.NatsRelation,
+                               contexts.CloudFoundryCredentials,
                                contexts.UAARelation.remote_view]},
         ]
     },

=== modified file 'config.yaml'
--- config.yaml	2014-09-30 21:15:05 +0000
+++ config.yaml	2014-11-04 16:50:28 +0000
@@ -1,4 +1,11 @@
 options:
+    admin_password:
+        type: string
+        description: >
+            The password for the default admin user in Cloud Foundry.
+            If left blank, a random password will be generated, which can be
+            retrieved using the cfadminpass helper from helpers.sh.
+        default: ""
     admin_secret:
         type: string
         description: >

=== modified file 'helpers.sh'
--- helpers.sh	2014-10-06 06:48:12 +0000
+++ helpers.sh	2014-11-04 16:50:28 +0000
@@ -1,9 +1,14 @@
+function cfadminpass() {
+    juju run --unit uaa/0 'relation-get -r $(relation-ids credentials) admin-password cloudfoundry/0'
+}
+
+
 function cflogin() {
     ENDPOINT=`juju status haproxy/0 |grep public-address|cut -f 2 -d : `
     IP=`dig +short $ENDPOINT`
     # get the _IP_ of the public address
     cf api http://api.${IP}.xip.io
-    cf auth admin admin
+    cf auth admin "$(cfadminpass)"
     cf create-space -o juju-org my-space
     cf target -o juju-org -s my-space
 }

=== modified file 'hooks/common.py'
--- hooks/common.py	2014-10-14 07:05:52 +0000
+++ hooks/common.py	2014-11-04 16:50:28 +0000
@@ -109,12 +109,20 @@
             charm = deployment.get_charm_for(service_name)
             if 'orchestrator' in charm.metadata.get('requires', {}):
                 try:
-                    env.add_relation(orchestrator, service_name)
+                    env.add_relation('{}:orchestrator'.format(orchestrator), service_name)
                 except EnvError as e:
                     if e.message.endswith('relation already exists'):
-                        continue  # existing relations are ok, just skip
+                        pass  # existing relations are ok, just skip
                     else:
                         hookenv.log('Error adding orchestrator relation: {}'.format(str(e)), hookenv.ERROR)
+            if 'credentials' in charm.metadata.get('requires', {}):
+                try:
+                    env.add_relation('{}:credentials'.format(orchestrator), service_name)
+                except EnvError as e:
+                    if e.message.endswith('relation already exists'):
+                        pass  # existing relations are ok, just skip
+                    else:
+                        hookenv.log('Error adding credentials relation: {}'.format(str(e)), hookenv.ERROR)
         env.expose('haproxy')
     finally:
         env.close()

=== modified file 'tests/test_contexts.py'
--- tests/test_contexts.py	2014-10-14 07:05:52 +0000
+++ tests/test_contexts.py	2014-11-04 16:50:28 +0000
@@ -200,11 +200,31 @@
 class TestCloudFoundryCredentials(unittest.TestCase):
     @mock.patch('charmhelpers.core.hookenv.charm_dir', lambda: 'charm_dir')
     @mock.patch('charmhelpers.core.services.RelationContext.get_data', mock.Mock())
-    def test_provide_data(self):
-        result = contexts.CloudFoundryCredentials().provide_data()
-        self.assertEqual(result, {
-                "admin-password": "admin",
-                "admin-user": "admin"
+    @mock.patch('charmhelpers.core.hookenv.config')
+    def test_provide_data_config(self, config):
+        config.return_value = {'admin_password': 'test'}
+        result = contexts.CloudFoundryCredentials().provide_data()
+        self.assertEqual(result, {
+            "admin-password": "test",
+            "admin-user": "admin"
+        })
+
+    @mock.patch('charmhelpers.core.hookenv.charm_dir', lambda: 'charm_dir')
+    @mock.patch('charmhelpers.core.services.RelationContext.get_data', mock.Mock())
+    @mock.patch('cloudfoundry.contexts.StoredContext')
+    @mock.patch('charmhelpers.core.host.pwgen')
+    @mock.patch('charmhelpers.core.hookenv.config')
+    def test_provide_data_gen(self, config, pwgen, StoredContext):
+        config.return_value = {'admin_password': ''}
+        pwgen.return_value = 'pw1'
+        StoredContext.return_value = {'admin_password': 'pw2'}
+        result = contexts.CloudFoundryCredentials().provide_data()
+        self.assertEqual(result, {
+            "admin-password": "pw2",
+            "admin-user": "admin"
+        })
+        StoredContext.assert_called_once_with('cf-secrets.yml', {
+            'admin_password': 'pw1',
         })
 
 


Follow ups