Add "create role" subcommand for nexus 15/61815/8
authorEric Ball <eball@linuxfoundation.org>
Wed, 22 May 2019 21:03:10 +0000 (14:03 -0700)
committerEric Ball <eball@linuxfoundation.org>
Tue, 29 Oct 2019 21:39:30 +0000 (14:39 -0700)
This enables users to create Nexus roles outside of project creation.

Add docs/commands/nexus.rst to WriteGoodLintBear ignore list, due to
issues with it failing the check based on example yaml code.

Issue: RELENG-290
Change-Id: Ie51e2bcd4d03221661b3cec79f8f62ea6d3e8c39
Signed-off-by: Eric Ball <eball@linuxfoundation.org>
.coafile
docs/commands/nexus.rst
lftools/cli/nexus.py
lftools/nexus/__init__.py
lftools/nexus/cmd.py
releasenotes/notes/add-create_roles-function-d0cd9c31fe34a73f.yaml [new file with mode: 0644]
tests/fixtures/nexus/role_config-good.yaml [new file with mode: 0644]
tests/fixtures/nexus/settings.yaml [new file with mode: 0644]
tests/fixtures/nexus/simplified_privs_list.json [new file with mode: 0644]
tests/fixtures/nexus/simplified_roles_list.json [new file with mode: 0644]
tests/test_nexus.py

index be307c4..5dfe0dc 100644 (file)
--- a/.coafile
+++ b/.coafile
@@ -18,6 +18,7 @@ ignore_length_regex = Signed-off-by,
 [all.Documentation]
 bears = WriteGoodLintBear
 files = docs/**/*.rst
+ignore += docs/commands/nexus.rst
 
 [all.MarkDown]
 bears = MarkdownBear,SpaceConsistencyBear,WriteGoodLintBear
index cd8ecf6..2ba4530 100644 (file)
@@ -31,6 +31,35 @@ repo
 For details and examples, please see
 :ref:`Create Nexus2 repos with lftools <create-repos-lftools>`
 
+.. _nexus-role:
+
+role
+^^^^
+
+.. program-output:: lftools nexus create role --help
+
+.. code-block:: yaml
+
+   # Example role-config.yaml. The top-level keys will be the role's id.
+   ---
+   # Minimal config
+   lf-deployment:
+     name: LF Deployment Role
+     roles: # Roles can be defined by ID or by Name
+       - nx-deployment
+       - analytics
+   # Full config with privileges (by name only) and description defined.
+   LF Deployment By Name:
+     name: LF Dep Role
+     privileges:
+       - Status - (read)
+       - Login to UI
+     roles:
+       - Nexus Deployment Role
+       - Analytics
+     description: "A role where I defined its contained roles by name"
+
+
 .. _nexus-reorder-staged-repos:
 
 reorder-staged-repos
index 945eee3..8ebe9e0 100644 (file)
@@ -60,6 +60,19 @@ def repo(ctx, config, settings):
     nexuscmd.create_repos(config, settings)
 
 
+@create.command()
+@click.option(
+    '-c', '--config', type=str, required=True,
+    help='Role config file for how the Nexus role should be created.')
+@click.option(
+    '-s', '--settings', type=str, required=True,
+    help='Config file containing administrative settings.')
+@click.pass_context
+def role(ctx, config, settings):
+    """Create a Nexus role as defined by a role-config.yaml file."""
+    nexuscmd.create_roles(config, settings)
+
+
 @nexus.group()
 @click.pass_context
 def docker(ctx):
index c78a827..f0f1191 100644 (file)
@@ -106,14 +106,18 @@ class Nexus:
         return r.json()['data']['id']
 
     def get_priv(self, name, priv):
+        """Get the ID for the privilege with the given name and privlege type."""
+        search_name = "{} - ({})".format(name, priv)
+        self.get_priv_by_name(search_name)
+
+    def get_priv_by_name(self, name):
         """Get the ID for the privilege with the given name."""
         url = os.path.join(self.baseurl, 'privileges')
 
-        search_name = "{} - ({})".format(name, priv)
         privileges = requests.get(url, auth=self.auth, headers=self.headers).json()
 
         for priv in privileges['data']:
-            if priv['name'] == search_name:
+            if priv['name'] == name:
                 return priv['id']
 
         raise LookupError("No privilege found named '{}'".format(name))
@@ -162,31 +166,45 @@ class Nexus:
             if role['name'] == name:
                 return role['id']
 
+        # If name is not found in names, check ids
+        for role in roles['data']:
+            if role['id'] == name:
+                return role['id']
+
         raise LookupError("No role with name '{}'".format(name))
 
-    def create_role(self, name, privs):
+    def create_role(self, name, privs, role_id="", description="", roles=[]):
         """Create a role with the given privileges."""
         url = os.path.join(self.baseurl, 'roles')
 
         role = {
             'data': {
-                'id': name,
+                'id': role_id if role_id else name,
                 'name': name,
-                'description': name,
+                'description': description if description else name,
                 'privileges': privs,
-                'roles': [
-                    'repository-any-read',
-                ],
+                'roles': ['repository-any-read'] + roles,
                 'sessionTimeout': 60,
             }
         }
 
         json_data = json.dumps(role).encode(encoding='latin-1')
+        log.debug("Sending role {} to Nexus".format(json_data))
 
-        r = requests.post(url, auth=self.auth, headers=self.headers, data=json_data)
+        r = requests.post(url, auth=self.auth, headers=self.headers,
+                          data=json_data)
 
         if r.status_code != requests.codes.created:
-            raise Exception("Role not created for '{}', code '{}'".format(name, r.status_code))
+            if r.status_code == 400 and "errors" in r.json().keys():
+                error_msgs = ""
+                for error in r.json()["errors"]:
+                    error_msgs += error["msg"] + "\n"
+                raise Exception("Role not created for '{}', code '{}', failed "
+                                "with the following errors: {}".format(
+                                    name, r.status_code, error_msgs))
+            else:
+                raise Exception("Role not created for '{}', code '{}'".format(
+                    role_id, r.status_code))
 
         return r.json()['data']['id']
 
index 6c72b47..f8da17f 100644 (file)
@@ -164,14 +164,14 @@ def create_repos(config_file, settings_file):
         # Create Role
         try:
             role_id = _nexus.get_role(name)
-            log.info('Creating {} role.'.format(role_id))
+            log.info('Role {} already exists.'.format(role_id))
         except LookupError as e:
             role_id = _nexus.create_role(name, privs)
 
         # Create user
         try:
             _nexus.get_user(name)
-            log.info('Creating {} user.'.format(name))
+            log.info('User {} already exists.'.format(name))
         except LookupError as e:
             _nexus.create_user(name, email, role_id, password, extra_privs)
 
@@ -222,6 +222,59 @@ def create_repos(config_file, settings_file):
                    config['base_groupId'], global_privs, config['email_domain'])
 
 
+def create_roles(config_file, settings_file):
+    """Create Nexus roles as defined by configuration file.
+
+    :arg str config: Configuration file containing role definitions that
+        will be used to create the new Nexus roles.
+    :arg str settings: Settings file containing administrative credentials and
+        information.
+    """
+    with open(config_file, 'r') as f:
+        config = yaml.safe_load(f)
+    with open(settings_file, 'r') as f:
+        settings = yaml.safe_load(f)
+
+    for setting in ['nexus', 'user', 'password']:
+        if setting not in settings:
+            log.error('{} needs to be defined in {}'.format(setting,
+                                                            settings_file))
+            sys.exit(1)
+
+    _nexus = Nexus(settings['nexus'], settings['user'], settings['password'])
+
+    required_settings = ['name', 'roles']
+    for role in config:
+        for setting in required_settings:
+            if setting not in config[role]:
+                log.error('{} not defined for role {}. Please ensure that {} '
+                          'are defined for each role in {}'.format(
+                              setting, role, required_settings, config_file))
+                sys.exit(1)
+
+        subrole_ids = []
+        for subrole in config[role]["roles"]:
+            subrole_id = _nexus.get_role(subrole)
+            subrole_ids.append(subrole_id)
+        config[role]["roles"] = subrole_ids
+
+        if "description" not in config[role]:
+            config[role]["description"] = config[role]["name"]
+
+        if "privileges" in config[role]:
+            priv_ids = []
+            for priv in config[role]["privileges"]:
+                priv_ids.append(_nexus.get_priv_by_name(priv))
+            config[role]["privileges"] = priv_ids
+        else:
+            config[role]["privileges"] = []
+
+    for role in config:
+        _nexus.create_role(config[role]["name"], config[role]["privileges"],
+                           role, config[role]["description"],
+                           config[role]["roles"])
+
+
 def search(settings_file, url, repo, pattern):
     """Return of list of images in the repo matching the pattern.
 
diff --git a/releasenotes/notes/add-create_roles-function-d0cd9c31fe34a73f.yaml b/releasenotes/notes/add-create_roles-function-d0cd9c31fe34a73f.yaml
new file mode 100644 (file)
index 0000000..ba0ee92
--- /dev/null
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Add "create role" subcommand for nexus, which enables users to create
+    Nexus roles outside of project creation.
diff --git a/tests/fixtures/nexus/role_config-good.yaml b/tests/fixtures/nexus/role_config-good.yaml
new file mode 100644 (file)
index 0000000..1b6bcae
--- /dev/null
@@ -0,0 +1,15 @@
+---
+lf-deployment:
+  name: LF Deployment Role
+  roles:
+    - nx-deployment
+    - analytics
+LF Deployment By Name:
+  name: LF Dep Role
+  privileges:
+    - Status - (read)
+    - Login to UI
+  roles:
+    - Nexus Deployment Role
+    - Analytics
+  description: "A role where I defined its contained roles by name"
diff --git a/tests/fixtures/nexus/settings.yaml b/tests/fixtures/nexus/settings.yaml
new file mode 100644 (file)
index 0000000..eeea928
--- /dev/null
@@ -0,0 +1,3 @@
+nexus: http://nexus.localhost
+user: admin
+password: admin123
diff --git a/tests/fixtures/nexus/simplified_privs_list.json b/tests/fixtures/nexus/simplified_privs_list.json
new file mode 100644 (file)
index 0000000..1780dcd
--- /dev/null
@@ -0,0 +1,40 @@
+{
+    "data": [
+        {
+            "id": "1",
+            "resourceURI": "http://nexus.localhost/service/local/privileges/1",
+            "name": "Status - (read)",
+            "description": "Give permission to query the nexus server for it's status.  This privilege is required by the anonymous user so that the UI can retrieve anonymous permissions on startup.",
+            "type": "method",
+            "userManaged": false,
+            "properties": [
+                {
+                    "key": "method",
+                    "value": "read"
+                },
+                {
+                    "key": "permission",
+                    "value": "nexus:status"
+                }
+            ]
+        },
+        {
+            "id": "2",
+            "resourceURI": "http://nexus.localhost/service/local/privileges/2",
+            "name": "Login to UI",
+            "description": "Give permission to allow a user to login to nexus.",
+            "type": "method",
+            "userManaged": false,
+            "properties": [
+                {
+                    "key": "method",
+                    "value": "read"
+                },
+                {
+                    "key": "permission",
+                    "value": "nexus:authentication"
+                }
+            ]
+        }
+    ]
+}
diff --git a/tests/fixtures/nexus/simplified_roles_list.json b/tests/fixtures/nexus/simplified_roles_list.json
new file mode 100644 (file)
index 0000000..7c2ea10
--- /dev/null
@@ -0,0 +1,30 @@
+{
+    "data": [
+        {
+            "resourceURI": "http://nexus.localhost/service/local/roles/analytics",
+            "id": "analytics",
+            "name": "Analytics",
+            "description": "Gives access to Analytics",
+            "sessionTimeout": 0,
+            "privileges": [
+                "analytics-all"
+            ],
+            "userManaged": false
+        },
+        {
+            "resourceURI": "http://nexus.localhost/service/local/roles/nx-deployment",
+            "id": "nx-deployment",
+            "name": "Nexus Deployment Role",
+            "description": "Deployment role for Nexus",
+            "sessionTimeout": 0,
+            "roles": [
+                "ui-basic",
+                "anonymous"
+            ],
+            "privileges": [
+                "83"
+            ],
+            "userManaged": false
+        }
+    ]
+}
index a4f883a..8ca9f54 100644 (file)
@@ -9,12 +9,48 @@
 ##############################################################################
 """Test nexus command."""
 
+import os
 import re
 
+import pytest
 
+from lftools.nexus import cmd
 from lftools.nexus import util
 
 
+FIXTURE_DIR = os.path.join(
+    os.path.dirname(os.path.realpath(__file__)), 'fixtures')
+
+
+@pytest.fixture
+def nexus2_obj_create(responses):
+    """Create the proper responses for the init of a nexus object"""
+    baseurl_endpoint = re.compile(".*nexus.*/service/local/repo_targets")
+    responses.add(responses.GET, baseurl_endpoint, status=200)
+
+
+@pytest.mark.datafiles(os.path.join(FIXTURE_DIR, 'nexus'))
+def test_create_roles(datafiles, responses, nexus2_obj_create):
+    """Test create_roles() method with good config."""
+    os.chdir(str(datafiles))
+    baseurl = "http://nexus.localhost/service/local/"
+    roles_url = baseurl + "roles"
+    privs_url = baseurl + "privileges"
+    role1_return = """{"data": {"id": "lf-deployment"}}"""
+    role2_return = """{"data": {"id": "LF Deployment By Name"}}"""
+
+    for _ in range(4):  # Add response for each expected "get_role" call
+        with open("simplified_roles_list.json", "r") as roles_return:
+            responses.add(responses.GET, roles_url, roles_return.read())
+    for _ in range(2):  # Add response for each expected "get_priv" call
+        with open("simplified_privs_list.json", "r") as privs_return:
+            responses.add(responses.GET, privs_url, privs_return.read())
+    responses.add(responses.POST, roles_url, role1_return, status=201)
+    responses.add(responses.POST, roles_url, role2_return, status=201)
+
+    cmd.create_roles("role_config-good.yaml", "settings.yaml")
+
+
 def test_create_repo_target_regex():
     """Test create_repo_target_regex() command."""