From: Eric Ball Date: Fri, 7 Dec 2018 01:39:43 +0000 (-0800) Subject: Add "nexus release" command X-Git-Tag: v0.20.0~12 X-Git-Url: https://gerrit.linuxfoundation.org/infra/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F68%2F13868%2F7;p=releng%2Flftools.git Add "nexus release" command This command will release one or more staging repositories in Nexus. To ensure proper functionality, this commit also includes fixes for lftools.nexus.cmd.get_credentials and lftools.cli.nexus.list_images. Issue: RELENG-916 Change-Id: I5fffea04e160004485e09513834825f4c08b220d Signed-off-by: Eric Ball --- diff --git a/docs/commands/nexus.rst b/docs/commands/nexus.rst index 423dac12..0ac1a611 100644 --- a/docs/commands/nexus.rst +++ b/docs/commands/nexus.rst @@ -60,3 +60,10 @@ list ^^^^ .. program-output:: lftools nexus docker list --help + +.. _nexus-release: + +release +------- + +.. program-output:: lftools nexus release --help diff --git a/lftools/cli/nexus.py b/lftools/cli/nexus.py index 34e7ef1d..56901580 100644 --- a/lftools/cli/nexus.py +++ b/lftools/cli/nexus.py @@ -94,8 +94,8 @@ def list_images(ctx, settings, server, repo, pattern, csv): Use '*' for wildcard, or begin with '!' to search for images NOT matching the string. """ - if not server and 'NEXUS_URL_ENV' in environ: - server = environ['NEXUS_URL_ENV'] + if not server and NEXUS_URL_ENV in environ: + server = environ[NEXUS_URL_ENV] images = nexuscmd.search(settings, server, repo, pattern) if images: nexuscmd.output_images(images, csv) @@ -116,3 +116,18 @@ def delete_images(ctx, settings, server, repo, pattern, yes): if yes or click.confirm("Would you like to delete all {} images?".format( str(len(images)))): nexuscmd.delete_images(settings, server, images) + + +@nexus.command() +@click.pass_context +@click.argument('REPOS', type=str, nargs=-1) +@click.option( + '-s', '--server', type=str, + help=('Nexus server URL. Can also be set as {} in the environment. ' + 'This will override any URL set in settings.yaml.').format( + NEXUS_URL_ENV)) +def release(ctx, repos, server): + """Release one or more staging repositories.""" + if not server and NEXUS_URL_ENV in environ: + server = environ[NEXUS_URL_ENV] + nexuscmd.release_staging_repos(repos, server) diff --git a/lftools/nexus/cmd.py b/lftools/nexus/cmd.py index 9195112c..a1c83dc6 100644 --- a/lftools/nexus/cmd.py +++ b/lftools/nexus/cmd.py @@ -14,6 +14,8 @@ import csv import logging import sys +import requests +from six.moves import configparser import yaml from lftools import config @@ -39,8 +41,12 @@ def get_credentials(settings_file, url=None): elif set(['nexus', 'user', 'password']).issubset(settings): return settings elif url: - user = config.get_setting("global", "username") - password = config.get_setting("global", "password") + try: + user = config.get_setting("nexus", "username") + password = config.get_setting("nexus", "password") + except (configparser.NoOptionError, + configparser.NoSectionError): + return {"nexus": url, "user": "", "password": ""} return {"nexus": url, "user": user, "password": password} log.error('Please define a settings.yaml file, or include a url if using ' + 'lftools.ini') @@ -273,3 +279,39 @@ def delete_images(settings_file, url, images): for image in images: _nexus.delete_image(image) + + +def release_staging_repos(repos, nexus_url=""): + """Release one or more staging repos. + + :arg tuple repos: A tuple containing one or more repo name strings. + :arg str nexus_url: Optional URL of target Nexus server. + """ + credentials = get_credentials(None, nexus_url) + _nexus = Nexus(credentials['nexus'], credentials['user'], + credentials['password']) + + for repo in repos: + data = {"data": {"stagedRepositoryIds": [repo]}} + log.debug("Sending data: {}".format(data)) + request_url = "{}/staging/bulk/promote".format(_nexus.baseurl) + log.debug("Request URL: {}".format(request_url)) + response = requests.post(request_url, json=data) + + if response.status_code != 201: + raise requests.HTTPError("Release failed with the following error:" + "\n{}: {}".format(response.status_code, + response.text)) + else: + log.debug("Successfully released {}".format(str(repo))) + + request_url = "{}/staging/bulk/drop".format(_nexus.baseurl) + log.debug("Request URL: {}".format(request_url)) + response = requests.post(request_url, json=data) + + if response.status_code != 201: + raise requests.HTTPError("Drop failed with the following error:" + "\n{}: {}".format(response.status_code, + response.text)) + else: + log.debug("Successfully dropped {}".format(str(repo))) diff --git a/releasenotes/notes/nexus-release-cbc4111e790aad50.yaml b/releasenotes/notes/nexus-release-cbc4111e790aad50.yaml new file mode 100644 index 00000000..2c52d58f --- /dev/null +++ b/releasenotes/notes/nexus-release-cbc4111e790aad50.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + Add Nexus command to release one or more staging repositories. Via the + Nexus 2 REST API, this command performs both a "release" and a "drop" + action on the repo(s), in order to best reproduce the action of manually + using the "Release" option in the Nexus UI. + + Usage: lftools nexus release [OPTIONS] [REPOS]... + + Options: + -s, --server TEXT Nexus server URL. Can also be set as NEXUS_URL in the + environment. This will override any URL set in + settings.yaml. diff --git a/tests/test_nexus.py b/tests/test_nexus.py index 0101731f..7d51fa63 100644 --- a/tests/test_nexus.py +++ b/tests/test_nexus.py @@ -10,9 +10,11 @@ """Test nexus command.""" import re +import requests import pytest +from lftools.nexus import cmd from lftools.nexus import util @@ -50,3 +52,49 @@ def test_create_repo_target_regex(): vpp = util.create_repo_target_regex('io.fd.vpp') vpp_regex = re.compile(vpp) assert vpp_regex.match('/io/fd/vpp/jvpp/16.06/jvpp-16.06.jar') + + +def test_release_staging_repos(responses): + """Test release_staging_repos() command.""" + good_url = "https://nexus.example.org" + # Prepare response for Nexus initialization + responses.add(responses.GET, + "{}/service/local/repo_targets".format(good_url), + json=None, status=200) + # The responses provide implicit assertions. + responses.add(responses.POST, "{}/service/local/staging/bulk/promote".format(good_url), + json=None, status=201) + responses.add(responses.POST, "{}/service/local/staging/bulk/drop".format(good_url), + json=None, status=201) + + # Test successful single release. + cmd.release_staging_repos(("release-1",), good_url) + + # Test successful multiple release. + cmd.release_staging_repos(("release-1", "release-2", "release-3"), + good_url) + + # Test promote failure + bad_url1 = "https://nexus-fail1.example.org" + responses.add(responses.GET, + "{}/service/local/repo_targets".format(bad_url1), + json=None, status=200) + responses.add(responses.POST, + "{}/service/local/staging/bulk/promote".format(bad_url1), + status=401) + with pytest.raises(requests.HTTPError): + cmd.release_staging_repos(("release-1",), bad_url1) + + # Test drop failure + bad_url2 = "https://nexus-fail2.example.org" + responses.add(responses.GET, + "{}/service/local/repo_targets".format(bad_url2), + json=None, status=200) + responses.add(responses.POST, + "{}/service/local/staging/bulk/promote".format(bad_url2), + status=201) + responses.add(responses.POST, + "{}/service/local/staging/bulk/drop".format(bad_url2), + status=403) + with pytest.raises(requests.HTTPError): + cmd.release_staging_repos(("release-1",), bad_url2)