Implement the Nexus 3 REST API in lftools 07/62607/16
authorDW Talton <dtalton@contractor.linuxfoundation.org>
Thu, 12 Dec 2019 00:26:02 +0000 (17:26 -0700)
committerDW Talton <dtalton@contractor.linuxfoundation.org>
Fri, 10 Jan 2020 19:32:00 +0000 (12:32 -0700)
Issue: RELENG-308
Signed-off-by: DW Talton <dtalton@contractor.linuxfoundation.org>
Change-Id: I3dc1b1b7280fe6a2d69a75e2f8c7f5a9d64c5126

18 files changed:
.coafile
docs/commands/index.rst
docs/commands/nexus3.rst [new file with mode: 0644]
lftools/api/client.py
lftools/api/endpoints/nexus.py [new file with mode: 0644]
lftools/cli/__init__.py
lftools/cli/nexus3/__init__.py [new file with mode: 0644]
lftools/cli/nexus3/asset.py [new file with mode: 0644]
lftools/cli/nexus3/privilege.py [new file with mode: 0644]
lftools/cli/nexus3/repository.py [new file with mode: 0644]
lftools/cli/nexus3/role.py [new file with mode: 0644]
lftools/cli/nexus3/script.py [new file with mode: 0644]
lftools/cli/nexus3/tag.py [new file with mode: 0644]
lftools/cli/nexus3/task.py [new file with mode: 0644]
lftools/cli/nexus3/user.py [new file with mode: 0644]
lftools/config.py
releasenotes/notes/nexus3-6a988f31e4876fd8.yaml [new file with mode: 0644]
requirements.txt

index dea5fbc..9412814 100644 (file)
--- a/.coafile
+++ b/.coafile
@@ -34,6 +34,7 @@ bears = BanditBear,
     PyImportSortBear
 files = lftools/**.py
 ignore += docs/conf.py
+ignore += lftools/cli/nexus3/*.py
 known_first_party_imports = lftools
 known_third_party_imports =
     defusedxml,
index 3aea343..c1bc775 100644 (file)
@@ -19,6 +19,7 @@ It supports the following commands:
     lfidapi
     license
     nexus
+    nexus3
     openstack
     rtd
     schema
diff --git a/docs/commands/nexus3.rst b/docs/commands/nexus3.rst
new file mode 100644 (file)
index 0000000..03db203
--- /dev/null
@@ -0,0 +1,71 @@
+.. _nexus3:
+
+******
+Nexus3
+******
+
+.. program-output:: lftools nexus3 --help
+
+.. _nexus3_commands:
+
+Commands
+========
+
+.. contents:: Nexus3 Commands
+    :local:
+
+.. _nexus3_asset:
+
+asset
+-----
+
+.. program-output:: lftools nexus3 asset --help
+
+.. _nexus3_privileges:
+
+privilege
+---------
+
+.. program-output:: lftools nexus3 privilege --help
+
+.. _nexus3_repository:
+
+repository
+----------
+
+.. program-output:: lftools nexus3 repository --help
+
+.. _nexus3_role:
+
+role
+----
+
+.. program-output:: lftools nexus3 role --help
+
+.. _nexus3_script:
+
+script
+------
+
+.. program-output:: lftools nexus3 script --help
+
+.. _nexus3_tag:
+
+tag
+---
+
+.. program-output:: lftools nexus3 tag --help
+
+.. _nexus3_task:
+
+task
+----
+
+.. program-output:: lftools nexus3 task --help
+
+.. _nexus3_user:
+
+user
+----
+
+.. program-output:: lftools nexus3 user --help
index f9f3f76..0e00df2 100644 (file)
@@ -21,39 +21,63 @@ class RestApi(object):
         """Initialize the REST API class."""
         self.params = params
 
-        if params['creds']:
-            self.creds = params['creds']
+        if params["creds"]:
+            self.creds = params["creds"]
 
-        if 'timeout' not in self.params:
+        if "timeout" not in self.params:
             self.timeout = None
 
-        self.endpoint = self.creds['endpoint']
+        self.endpoint = self.creds["endpoint"]
 
-        if self.creds['authtype'] == 'basic':
-            self.username = self.creds['username']
-            self.password = self.creds['password']
+        if self.creds["authtype"] == "basic":
+            self.username = self.creds["username"]
+            self.password = self.creds["password"]
             self.r = requests.Session()
             self.r.auth = (self.username, self.password)
-            self.r.headers.update({'Content-Type': 'application/json; charset=UTF-8'})
+            self.r.headers.update(
+                {"Content-Type": "application/json; charset=UTF-8"}
+            )
 
-        if self.creds['authtype'] == 'token':
-            self.token = self.creds['token']
+        if self.creds["authtype"] == "token":
+            self.token = self.creds["token"]
             self.r = requests.Session()
-            self.r.headers.update({'Authorization': 'Token {}'
-                                   .format(self.token)})
-            self.r.headers.update({'Content-Type': 'application/json'})
+            self.r.headers.update(
+                {"Authorization": "Token {}".format(self.token)}
+            )
+            self.r.headers.update({"Content-Type": "application/json"})
 
     def _request(self, url, method, data=None, timeout=30):
         """Execute the request."""
-        resp = self.r.request(method, self.endpoint + url, data=data, timeout=timeout)
+        resp = self.r.request(
+            method, self.endpoint + url, data=data, timeout=timeout
+        )
 
+        # Some massaging to make our gerrit python code work
         if resp.status_code == 409:
             return resp
 
+        # otherwise abort on any actual HTTP errors and suppress traceback
+        try:
+            resp.raise_for_status()
+        except requests.exceptions.RequestException as e:
+            raise e
+        except requests.exceptions.HTTPError as e:
+            raise e.args
+        except requests.exceptions.ConnectionError as e:
+            raise e
+        except requests.exceptions.ProxyError as e:
+            raise e
+        except requests.exceptions.Timeout as e:
+            raise e
+        except requests.exceptions.URLRequired as e:
+            raise e
+        except requests.exceptions.InvalidURL as e:
+            raise e
+
         if resp.text:
             try:
-                if 'application/json' in resp.headers['Content-Type']:
-                    remove_xssi_magic = resp.text.replace(')]}\'', '')
+                if "application/json" in resp.headers["Content-Type"]:
+                    remove_xssi_magic = resp.text.replace(")]}'", "")
                     body = json.loads(remove_xssi_magic)
                 else:
                     body = resp.text
@@ -68,20 +92,20 @@ class RestApi(object):
 
     def get(self, url, **kwargs):
         """HTTP GET request."""
-        return self._request(url, 'GET', **kwargs)
+        return self._request(url, "GET", **kwargs)
 
     def patch(self, url, **kwargs):
         """HTTP PATCH request."""
-        return self._request(url, 'PATCH', **kwargs)
+        return self._request(url, "PATCH", **kwargs)
 
     def post(self, url, **kwargs):
         """HTTP POST request."""
-        return self._request(url, 'POST', **kwargs)
+        return self._request(url, "POST", **kwargs)
 
     def put(self, url, **kwargs):
         """HTTP PUT request."""
-        return self._request(url, 'PUT', **kwargs)
+        return self._request(url, "PUT", **kwargs)
 
     def delete(self, url, **kwargs):
         """HTTP DELETE request."""
-        return self._request(url, 'DELETE', **kwargs)
+        return self._request(url, "DELETE", **kwargs)
diff --git a/lftools/api/endpoints/nexus.py b/lftools/api/endpoints/nexus.py
new file mode 100644 (file)
index 0000000..df4d265
--- /dev/null
@@ -0,0 +1,366 @@
+# SPDX-License-Identifier: EPL-1.0
+##############################################################################
+# Copyright (c) 2019 The Linux Foundation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+##############################################################################
+
+"""Nexus3 REST API interface."""
+import json
+import logging
+
+from lftools import config
+import lftools.api.client as client
+
+log = logging.getLogger(__name__)
+
+
+class Nexus(client.RestApi):
+    """API endpoint wrapper for Nexus3."""
+
+    def __init__(self, **params):
+        """Initialize the class."""
+        self.params = params
+        self.fqdn = self.params["fqdn"]
+        if "creds" not in self.params:
+            creds = {
+                "authtype": "basic",
+                "username": config.get_setting(self.fqdn, "username"),
+                "password": config.get_setting(self.fqdn, "password"),
+                "endpoint": config.get_setting(self.fqdn, "endpoint"),
+            }
+            params["creds"] = creds
+
+        super(Nexus, self).__init__(**params)
+
+    def create_role(self, name, description, privileges, roles):
+        """Create a new role.
+
+        :param name: the role name
+        :param description: the role description
+        :param privileges: privileges assigned to this role
+        :param roles: other roles attached to this role
+        """
+        list_of_privileges = privileges.split(",")
+        list_of_roles = roles.split(",")
+
+        data = {
+            "id": name,
+            "name": name,
+            "description": description,
+            "privileges": list_of_privileges,
+            "roles": list_of_roles,
+        }
+
+        json_data = json.dumps(data, indent=4)
+        result = self.post("beta/security/roles", data=json_data)
+
+        if result[0].status_code == 200:
+            return "Role {} created".format(name)
+        else:
+            return "Failed to create role"
+
+    def create_script(self, name, content):
+        """Create a new script.
+
+        :param name: script name
+        :param content: content of the script (groovy code)
+        """
+        data = {"name": name, "content": content, "type": "groovy"}
+
+        json_data = json.dumps(data)
+        result = self.post("v1/script", data=json_data)
+
+        if result.status_code == 204:
+            return "Script {} successfully added.".format(name)
+        else:
+            return "Failed to create script {}".format(name)
+
+    def create_tag(self, name, attributes):
+        """Create a new tag.
+
+        :param name: the tag name
+        :param attributes: the tag's attributes
+        """
+        data = {
+            "name": name,
+        }
+
+        if attributes is not None:
+            data["attributes"] = attributes
+
+        json_data = json.dumps(data)
+        result = self.post("v1/tags", data=json_data)[0]
+
+        if result.status_code == 200:
+            return "Tag {} successfully added.".format(name)
+        else:
+            return "Failed to create tag {}".format(name)
+
+    def delete_script(self, name):
+        """Delete a script from the server.
+
+        :param name: the script name
+        """
+        result = self.delete("v1/script/{}".format(name))
+
+        if result.status_code == 204:
+            return "Successfully deleted {}".format(name)
+        else:
+            return "Failed to delete script {}".format(name)
+
+    def delete_tag(self, name):
+        """Delete a tag from the server.
+
+        :param name: the tag's name
+        """
+        result = self.delete("v1/tags/{}".format(name))
+
+        if result.status_code == 204:
+            return "Tag {} successfully deleted.".format(name)
+        else:
+            return "Failed to delete tag {}.".format(name)
+
+    def list_assets(self, repository, **kwargs):
+        """List the assets of a given repo.
+
+        :param repository: repo name
+        """
+        result = self.get("v1/assets?repository={}".format(repository))[1][
+            "items"
+        ]
+        if not result:
+            return "This repository has no assets"
+        else:
+            item_list = []
+            for item in result:
+                item_list.append(item["path"])
+            return item_list
+
+    def list_blobstores(self, **kwargs):
+        """List server blobstores."""
+        result = self.get("beta/blobstores")[1]
+        list_of_blobstores = []
+        for blob in result:
+            list_of_blobstores.append(blob["name"])
+        return list_of_blobstores
+
+    def list_components(self, repository, **kwargs):
+        """List components from a repo.
+
+        :param repository: the repo name
+        """
+        result = self.get("v1/components?repository={}".format(repository))[1][
+            "items"
+        ]
+        if not result:
+            return "This repository has no components"
+        else:
+            return result
+
+    def list_privileges(self, **kwargs):
+        """List server-configured privileges."""
+        result = self.get("beta/security/privileges")[1]
+        list_of_privileges = []
+        for privilege in result:
+            list_of_privileges.append(
+                [
+                    privilege["type"],
+                    privilege["name"],
+                    privilege["description"],
+                    privilege["readOnly"],
+                ]
+            )
+        return list_of_privileges
+
+    def list_repositories(self, **kwargs):
+        """List server repositories."""
+        result = self.get("v1/repositories")[1]
+        list_of_repositories = []
+        for repository in result:
+            list_of_repositories.append(repository["name"])
+        return list_of_repositories
+
+    def list_roles(self, **kwargs):
+        """List server roles."""
+        result = self.get("beta/security/roles")[1]
+        list_of_roles = []
+        for role in result:
+            list_of_roles.append([role["name"]])
+        return list_of_roles
+
+    def list_scripts(self, **kwargs):
+        """List server scripts."""
+        result = self.get("v1/script")[1]
+        list_of_scripts = []
+        for script in result:
+            list_of_scripts.append(script["name"])
+        return list_of_scripts
+
+    def show_tag(self, name):
+        """Get tag details.
+
+        :param name: tag name
+        :return:
+        """
+        result = self.get("v1/tags/{}".format(name))[1]
+        return result
+
+    def list_tags(self):
+        """List all tag."""
+        result = self.get("v1/tags")[1]
+        list_of_tags = []
+        token = result["continuationToken"]
+        if token is not None:
+            while token is not None:
+                for tag in result["items"]:
+                    list_of_tags.append(tag["name"])
+                result = self.get(
+                    "v1/tags?continuationToken={}".format(
+                        result["continuationToken"]
+                    )
+                )[1]
+                token = result["continuationToken"]
+        else:
+            for tag in result["items"]:
+                list_of_tags.append(tag["name"])
+
+        if list_of_tags:
+            return list_of_tags
+        else:
+            return "There are no tags"
+
+    def list_tasks(self, **kwargs):
+        """List all tasks."""
+        result = self.get("v1/tasks")[1]["items"]
+        list_of_tasks = []
+        for task in result:
+            list_of_tasks.append(
+                [
+                    task["name"],
+                    task["message"],
+                    task["currentState"],
+                    task["lastRunResult"],
+                ]
+            )
+        return list_of_tasks
+
+    def list_user(self, username, **kwargs):
+        """Show user details.
+
+        :param username: the user's username
+        """
+        result = self.get("beta/security/users?userId={}".format(username))[1]
+        user_info = []
+        for user in result:
+            user_info.append(
+                [
+                    user["userId"],
+                    user["firstName"],
+                    user["lastName"],
+                    user["emailAddress"],
+                    user["status"],
+                    user["roles"],
+                ]
+            )
+        return user_info
+
+    def list_users(self, **kwargs):
+        """List all users."""
+        result = self.get("beta/security/users")[1]
+        list_of_users = []
+        for user in result:
+            list_of_users.append(
+                [
+                    user["userId"],
+                    user["firstName"],
+                    user["lastName"],
+                    user["emailAddress"],
+                    user["status"],
+                    user["roles"],
+                ]
+            )
+        return list_of_users
+
+    def staging_promotion(self, destination_repo, tag):
+        """Promote repo assets to a new location.
+
+        :param destination_repo: the repo to promote into
+        :param tag: the tag used to identify the assets
+        """
+        data = {"tag": tag}
+        json_data = json.dumps(data)
+        result = self.post(
+            "v1/staging/move/{}".format(destination_repo), data=json_data
+        )
+        return result
+
+    def read_script(self, name):
+        """Get the contents of a script.
+
+        :param name: the script name
+        """
+        result = self.get("v1/script/{}".format(name))
+
+        if result[0].status_code == 200:
+            return result[1]
+        else:
+            return "Failed to read script {}".format(name)
+
+    def run_script(self, name):
+        """Run a script on the server.
+
+        :param name: the script name
+        """
+        result = self.post("v1/script/{}/run".format(name))
+
+        if result[0].status_code == 200:
+            return result[1]
+        else:
+            return "Failed to execute script {}".format(name)
+
+    def search_asset(self, query, repository, details=False):
+        """Search for an asset.
+
+        :param query: querystring to use, eg myjar-1 to find myjar-1.2.3.jar
+        :param repository: the repo to search in
+        :param details: returns a fully-detailed json dump
+        """
+        data = {
+            "q": query,
+            "repository": repository,
+        }
+        json_data = json.dumps(data)
+        result = self.get(
+            "v1/search/assets?q={}&repository={}".format(query, repository),
+            data=json_data,
+        )[1]["items"]
+        list_of_assets = []
+
+        if details:
+            return json.dumps(result, indent=4)
+
+        for item in result:
+            list_of_assets.append(item["path"])
+
+        return list_of_assets
+
+    def update_script(self, name, content):
+        """Update an existing script on the server.
+
+        :param name: script name
+        :param content: new content for the script (groovy code)
+        """
+        data = {"name": name, "content": content, "type": "groovy"}
+
+        json_data = json.dumps(data)
+
+        result = self.put("v1/script/{}".format(name), data=json_data)
+
+        if result.status_code == 204:
+            return "Successfully updated {}".format(name)
+        else:
+            return "Failed to update script {}".format(name)
index 534d229..87370a9 100644 (file)
@@ -28,6 +28,7 @@ from lftools.cli.infofile import infofile
 from lftools.cli.jenkins import jenkins_cli
 from lftools.cli.lfidapi import lfidapi
 from lftools.cli.license import license
+from lftools.cli.nexus3 import nexus3
 from lftools.cli.nexus import nexus
 from lftools.cli.rtd import rtd
 from lftools.cli.schema import schema
@@ -87,6 +88,7 @@ cli.add_command(infofile)
 cli.add_command(jenkins_cli, name='jenkins')
 cli.add_command(license)
 cli.add_command(nexus)
+cli.add_command(nexus3)
 cli.add_command(rtd)
 cli.add_command(schema)
 cli.add_command(lfidapi)
diff --git a/lftools/cli/nexus3/__init__.py b/lftools/cli/nexus3/__init__.py
new file mode 100644 (file)
index 0000000..d408520
--- /dev/null
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: EPL-1.0
+##############################################################################
+# Copyright (c) 2019 The Linux Foundation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+##############################################################################
+
+"""Nexus3 REST API sub-interfaces."""
+
+from .asset import *
+from .privilege import *
+from .repository import *
+from .role import *
+from .script import *
+from .tag import *
+from .task import *
+from .user import *
+
+
+@click.group()
+@click.argument("fqdn")
+@click.pass_context
+def nexus3(ctx, fqdn):
+    """Provide an interface to Nexus3."""
+    nexus_obj = nexus.Nexus(fqdn=fqdn)
+    ctx.obj = {"nexus": nexus_obj}
+    pass
+
+
+nexus3.add_command(asset)
+nexus3.add_command(privilege)
+nexus3.add_command(repository)
+nexus3.add_command(role)
+nexus3.add_command(script)
+nexus3.add_command(tag)
+nexus3.add_command(task)
+nexus3.add_command(user)
diff --git a/lftools/cli/nexus3/asset.py b/lftools/cli/nexus3/asset.py
new file mode 100644 (file)
index 0000000..1aac887
--- /dev/null
@@ -0,0 +1,55 @@
+# SPDX-License-Identifier: EPL-1.0
+##############################################################################
+# Copyright (c) 2019 The Linux Foundation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+##############################################################################
+
+"""Nexus3 REST API asset interface."""
+
+import logging
+from pprint import pformat
+
+import click
+
+from lftools.api.endpoints import nexus
+
+log = logging.getLogger(__name__)
+
+
+@click.group()
+@click.pass_context
+def asset(ctx):
+    """Asset primary interface."""
+    pass
+
+
+@asset.command(name="list")
+@click.argument("repository")
+@click.pass_context
+def asset_list(ctx, repository):
+    """List assets."""
+    r = ctx.obj["nexus"]
+    data = r.list_assets(repository)
+    for item in data:
+        log.info(pformat(item))
+
+
+@asset.command(name="search")
+@click.argument("query-string")
+@click.argument("repository")
+@click.option("--details", is_flag=True)
+@click.pass_context
+def asset_search(ctx, query_string, repository, details):
+    """Search assets."""
+    r = ctx.obj["nexus"]
+    data = r.search_asset(query_string, repository, details)
+
+    if details:
+        log.info(data)
+    else:
+        for item in data:
+            log.info(item)
diff --git a/lftools/cli/nexus3/privilege.py b/lftools/cli/nexus3/privilege.py
new file mode 100644 (file)
index 0000000..32841da
--- /dev/null
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: EPL-1.0
+##############################################################################
+# Copyright (c) 2019 The Linux Foundation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+##############################################################################
+
+"""Nexus3 REST API privileges interface."""
+
+import logging
+
+import click
+from tabulate import tabulate
+
+log = logging.getLogger(__name__)
+
+
+@click.group()
+@click.pass_context
+def privilege(ctx):
+    """Privilege primary interface."""
+    pass
+
+
+@privilege.command(name="list")
+@click.pass_context
+def list_privileges(ctx):
+    """List privileges."""
+    r = ctx.obj["nexus"]
+    data = r.list_privileges()
+    log.info(
+        tabulate(data, headers=["Type", "Name", "Description", "Read Only"])
+    )
diff --git a/lftools/cli/nexus3/repository.py b/lftools/cli/nexus3/repository.py
new file mode 100644 (file)
index 0000000..981fe13
--- /dev/null
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: EPL-1.0
+##############################################################################
+# Copyright (c) 2019 The Linux Foundation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+##############################################################################
+
+"""Nexus3 REST API repository interface."""
+
+import logging
+from pprint import pformat
+
+import click
+
+from lftools.api.endpoints import nexus  # noqa: F401
+
+log = logging.getLogger(__name__)
+
+
+@click.group()
+@click.pass_context
+def repository(ctx):
+    """Repository primary interface."""
+    pass
+
+
+@repository.command(name="list")
+@click.pass_context
+def list_repositories(ctx):
+    """List repositories."""
+    r = ctx.obj["nexus"]
+    data = r.list_repositories()
+    log.info(pformat(data))
diff --git a/lftools/cli/nexus3/role.py b/lftools/cli/nexus3/role.py
new file mode 100644 (file)
index 0000000..6c25e1c
--- /dev/null
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: EPL-1.0
+##############################################################################
+# Copyright (c) 2019 The Linux Foundation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+##############################################################################
+
+"""Nexus3 REST API role interface."""
+
+import logging
+from pprint import pformat
+
+import click
+from tabulate import tabulate
+
+from lftools.api.endpoints import nexus  # noqa: F401
+
+log = logging.getLogger(__name__)
+
+
+@click.group()
+@click.pass_context
+def role(ctx):
+    """Role primary interface."""
+    pass
+
+
+@role.command(name="list")
+@click.pass_context
+def list_roles(ctx):
+    """List roles."""
+    r = ctx.obj["nexus"]
+    data = r.list_roles()
+    log.info(tabulate(data, headers=["Roles"]))
+
+
+@role.command(name="create")
+@click.argument("name")
+@click.argument("description")
+@click.argument("privileges")
+@click.argument("roles")
+@click.pass_context
+def create_role(ctx, name, description, privileges, roles):
+    """Create roles."""
+    r = ctx.obj["nexus"]
+    data = r.create_role(name, description, privileges, roles)
+    log.info(pformat(data))
diff --git a/lftools/cli/nexus3/script.py b/lftools/cli/nexus3/script.py
new file mode 100644 (file)
index 0000000..b02dc59
--- /dev/null
@@ -0,0 +1,87 @@
+# SPDX-License-Identifier: EPL-1.0
+##############################################################################
+# Copyright (c) 2019 The Linux Foundation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+##############################################################################
+
+"""Nexus3 REST API script interface."""
+
+import logging
+
+import click
+
+from lftools.api.endpoints import nexus  # noqa: F401
+
+log = logging.getLogger(__name__)
+
+
+@click.group()
+@click.pass_context
+def script(ctx):
+    """Script primary interface."""
+    pass
+
+
+@script.command(name="create")
+@click.argument("name")
+@click.argument("filename")
+@click.pass_context
+def create_script(ctx, name, filename):
+    """Create a new script."""
+    r = ctx.obj["nexus"]
+    data = r.create_script(name, filename)
+    log.info(data)
+
+
+@script.command(name="delete")
+@click.argument("name")
+@click.pass_context
+def delete_script(ctx, name):
+    """Delete a script."""
+    r = ctx.obj["nexus"]
+    data = r.delete_script(name)
+    log.info(data)
+
+
+@script.command(name="list")
+@click.pass_context
+def list_scripts(ctx):
+    """List all scripts."""
+    r = ctx.obj["nexus"]
+    data = r.list_scripts()
+    log.info(data)
+
+
+@script.command(name="read")
+@click.argument("name")
+@click.pass_context
+def read_script(ctx, name):
+    """Get script contents."""
+    r = ctx.obj["nexus"]
+    data = r.read_script(name)
+    log.info(data)
+
+
+@script.command(name="run")
+@click.argument("name")
+@click.pass_context
+def run_script(ctx, name):
+    """Run a script."""
+    r = ctx.obj["nexus"]
+    data = r.run_script(name)
+    log.info(data)
+
+
+@script.command(name="update")
+@click.argument("name")
+@click.argument("content")
+@click.pass_context
+def update_script(ctx, name, content):
+    """Update script contents."""
+    r = ctx.obj["nexus"]
+    data = r.update_script(name, content)
+    log.info(data)
diff --git a/lftools/cli/nexus3/tag.py b/lftools/cli/nexus3/tag.py
new file mode 100644 (file)
index 0000000..c98606d
--- /dev/null
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: EPL-1.0
+##############################################################################
+# Copyright (c) 2019 The Linux Foundation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+##############################################################################
+
+"""Nexus3 REST API tag interface."""
+
+import logging
+from pprint import pformat
+
+import click
+
+log = logging.getLogger(__name__)
+
+
+@click.group()
+@click.pass_context
+def tag(ctx):
+    """Tag primary interface."""
+    pass
+
+
+@tag.command(name="add")
+@click.argument("name")
+@click.argument("attributes", required=False)
+@click.pass_context
+def add_tag(ctx, name, attributes):
+    """Add a tag."""
+    r = ctx.obj["nexus"]
+    data = r.create_tag(name, attributes)
+    log.info(pformat(data))
+
+
+@tag.command(name="delete")
+@click.argument("name")
+@click.pass_context
+def delete_tag(ctx, name):
+    """Delete a tag."""
+    r = ctx.obj["nexus"]
+    data = r.delete_tag(name)
+    log.info(pformat(data))
+
+
+@tag.command(name="list")
+@click.pass_context
+def list_tags(ctx):
+    """List tags."""
+    r = ctx.obj["nexus"]
+    data = r.list_tags()
+    log.info(pformat(data))
+
+
+@tag.command(name="show")
+@click.argument("name")
+@click.pass_context
+def show_tag(ctx, name):
+    """Show tags."""
+    r = ctx.obj["nexus"]
+    data = r.show_tag(name)
+    log.info(pformat(data))
diff --git a/lftools/cli/nexus3/task.py b/lftools/cli/nexus3/task.py
new file mode 100644 (file)
index 0000000..5d30e25
--- /dev/null
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: EPL-1.0
+##############################################################################
+# Copyright (c) 2019 The Linux Foundation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+##############################################################################
+
+"""Nexus3 REST API task interface."""
+import logging
+
+import click
+from tabulate import tabulate
+
+from lftools.api.endpoints import nexus  # noqa: F401
+
+log = logging.getLogger(__name__)
+
+
+@click.group()
+@click.pass_context
+def task(ctx):
+    """Task primary interface."""
+    pass
+
+
+@task.command(name="list")
+@click.pass_context
+def list_tasks(ctx):
+    """List tasks."""
+    r = ctx.obj["nexus"]
+    data = r.list_tasks()
+    log.info(
+        tabulate(
+            data,
+            headers=["Name", "Message", "Current State", "Last Run Result"],
+        )
+    )
diff --git a/lftools/cli/nexus3/user.py b/lftools/cli/nexus3/user.py
new file mode 100644 (file)
index 0000000..43821a7
--- /dev/null
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: EPL-1.0
+##############################################################################
+# Copyright (c) 2019 The Linux Foundation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+##############################################################################
+
+"""Nexus3 REST API user interface."""
+
+import logging
+
+import click
+from tabulate import tabulate
+
+from lftools.api.endpoints import nexus  # noqa: F401
+
+log = logging.getLogger(__name__)
+
+
+@click.group()
+@click.pass_context
+def user(ctx):
+    """User primary interface."""
+    pass
+
+
+@user.command(name="search")
+@click.argument("username")
+@click.pass_context
+def search_user(ctx, username):
+    """Search users."""
+    r = ctx.obj["nexus"]
+    data = r.list_user(username)
+    log.info(
+        tabulate(
+            data,
+            headers=[
+                "User ID",
+                "First Name",
+                "Last Name",
+                "Email Address",
+                "Status",
+                "Roles",
+            ],
+        )
+    )
index ff39e97..27b1abd 100644 (file)
 ##############################################################################
 """Configuration subsystem for lftools."""
 
-__author__ = 'Thanh Ha'
 
 import configparser
 import logging
 import os.path
+import sys
 
 from xdg import XDG_CONFIG_HOME
 
@@ -25,7 +25,6 @@ LFTOOLS_CONFIG_FILE = os.path.join(XDG_CONFIG_HOME, 'lftools', 'lftools.ini')
 
 def get_config():
     """Get the config object."""
-    #config = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())  # noqa
     config = configparser.ConfigParser()  # noqa
     config.read(LFTOOLS_CONFIG_FILE)
     return config
@@ -39,6 +38,7 @@ def has_section(section):
 
 def get_setting(section, option=None):
     """Get a configuration from a section."""
+    sys.tracebacklimit = 0
     config = get_config()
 
     if option:
diff --git a/releasenotes/notes/nexus3-6a988f31e4876fd8.yaml b/releasenotes/notes/nexus3-6a988f31e4876fd8.yaml
new file mode 100644 (file)
index 0000000..167f390
--- /dev/null
@@ -0,0 +1,23 @@
+---
+features:
+  - |
+    Nexus3 API operations.
+
+    Usage: lftools nexus3 [OPTIONS] FQDN COMMAND [ARGS]...
+
+    .. code-block:: none
+
+       Commands:
+           asset       Asset primary interface.
+           privilege   Privilege primary interface.
+           repository  Repository primary interface.
+           role        Role primary interface.
+           script      Script primary interface.
+           tag         Tag primary interface.
+           task        Task primary interface.
+           user        User primary interface.
+
+    .. code-block:: none
+
+       Options:
+         --help             Show this message and exit.
index c3e0295..757d783 100644 (file)
@@ -1,19 +1,20 @@
+bs4
 click
-docker
 defusedxml # Needed due to tox complains on parseString not safe
+docker
+email_validator
+httplib2
 jsonschema
+lxml
+oauth2client
+pygerrit2
+pygithub
+python-jenkins
+pyyaml
 requests
 ruamel.yaml
 setuptools>=36.5.0
 six
-python-jenkins
+tabulate
 tqdm
 xdg==3.0.2
-pygithub
-httplib2
-email_validator
-oauth2client
-pyyaml
-pygerrit2
-bs4
-lxml