From e90bfde6867cfe31665223929a794fdb66fa09be Mon Sep 17 00:00:00 2001 From: Eric Ball Date: Wed, 22 May 2019 14:03:10 -0700 Subject: [PATCH] Add "create role" subcommand for nexus 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 --- .coafile | 1 + docs/commands/nexus.rst | 29 +++++++++++ lftools/cli/nexus.py | 13 +++++ lftools/nexus/__init__.py | 38 +++++++++++---- lftools/nexus/cmd.py | 57 +++++++++++++++++++++- ...add-create_roles-function-d0cd9c31fe34a73f.yaml | 5 ++ tests/fixtures/nexus/role_config-good.yaml | 15 ++++++ tests/fixtures/nexus/settings.yaml | 3 ++ tests/fixtures/nexus/simplified_privs_list.json | 40 +++++++++++++++ tests/fixtures/nexus/simplified_roles_list.json | 30 ++++++++++++ tests/test_nexus.py | 36 ++++++++++++++ 11 files changed, 255 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/add-create_roles-function-d0cd9c31fe34a73f.yaml create mode 100644 tests/fixtures/nexus/role_config-good.yaml create mode 100644 tests/fixtures/nexus/settings.yaml create mode 100644 tests/fixtures/nexus/simplified_privs_list.json create mode 100644 tests/fixtures/nexus/simplified_roles_list.json diff --git a/.coafile b/.coafile index be307c43..5dfe0dcf 100644 --- 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 diff --git a/docs/commands/nexus.rst b/docs/commands/nexus.rst index cd8ecf65..2ba4530a 100644 --- a/docs/commands/nexus.rst +++ b/docs/commands/nexus.rst @@ -31,6 +31,35 @@ repo For details and examples, please see :ref:`Create Nexus2 repos with 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 diff --git a/lftools/cli/nexus.py b/lftools/cli/nexus.py index 945eee3b..8ebe9e0e 100644 --- a/lftools/cli/nexus.py +++ b/lftools/cli/nexus.py @@ -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): diff --git a/lftools/nexus/__init__.py b/lftools/nexus/__init__.py index c78a8277..f0f11913 100644 --- a/lftools/nexus/__init__.py +++ b/lftools/nexus/__init__.py @@ -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'] diff --git a/lftools/nexus/cmd.py b/lftools/nexus/cmd.py index 6c72b47c..f8da17f3 100644 --- a/lftools/nexus/cmd.py +++ b/lftools/nexus/cmd.py @@ -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 index 00000000..ba0ee929 --- /dev/null +++ b/releasenotes/notes/add-create_roles-function-d0cd9c31fe34a73f.yaml @@ -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 index 00000000..1b6bcae5 --- /dev/null +++ b/tests/fixtures/nexus/role_config-good.yaml @@ -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 index 00000000..eeea928e --- /dev/null +++ b/tests/fixtures/nexus/settings.yaml @@ -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 index 00000000..1780dcdb --- /dev/null +++ b/tests/fixtures/nexus/simplified_privs_list.json @@ -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 index 00000000..7c2ea101 --- /dev/null +++ b/tests/fixtures/nexus/simplified_roles_list.json @@ -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 + } + ] +} diff --git a/tests/test_nexus.py b/tests/test_nexus.py index a4f883a6..8ca9f545 100644 --- a/tests/test_nexus.py +++ b/tests/test_nexus.py @@ -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.""" -- 2.16.6