Add "nexus release" command 68/13868/7
authorEric Ball <eball@linuxfoundation.org>
Fri, 7 Dec 2018 01:39:43 +0000 (17:39 -0800)
committerEric Ball <eball@linuxfoundation.org>
Wed, 9 Jan 2019 14:01:03 +0000 (06:01 -0800)
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 <eball@linuxfoundation.org>
docs/commands/nexus.rst
lftools/cli/nexus.py
lftools/nexus/cmd.py
releasenotes/notes/nexus-release-cbc4111e790aad50.yaml [new file with mode: 0644]
tests/test_nexus.py

index 423dac1..0ac1a61 100644 (file)
@@ -60,3 +60,10 @@ list
 ^^^^
 
 .. program-output:: lftools nexus docker list --help
+
+.. _nexus-release:
+
+release
+-------
+
+.. program-output:: lftools nexus release --help
index 34e7ef1..5690158 100644 (file)
@@ -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)
index 9195112..a1c83dc 100644 (file)
@@ -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 (file)
index 0000000..2c52d58
--- /dev/null
@@ -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.
index 0101731..7d51fa6 100644 (file)
 """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)