Clean up the contrib folder

Remove the content contributed by the community users such as api client and
harbor-py is no longer maintained and outdated that is not working with
recent releases.

Signed-off-by: Daniel Jiang <jiangd@vmware.com>
This commit is contained in:
Daniel Jiang 2018-09-10 20:37:32 +08:00
parent 7a8c1bfbac
commit 06765756e3
61 changed files with 0 additions and 4192 deletions

View File

@ -1,10 +0,0 @@
FROM python:2
MAINTAINER int32bit krystism@gmail.com
ADD . /opt/harborclient
RUN pip install -r /opt/harborclient/requirements.txt
RUN set -ex \
&& cd /opt/harborclient \
&& python setup.py install \
&& rm -rf /opt/harborclient
CMD ["harbor"]

View File

@ -1,575 +0,0 @@
## About This Project
Project Harbor is an enterprise-class registry server that stores and distributes Docker images. Harbor extends the open source Docker Distribution by adding the functionalities usually required by an enterprise, such as security, identity and management. As an enterprise private registry, Harbor offers better performance and security. Having a registry closer to the build and run environment improves the image transfer efficiency. Harbor supports the setup of multiple registries and has images replicated between them. With Harbor, the images are stored within the private registry, keeping the bits and intellectual properties behind the company firewall. In addition, Harbor offers advanced security features, such as user management, access control and activity auditing.
This project provides a great native command-line experience for managing Harbor resources like user, project, etc. It can be used on macOS, Linux, and Docker.
## Install Harbor CLI
Harbor CLI can be installed by one of three approaches:
* Option 1: Build as a Docker image(easy, recommended)
* Option 2: Native Installation from Source
* Option 3: Install from pypi
### Option 1: Build as a Docker image(easy, recommended)
We maintain a Docker prebuilt image with Harbor CLI. Install the CLI using `docker run`.
```sh
docker run -t -i krystism/harborclient harbor help
```
We strongly suggest you build image from code manually, because our prebuilt image may be not latest version.
```sh
docker build -t yourname/harborclient .
```
Run Harbor CLI as follows:
```bash
$ docker run --rm \
-e HARBOR_USERNAME="admin" \
-e HARBOR_PASSWORD="Harbor12345" \
-e HARBOR_PROJECT=1 \
-e HARBOR_URL="http://localhost" \
yourname/harborclient harbor info
+------------------------------+---------------------+
| Property | Value |
+------------------------------+---------------------+
| admiral_endpoint | NA |
| auth_mode | db_auth |
| disk_free | 4993355776 |
| disk_total | 18381979648 |
| harbor_version | v1.2.2 |
| has_ca_root | False |
| next_scan_all | 0 |
| project_creation_restriction | everyone |
| registry_url | localhost |
| self_registration | True |
| with_admiral | False |
| with_clair | False |
| with_notary | False |
+------------------------------+---------------------+
```
Create an alias:
```bash
alias harbor='docker run \
-e HARBOR_USERNAME="admin" \
-e HARBOR_PASSWORD="Harbor12345" \
-e HARBOR_PROJECT=1 \
-e HARBOR_URL="http://localhost" \
--rm krystism/harborclient harbor'
```
Then you can run Harbor CLI like:
```
$ harbor user-list
+---------+----------+----------+----------------------+--------------+-------------+
| user_id | username | is_admin | email | realname | comment |
+---------+----------+----------+----------------------+--------------+-------------+
| 1 | admin | 1 | admin@example.com | system admin | admin user |
| 2 | int32bit | 0 | int32bit@example.com | int32bit | int32bit |
+---------+----------+----------+----------------------+--------------+-------------+
```
### Option 2: Native Installation from Source
The installation steps boil down to the following:
#### Install requirements
```
sudo pip install -r requirements.txt
```
#### Install Harbor CLI.
```sh
sudo python setup.py install
```
Or
```sh
sudo pip install .
```
### Option 3: Install from pypi
```
sudo pip install python-harborclient
```
### Verify operation
As the `admin` user, do a `info` request:
```
$ harbor --os-baseurl https://localhost --os-username admin --os-project 1 info
password: *****
+------------------------------+---------------------+
| Property | Value |
+------------------------------+---------------------+
| admiral_endpoint | NA |
| auth_mode | db_auth |
| disk_free | 4992696320 |
| disk_total | 18381979648 |
| harbor_version | v1.2.2 |
| has_ca_root | False |
| next_scan_all | 0 |
| project_creation_restriction | everyone |
| registry_url | localhost |
| self_registration | True |
| with_admiral | False |
| with_clair | False |
| with_notary | False |
+------------------------------+---------------------+
```
### Create harbor client environment scripts
To increase efficiency of client operations, Harbor CLI supports simple client environment scrips also known as `harborrc` file.
These scripts typically contain common options for all client, but also support unique options.
Create client environment scripts for `admin` user:
```bash
cat >admin-harborrc <<EOF
export HARBOR_USERNAME=admin
export HARBOR_PASSWORD=Harbor12345
export HARBOR_URL=http://localhost
export HARBOR_PROJECT=1
EOF
```
Replace `HARBOR_PASSWORD` with your password.
To run clients as a specific project and user, you can simply load the associated client environment script prior to running them.
```bash
source admin-harborrc
```
List images:
```bash
$ harbor list
+-----------------------+------------+-----------+------------+------------+------------+----------------------+
| name | project_id | size | tags_count | star_count | pull_count | update_time |
+-----------------------+------------+-----------+------------+------------+------------+----------------------+
| int32bit/busybox | 2 | 715181 | 1 | 0 | 0 | 2017-11-01T07:06:36Z |
| int32bit/golang:1.7.3 | 2 | 257883053 | 2 | 0 | 0 | 2017-11-01T12:59:05Z |
| int32bit/hello-world | 2 | 974 | 1 | 0 | 0 | 2017-11-01T13:22:46Z |
+-----------------------+------------+-----------+------------+------------+------------+----------------------+
```
### Setup bash completion
```bash
$ complete -W $(harbor bash-completion) harbor
$ harbor us<tab><tab>
usage user-create user-delete user-list user-show user-update
```
## User Guide
This guide walks you through the fundamentals of using Harbor CLI. You'll learn how to use Harbor CLI to:
* Manage your projects.
* Manage members of a project.
* Search projects and repositories.
* Manage users.
* Manage replication policies.
* Manage configuration.
* Delete repositories and images.
* Show logs.
* Get statistics data.
* ...
Once you install Harbor CLI, you can run `harbor help` to get usage:
```bash
$ harbor help
usage: harbor [--debug] [--timings] [--version] [--os-username <username>]
[--os-password <password>] [--os-project <project>]
[--timeout <timeout>] [--os-baseurl <baseurl>] [--insecure]
[--os-cacert <ca-certificate>] [--os-api-version <api-version>]
<subcommand> ...
```
Run "harbor help COMMAND" for help on a specific command.
```bash
$ harbor help user-create
usage: harbor user-create --username <username> --password <password> --email
<email> [--realname <realname>]
[--comment <comment>]
Create a new User.
Optional arguments:
--username <username> Unique name of the new user.
--password <password> Password of the new user.
--email <email> Email of the new user.
--realname <realname> Realname of the new user.
--comment <comment> Comment of the new user.
```
Show details about API requests using `--debug` option:
```bash
$ harbor --debug --insecure project-list
DEBUG (connectionpool:824) Starting new HTTPS connection (1): devstack
DEBUG (connectionpool:396) https://devstack:443 "POST /login HTTP/1.1" 200 0
DEBUG (client:274) Successfully login, session id: 2642a18db2cb0fb207bd721899da9f8b
REQ: curl -g -i --insecure 'https://devstack/api/projects' -X GET -H "Accept: application/json" -H "Harbor-API-Version: v2" -H "User-Agent: python-harborclient" -b "beegosessionID: 2642a18db2cb0fb207bd721899da9f8b"
DEBUG (connectionpool:824) Starting new HTTPS connection (1): devstack
DEBUG (connectionpool:396) https://devstack:443 "GET /api/projects HTTP/1.1" 200 316
RESP: [200] {'Content-Length': '316', 'Content-Encoding': 'gzip', 'X-Total-Count': '2', 'Server': 'nginx/1.11.13', 'Connection': 'keep-alive', 'Date': 'Mon, 06 Nov 2017 12:24:53 GMT', 'Content-Type': 'application/json; charset=utf-8'}
RESP BODY: [{"creation_time_str": "", "enable_content_trust": false, "Togglable": true, "owner_name": "", "name": "int32bit", "deleted": 0, "repo_count": 3, "creation_time": "2017-11-01T06:56:07Z", "update_time": "2017-11-01T06:56:07Z", "prevent_vulnerable_images_from_running": false, "current_user_role_id": 1, "project_id": 2, "automatically_scan_images_on_push": false, "public": 1, "prevent_vulnerable_images_from_running_severity": "", "owner_id": 1}, {"creation_time_str": "", "enable_content_trust": false, "Togglable": true, "owner_name": "", "name": "library", "deleted": 0, "repo_count": 0, "creation_time": "2017-11-01T06:08:43Z", "update_time": "2017-11-01T06:08:43Z", "prevent_vulnerable_images_from_running": false, "current_user_role_id": 1, "project_id": 1, "automatically_scan_images_on_push": false, "public": 1, "prevent_vulnerable_images_from_running_severity": "", "owner_id": 1}]
+------------+----------+----------+----------------------+------------+----------------------+--------+
| project_id | name | owner_id | current_user_role_id | repo_count | creation_time | public |
+------------+----------+----------+----------------------+------------+----------------------+--------+
| 1 | library | 1 | 1 | 0 | 2017-11-01T06:08:43Z | 1 |
| 2 | int32bit | 1 | 1 | 3 | 2017-11-01T06:56:07Z | 1 |
+------------+----------+----------+----------------------+------------+----------------------+--------+
```
Print call timing info with `--timings` option:
```
$ harbor --insecure --timings user-list
+---------+----------+----------+----------------------+--------------+-------------+
| user_id | username | is_admin | email | realname | comment |
+---------+----------+----------+----------------------+--------------+-------------+
| 1 | admin | 1 | admin@example.com | system admin | admin user |
| 3 | int32bit | 0 | int32bit@example.com | int32bit | test |
+---------+----------+----------+----------------------+--------------+-------------+
+--------------+-----------------+
| url | seconds |
+--------------+-----------------+
| GET /users | 0.0146510601044 |
| GET /users/1 | 0.0146780014038 |
| Total | 0.0293290615082 |
+--------------+-----------------+
Total: 0.0293290615082 seconds
```
All SSL connections are attempted to be made secure by using the CA certificate bundle installed by default. This makes all connections considered "insecure" fail unless `--insecure` is used.
```
$ harbor info
Traceback (most recent call last):
File "/usr/local/bin/harbor", line 10, in <module>
sys.exit(main())
File "/usr/local/lib/python2.7/dist-packages/harborclient/shell.py", line 404, in main
HarborShell().main(argv)
File "/usr/local/lib/python2.7/dist-packages/harborclient/shell.py", line 330, in main
self.cs.authenticate()
File "/usr/local/lib/python2.7/dist-packages/harborclient/v2/client.py", line 83, in authenticate
self.client.authenticate()
File "/usr/local/lib/python2.7/dist-packages/harborclient/client.py", line 270, in authenticate
verify=self.verify_cert)
File "/usr/local/lib/python2.7/dist-packages/requests/api.py", line 112, in post
return request('post', url, data=data, json=json, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/requests/api.py", line 58, in request
return session.request(method=method, url=url, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/requests/sessions.py", line 508, in request
resp = self.send(prep, **send_kwargs)
File "/usr/local/lib/python2.7/dist-packages/requests/sessions.py", line 618, in send
r = adapter.send(request, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/requests/adapters.py", line 506, in send
raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='devstack', port=443): Max retries exceeded with url: /login (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",),))
$ harbor --insecure info
+------------------------------+---------------------+
| Property | Value |
+------------------------------+---------------------+
| admiral_endpoint | NA |
| auth_mode | db_auth |
| disk_free | 4991021056 |
| disk_total | 18381979648 |
| harbor_version | v1.2.2 |
| has_ca_root | False |
| next_scan_all | 0 |
| project_creation_restriction | everyone |
| registry_url | 192.168.99.101:8888 |
| self_registration | True |
| with_admiral | False |
| with_clair | False |
| with_notary | False |
+------------------------------+---------------------+
```
## Examples
### Create a new user
```
$ harbor --insecure user-create \
--username new-user \
--password 1q2w3e4r \
--email new_user@example.com \
--realname newuser \
--comment "I am a new user"
Create user 'new-user' successfully.
```
### Delete a user
```
$ harbor --insecure user-delete new-user
Delete user 'new-user' sucessfully.
```
### List repositories and images
```
$ harbor list
+-----------------------+------------+-----------+------------+------------+------------+----------------------+
| name | project_id | size | tags_count | star_count | pull_count | update_time |
+-----------------------+------------+-----------+------------+------------+------------+----------------------+
| int32bit/busybox | 2 | 715181 | 1 | 0 | 0 | 2017-11-01T07:06:36Z |
| int32bit/golang:1.7.3 | 2 | 257883053 | 2 | 0 | 0 | 2017-11-01T12:59:05Z |
| int32bit/hello-world | 2 | 974 | 1 | 0 | 0 | 2017-11-01T13:22:46Z |
+-----------------------+------------+-----------+------------+------------+------------+----------------------+
```
### Show details about image
```
$ harbor show int32bit/golang:1.7.3
+--------------------+-------------------------------------------------------------------------+
| Property | Value |
+--------------------+-------------------------------------------------------------------------+
| creation_time | 2017-11-01T12:59:05Z |
| description | |
| id | 2 |
| name | int32bit/golang |
| project_id | 2 |
| pull_count | 0 |
| star_count | 0 |
| tag_architecture | amd64 |
| tag_author | |
| tag_created | 2016-11-08T19:32:39.908048617Z |
| tag_digest | sha256:37d263ccd240e113a752c46306ad004e36532ce118eb3131d9f76f43cc606d5d |
| tag_docker_version | 1.12.3 |
| tag_name | 1.7.3 |
| tag_os | linux |
| tag_signature | - |
| tags_count | 2 |
| update_time | 2017-11-01T12:59:05Z |
+--------------------+-------------------------------------------------------------------------+
```
### Get top accessed repositories
```
$ harbor top
+----------------------+------------+------------+
| name | pull_count | star_count |
+----------------------+------------+------------+
| int32bit/busybox | 10 | 0 |
| int32bit/golang | 8 | 0 |
| int32bit/hello-world | 1 | 0 |
+----------------------+------------+------------+
```
### Lists members of a project.
```
$ harbor member-list
+----------+--------------+---------+---------+
| username | role_name | user_id | role_id |
+----------+--------------+---------+---------+
| admin | projectAdmin | 1 | 1 |
| foo | developer | 5 | 2 |
| test | guest | 6 | 3 |
+----------+--------------+---------+---------+
```
### Show logs
```
$ harbor logs
+--------+----------------------+----------+------------+-----------+-----------------------------+
| log_id | op_time | username | project_id | operation | repository |
+--------+----------------------+----------+------------+-----------+-----------------------------+
| 1 | 2017-11-01T06:56:07Z | admin | 2 | create | int32bit/ |
| 2 | 2017-11-01T07:06:36Z | admin | 2 | push | int32bit/busybox:latest |
| 3 | 2017-11-01T12:59:05Z | admin | 2 | push | int32bit/golang:1.7.3 |
| 4 | 2017-11-01T13:22:46Z | admin | 2 | push | int32bit/hello-world:latest |
| 5 | 2017-11-01T14:21:49Z | admin | 2 | push | int32bit/golang:latest |
| 6 | 2017-11-03T20:39:04Z | admin | 3 | create | test/ |
| 7 | 2017-11-03T20:39:22Z | admin | 3 | delete | test/ |
| 8 | 2017-11-03T20:39:38Z | admin | 4 | create | test/ |
| 9 | 2017-11-03T20:49:33Z | admin | 4 | delete | test/ |
+--------+----------------------+----------+------------+-----------+-----------------------------+
```
### Search projects and repositories.
```
$ harbor search int32bit
Find 1 Projects:
+------------+----------+--------+------------+----------------------+
| project_id | name | public | repo_count | creation_time |
+------------+----------+--------+------------+----------------------+
| 2 | int32bit | 1 | 3 | 2017-11-01T06:56:07Z |
+------------+----------+--------+------------+----------------------+
Find 3 Repositories:
+----------------------+--------------+------------+----------------+
| repository_name | project_name | project_id | project_public |
+----------------------+--------------+------------+----------------+
| int32bit/busybox | int32bit | 2 | 1 |
| int32bit/golang | int32bit | 2 | 1 |
| int32bit/hello-world | int32bit | 2 | 1 |
+----------------------+--------------+------------+----------------+
```
### Lists targets
```
$ harbor target-list
+----+----------------------+-------------------------------------+----------+----------+----------------------+
| id | name | endpoint | username | password | creation_time |
+----+----------------------+-------------------------------------+----------+----------+----------------------+
| 1 | test-target | http://192.168.99.101:8888 | admin | - | 2017-11-02T01:35:30Z |
| 2 | test-target-2 | http://192.168.99.101:9999 | admin | - | 2017-11-02T13:43:07Z |
| 3 | int32bit-test-target | http://192.168.99.101:8888/int32bit | admin | - | 2017-11-02T14:28:54Z |
+----+----------------------+-------------------------------------+----------+----------+----------------------+
```
### Ping a target
```
$ harbor target-ping 1
OK
```
### Lists replication job
```
$ harbor job-list 1
+----+----------------------+-----------+----------+----------------------+
| id | repository | operation | status | update_time |
+----+----------------------+-----------+----------+----------------------+
| 1 | int32bit/busybox | transfer | finished | 2017-11-02T01:35:31Z |
| 2 | int32bit/golang | transfer | finished | 2017-11-02T01:35:31Z |
| 3 | int32bit/hello-world | transfer | finished | 2017-11-02T01:35:31Z |
+----+----------------------+-----------+----------+----------------------+
```
### Show job logs:
```
$ harbor job-log 1
2017-11-02T01:35:30Z [INFO] initializing: repository: int32bit/busybox, tags: [], source URL: http://registry:5000, destination URL: http://192.168.99.101:8888, insecure: false, destination user: admin
2017-11-02T01:35:30Z [INFO] initialization completed: project: int32bit, repository: int32bit/busybox, tags: [latest], source URL: http://registry:5000, destination URL: http://192.168.99.101:8888, insecure: false, destination user: admin
2017-11-02T01:35:30Z [WARNING] the status code is 409 when creating project int32bit on http://192.168.99.101:8888 with user admin, try to do next step
2017-11-02T01:35:30Z [INFO] manifest of int32bit/busybox:latest pulled successfully from http://registry:5000: sha256:030fcb92e1487b18c974784dcc110a93147c9fc402188370fbfd17efabffc6af
2017-11-02T01:35:30Z [INFO] all blobs of int32bit/busybox:latest from http://registry:5000: [sha256:54511612f1c4d97e93430fc3d5dc2f05dfbe8fb7e6259b7351deeca95eaf2971 sha256:03b1be98f3f9b05cb57782a3a71a44aaf6ec695de5f4f8e6c1058cd42f04953e]
2017-11-02T01:35:31Z [INFO] blob sha256:54511612f1c4d97e93430fc3d5dc2f05dfbe8fb7e6259b7351deeca95eaf2971 of int32bit/busybox:latest already exists in http://192.168.99.101:8888
2017-11-02T01:35:31Z [INFO] blob sha256:03b1be98f3f9b05cb57782a3a71a44aaf6ec695de5f4f8e6c1058cd42f04953e of int32bit/busybox:latest already exists in http://192.168.99.101:8888
2017-11-02T01:35:31Z [INFO] blobs of int32bit/busybox:latest need to be transferred to http://192.168.99.101:8888: []
2017-11-02T01:35:31Z [INFO] manifest of int32bit/busybox:latest exists on source registry http://registry:5000, continue manifest pushing
2017-11-02T01:35:31Z [INFO] manifest of int32bit/busybox:latest exists on destination registry http://192.168.99.101:8888, skip manifest pushing
2017-11-02T01:35:31Z [INFO] no tag needs to be replicated, next state is "finished"
```
### Show usage
```
$ harbor usage
+-----------------------+-------+
| Property | Value |
+-----------------------+-------+
| private_project_count | 0 |
| private_repo_count | 0 |
| public_project_count | 2 |
| public_repo_count | 3 |
| total_project_count | 2 |
| total_repo_count | 3 |
+-----------------------+-------+
```
### Show Harbor info
```
$ harbor info
+------------------------------+---------------------+
| Property | Value |
+------------------------------+---------------------+
| admiral_endpoint | NA |
| auth_mode | db_auth |
| disk_free | 4989370368 |
| disk_total | 18381979648 |
| harbor_version | v1.2.2 |
| has_ca_root | False |
| next_scan_all | 0 |
| project_creation_restriction | everyone |
| registry_url | 192.168.99.101:8888 |
| self_registration | True |
| with_admiral | False |
| with_clair | False |
| with_notary | False |
+------------------------------+---------------------+
```
### Get configrations
```
$ harbor get-conf
+------------------------------+-------------------------------------------------------+----------+
| name | value | editable |
+------------------------------+-------------------------------------------------------+----------+
| auth_mode | db_auth | False |
| email_from | admin <sample_admin@mydomain.com> | True |
| email_host | smtp.mydomain.com | True |
| email_identity | - | True |
| email_port | 25 | True |
| email_ssl | False | True |
| email_username | sample_admin@mydomain.com | True |
| ldap_base_dn | ou=people,dc=mydomain,dc=com | True |
| ldap_filter | - | True |
| ldap_scope | 3 | True |
| ldap_search_dn | - | True |
| ldap_timeout | 5 | True |
| ldap_uid | uid | True |
| ldap_url | ldaps://ldap.mydomain.com | True |
| project_creation_restriction | everyone | True |
| scan_all_policy | {u'parameter': {u'daily_time': 0}, u'type': u'daily'} | True |
| self_registration | True | True |
| token_expiration | 30 | True |
| verify_remote_cert | True | True |
+------------------------------+-------------------------------------------------------+----------+
```
### Update user password
```
$ harbor change-password int32bit
Old password: *****
New Password: *****
Retype new Password: *****
Update password successfully.
```
### Promote a user to administrator
```
$ harbor promote int32bit
Promote user 'int32bit' as administrator successfully.
```
## Licensing
HarborClient is licensed under the MIT License, Version 2.0. See [LICENSE](./LICENSE) for the full license text.

View File

@ -1,13 +0,0 @@
import pbr.version
from harborclient import api_versions
__version__ = pbr.version.VersionInfo('python-harborclient').version_string()
API_MIN_VERSION = api_versions.APIVersion("2.0")
# The max version should be the latest version that is supported in the client,
# not necessarily the latest that the server can provide. This is only bumped
# when client supported the max version, and bumped sequentially, otherwise
# the client may break due to server side new version may include some
# backward incompatible change.
API_MAX_VERSION = api_versions.APIVersion("2.0")

View File

@ -1,274 +0,0 @@
import logging
import os
import pkgutil
import re
import harborclient
from harborclient import exceptions
LOG = logging.getLogger(__name__)
_type_error_msg = "'%(other)s' should be an instance of '%(cls)s'"
if not LOG.handlers:
LOG.addHandler(logging.StreamHandler())
class APIVersion(object):
"""This class represents an API Version Request.
This class provides convenience methods for manipulation
and comparison of version numbers that we need to do to
implement microversions.
"""
def __init__(self, version_str=None):
"""Create an API version object.
:param version_str: String representation of APIVersionRequest.
Correct format is 'X.Y', where 'X' and 'Y'
are int values. None value should be used
to create Null APIVersionRequest, which is
equal to 0.0
"""
self.ver_major = 0
self.ver_minor = 0
if version_str is not None:
match = re.match(r"^([1-9]\d*)\.([1-9]\d*|0|latest)$", version_str)
if match:
self.ver_major = int(match.group(1))
if match.group(2) == "latest":
# NOTE(andreykurilin): Infinity allows to easily determine
# latest version and doesn't require any additional checks
# in comparison methods.
self.ver_minor = float("inf")
else:
self.ver_minor = int(match.group(2))
else:
msg = ("Invalid format of client version '%s'. "
"Expected format 'X.Y', where X is a major part and Y "
"is a minor part of version.") % version_str
raise exceptions.UnsupportedVersion(msg)
def __str__(self):
"""Debug/Logging representation of object."""
if self.is_latest():
return "Latest API Version Major: %s" % self.ver_major
return ("API Version Major: %s, Minor: %s" % (self.ver_major,
self.ver_minor))
def __repr__(self):
if self.is_null():
return "<APIVersion: null>"
else:
return "<APIVersion: %s>" % self.get_string()
def is_null(self):
return self.ver_major == 0 and self.ver_minor == 0
def is_latest(self):
return self.ver_minor == float("inf")
def __lt__(self, other):
if not isinstance(other, APIVersion):
raise TypeError(
_type_error_msg % {"other": other,
"cls": self.__class__})
return ((self.ver_major, self.ver_minor) <
(other.ver_major, other.ver_minor))
def __eq__(self, other):
if not isinstance(other, APIVersion):
raise TypeError(
_type_error_msg % {"other": other,
"cls": self.__class__})
return ((self.ver_major, self.ver_minor) == (other.ver_major,
other.ver_minor))
def __gt__(self, other):
if not isinstance(other, APIVersion):
raise TypeError(
_type_error_msg % {"other": other,
"cls": self.__class__})
return ((self.ver_major, self.ver_minor) >
(other.ver_major, other.ver_minor))
def __le__(self, other):
return self < other or self == other
def __ne__(self, other):
return not self.__eq__(other)
def __ge__(self, other):
return self > other or self == other
def matches(self, min_version, max_version):
"""Matches the version object.
Returns whether the version object represents a version
greater than or equal to the minimum version and less than
or equal to the maximum version.
:param min_version: Minimum acceptable version.
:param max_version: Maximum acceptable version.
:returns: boolean
If min_version is null then there is no minimum limit.
If max_version is null then there is no maximum limit.
If self is null then raise ValueError
"""
if self.is_null():
raise ValueError("Null APIVersion doesn't support 'matches'.")
if max_version.is_null() and min_version.is_null():
return True
elif max_version.is_null():
return min_version <= self
elif min_version.is_null():
return self <= max_version
else:
return min_version <= self <= max_version
def get_string(self):
"""Version string representation.
Converts object to string representation which if used to create
an APIVersion object results in the same version.
"""
if self.is_null():
raise ValueError("Null APIVersion cannot be converted to string.")
elif self.is_latest():
return "%s.%s" % (self.ver_major, "latest")
return "%s.%s" % (self.ver_major, self.ver_minor)
class VersionedMethod(object):
def __init__(self, name, start_version, end_version, func):
"""Versioning information for a single method
:param name: Name of the method
:param start_version: Minimum acceptable version
:param end_version: Maximum acceptable_version
:param func: Method to call
Minimum and maximums are inclusive
"""
self.name = name
self.start_version = start_version
self.end_version = end_version
self.func = func
def __str__(self):
return ("Version Method %s: min: %s, max: %s" %
(self.name, self.start_version, self.end_version))
def __repr__(self):
return "<VersionedMethod %s>" % self.name
def get_available_major_versions():
# NOTE(andreykurilin): available clients version should not be
# hardcoded, so let's discover them.
matcher = re.compile(r"v[0-9]*$")
submodules = pkgutil.iter_modules([os.path.dirname(__file__)])
available_versions = [
name[1:] for loader, name, ispkg in submodules if matcher.search(name)
]
return available_versions
def check_major_version(api_version):
"""Checks major part of ``APIVersion`` obj is supported.
:raises harborclient.exceptions.UnsupportedVersion: if major part is not
supported
"""
available_versions = get_available_major_versions()
if (not api_version.is_null() and
str(api_version.ver_major) not in available_versions):
if len(available_versions) == 1:
msg = ("Invalid client version '%(version)s'. "
"Major part should be '%(major)s'") % {
"version": api_version.get_string(),
"major": available_versions[0]}
else:
msg = ("Invalid client version '%(version)s'. "
"Major part must be one of: '%(major)s'") % {
"version": api_version.get_string(),
"major": ", ".join(available_versions)}
raise exceptions.UnsupportedVersion(msg)
def get_api_version(version_string):
"""Returns checked APIVersion object"""
version_string = str(version_string)
api_version = APIVersion(version_string)
check_major_version(api_version)
return api_version
def _get_server_version_range(client):
version = client.versions.get_current()
if not hasattr(version, 'version') or not version.version:
return APIVersion(), APIVersion()
return APIVersion(version.min_version), APIVersion(version.version)
def discover_version(client, requested_version):
"""Discover most recent version supported by API and client.
Checks ``requested_version`` and returns the most recent version
supported by both the API and the client.
:param client: client object
:param requested_version: requested version represented by APIVersion obj
:returns: APIVersion
"""
server_start_version, server_end_version = _get_server_version_range(
client)
if (not requested_version.is_latest() and
requested_version != APIVersion('2.0')):
if server_start_version.is_null() and server_end_version.is_null():
raise exceptions.UnsupportedVersion(
("Server doesn't support microversions"))
if not requested_version.matches(server_start_version,
server_end_version):
raise exceptions.UnsupportedVersion(
("The specified version isn't supported by server. The valid "
"version range is '%(min)s' to '%(max)s'") % {
"min": server_start_version.get_string(),
"max": server_end_version.get_string()})
return requested_version
if server_start_version.is_null() and server_end_version.is_null():
return APIVersion('2.0')
elif harborclient.API_MIN_VERSION > server_end_version:
raise exceptions.UnsupportedVersion(
("Server version is too old. The client valid version range is "
"'%(client_min)s' to '%(client_max)s'. The server valid version "
"range is '%(server_min)s' to '%(server_max)s'.") % {
'client_min': harborclient.API_MIN_VERSION.get_string(),
'client_max': harborclient.API_MAX_VERSION.get_string(),
'server_min': server_start_version.get_string(),
'server_max': server_end_version.get_string()})
elif harborclient.API_MAX_VERSION < server_start_version:
raise exceptions.UnsupportedVersion(
("Server version is too new. The client valid version range is "
"'%(client_min)s' to '%(client_max)s'. The server valid version "
"range is '%(server_min)s' to '%(server_max)s'.") % {
'client_min': harborclient.API_MIN_VERSION.get_string(),
'client_max': harborclient.API_MAX_VERSION.get_string(),
'server_min': server_start_version.get_string(),
'server_max': server_end_version.get_string()})
elif harborclient.API_MAX_VERSION <= server_end_version:
return harborclient.API_MAX_VERSION
elif server_end_version < harborclient.API_MAX_VERSION:
return server_end_version

View File

@ -1,41 +0,0 @@
"""
Base utilities to build API operation managers and objects on top of.
"""
class Manager(object):
"""Manager for API service.
Managers interact with a particular type of API (projects, users,
reposiries,etc.) and provide CRUD operations for them.
"""
def __init__(self, api):
self.api = api
@property
def client(self):
return self.api.client
@property
def api_version(self):
return self.api.api_version
def _list(self, url, body=None):
if body:
data = self.api.client.post(url, body=body)
else:
data = self.api.client.get(url)
return data
def _get(self, url):
return self.api.client.get(url)
def _create(self, url, body=None, **kwargs):
return self.api.client.post(url, body=body)
def _delete(self, url):
return self.api.client.delete(url)
def _update(self, url, body, **kwargs):
return self.api.client.put(url, body=body)

View File

@ -1,374 +0,0 @@
"""
Harbor Client interface. Handles the REST calls and responses.
"""
import copy
import hashlib
import logging
from urlparse import urlparse
from oslo_utils import importutils
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
try:
import json
except ImportError:
import simplejson as json
from harborclient import api_versions
from harborclient import exceptions
from harborclient import utils
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
class HTTPClient(object):
USER_AGENT = 'python-harborclient'
def __init__(self,
username,
password,
project,
baseurl,
timeout=None,
timings=False,
http_log_debug=False,
cacert=None,
insecure=False,
api_version=None):
self.username = username
self.password = password
self.project = project
self.baseurl = baseurl
self.api_version = api_version or api_versions.APIVersion()
self.timings = timings
self.http_log_debug = http_log_debug
# Has no protocol, use http
if not urlparse(baseurl).scheme:
self.baseurl = 'http://' + baseurl
parsed_url = urlparse(self.baseurl)
self.protocol = parsed_url.scheme
self.host = parsed_url.hostname
self.port = parsed_url.port
if timeout is not None:
self.timeout = float(timeout)
else:
self.timeout = None
# https
if insecure:
self.verify_cert = False
else:
if cacert:
self.verify_cert = cacert
else:
self.verify_cert = True
self.times = [] # [("item", starttime, endtime), ...]
self._logger = logging.getLogger(__name__)
self.session_id = None
if self.http_log_debug and not self._logger.handlers:
# Logging level is already set on the root logger
ch = logging.StreamHandler()
self._logger.addHandler(ch)
self._logger.propagate = False
if hasattr(requests, 'logging'):
rql = requests.logging.getLogger(requests.__name__)
rql.addHandler(ch)
# Since we have already setup the root logger on debug, we
# have to set it up here on WARNING (its original level)
# otherwise we will get all the requests logging messages
rql.setLevel(logging.WARNING)
def unauthenticate(self):
"""Forget all of our authentication information."""
requests.get(
'%s://%s/logout' % (self.protocol, self.host),
cookies={'beegosessionID': self.session_id},
verify=self.verify_cert)
logging.debug("Successfully logout")
def get_timings(self):
return self.times
def reset_timings(self):
self.times = []
def _redact(self, target, path, text=None):
"""Replace the value of a key in `target`.
The key can be at the top level by specifying a list with a single
key as the path. Nested dictionaries are also supported by passing a
list of keys to be navigated to find the one that should be replaced.
In this case the last one is the one that will be replaced.
:param dict target: the dictionary that may have a key to be redacted;
modified in place
:param list path: a list representing the nested structure in `target`
that should be redacted; modified in place
:param string text: optional text to use as a replacement for the
redacted key. if text is not specified, the
default text will be sha1 hash of the value being
redacted
"""
key = path.pop()
# move to the most nested dict
for p in path:
try:
target = target[p]
except KeyError:
return
if key in target:
if text:
target[key] = text
elif target[key] is not None:
# because in python3 byte string handling is ... ug
value = target[key].encode('utf-8')
sha1sum = hashlib.sha1(value)
target[key] = "{SHA1}%s" % sha1sum.hexdigest()
def http_log_req(self, method, url, kwargs):
if not self.http_log_debug:
return
string_parts = ['curl -g -i']
if self.verify_cert is not None:
if not self.verify_cert:
string_parts.append(' --insecure')
string_parts.append(" '%s'" % url)
string_parts.append(' -X %s' % method)
headers = copy.deepcopy(kwargs['headers'])
# because dict ordering changes from 2 to 3
keys = sorted(headers.keys())
for name in keys:
value = headers[name]
header = ' -H "%s: %s"' % (name, value)
string_parts.append(header)
cookies = kwargs['cookies']
for name in sorted(cookies.keys()):
value = cookies[name]
cookie = header = ' -b "%s: %s"' % (name, value)
string_parts.append(cookie)
if 'data' in kwargs:
data = json.loads(kwargs['data'])
string_parts.append(" -d '%s'" % json.dumps(data))
self._logger.debug("REQ: %s" % "".join(string_parts))
def http_log_resp(self, resp):
if not self.http_log_debug:
return
if resp.text and resp.status_code != 400:
try:
body = json.loads(resp.text)
except ValueError:
body = None
else:
body = None
self._logger.debug("RESP: [%(status)s] %(headers)s\nRESP BODY: "
"%(text)s\n", {
'status': resp.status_code,
'headers': resp.headers,
'text': json.dumps(body)
})
def request(self, url, method, **kwargs):
url = self.baseurl + "/api" + url
kwargs.setdefault('headers', kwargs.get('headers', {}))
kwargs['headers']['User-Agent'] = self.USER_AGENT
kwargs['headers']['Accept'] = 'application/json'
if 'body' in kwargs:
kwargs['headers']['Content-Type'] = 'application/json'
kwargs['data'] = json.dumps(kwargs['body'])
del kwargs['body']
kwargs["headers"]['Harbor-API-Version'] = "v2"
if self.timeout is not None:
kwargs.setdefault('timeout', self.timeout)
self.http_log_req(method, url, kwargs)
resp = requests.request(method, url, verify=self.verify_cert, **kwargs)
self.http_log_resp(resp)
if resp.status_code >= 400:
raise exceptions.from_response(resp, resp.text, url, method)
try:
body = json.loads(resp.text)
except ValueError:
body = resp.text
return body
def _time_request(self, url, method, **kwargs):
with utils.record_time(self.times, self.timings, method, url):
body = self.request(url, method, **kwargs)
return body
def _cs_request(self, url, method, **kwargs):
if not self.session_id:
self.authenticate()
# Perform the request once. If we get a 401 back then it
# might be because the auth token expired, so try to
# re-authenticate and try again. If it still fails, bail.
try:
body = self._time_request(
url,
method,
cookies={'beegosessionID': self.session_id},
**kwargs)
return body
except exceptions.Unauthorized as e:
try:
# first discard auth token, to avoid the possibly expired
# token being re-used in the re-authentication attempt
self.unauthenticate()
# overwrite bad token
self.authenticate()
body = self._time_request(url, method, **kwargs)
return body
except exceptions.Unauthorized:
raise e
def get(self, url, **kwargs):
return self._cs_request(url, 'GET', **kwargs)
def post(self, url, **kwargs):
return self._cs_request(url, 'POST', **kwargs)
def put(self, url, **kwargs):
return self._cs_request(url, 'PUT', **kwargs)
def delete(self, url, **kwargs):
return self._cs_request(url, 'DELETE', **kwargs)
def authenticate(self):
if not self.baseurl:
msg = ("Authentication requires 'baseurl', which should be "
"specified in '%s'") % self.__class__.__name__
raise exceptions.AuthorizationFailure(msg)
if not self.username:
msg = ("Authentication requires 'username', which should be "
"specified in '%s'") % self.__class__.__name__
raise exceptions.AuthorizationFailure(msg)
if not self.password:
msg = ("Authentication requires 'password', which should be "
"specified in '%s'") % self.__class__.__name__
raise exceptions.AuthorizationFailure(msg)
try:
resp = requests.post(
self.baseurl + "/login",
data={'principal': self.username,
'password': self.password},
verify=self.verify_cert)
except requests.exceptions.SSLError:
msg = ("Certificate verify failed, please use '--os-cacert' option"
" to specify a CA bundle file to use in verifying a TLS"
" (https) server certificate or use '--insecure' option"
" to explicitly allow client to perform insecure"
" TLS (https) requests.")
raise exceptions.AuthorizationFailure(msg)
if resp.status_code == 200:
self.session_id = resp.cookies.get('beegosessionID')
logging.debug(
"Successfully login, session id: %s" % self.session_id)
if resp.status_code >= 400:
msg = resp.text or ("The request you have made requires "
"authentication. (HTTP 401)")
reason = '{"reason": "%s", "message": "%s"}' % (resp.reason, msg)
raise exceptions.AuthorizationFailure(reason)
def _construct_http_client(username=None,
password=None,
project=None,
baseurl=None,
timeout=None,
extensions=None,
timings=False,
http_log_debug=False,
user_agent='python-harborclient',
api_version=None,
insecure=False,
cacert=None,
**kwargs):
return HTTPClient(
username,
password,
project,
baseurl,
timeout=timeout,
timings=timings,
http_log_debug=http_log_debug,
insecure=insecure,
cacert=cacert,
api_version=api_version)
def _get_client_class_and_version(version):
if not isinstance(version, api_versions.APIVersion):
version = api_versions.get_api_version(version)
else:
api_versions.check_major_version(version)
if version.is_latest():
raise exceptions.UnsupportedVersion(("The version should be explicit, "
"not latest."))
return version, importutils.import_class(
"harborclient.v%s.client.Client" % version.ver_major)
def get_client_class(version):
"""Returns Client class based on given version."""
_api_version, client_class = _get_client_class_and_version(version)
return client_class
def Client(version,
username=None,
password=None,
project=None,
baseurl=None,
insecure=False,
cacert=None,
*args,
**kwargs):
"""Initialize client object based on given version.
HOW-TO:
The simplest way to create a client instance is initialization with your
credentials::
>>> from harborclient import client
>>> harbor = client.Client(VERSION, USERNAME, PASSWORD,
... PROJECT, HARBOR_URL)
Here ``VERSION`` can be a string or
``harborclient.api_versions.APIVersion`` obj. If you prefer string value,
you can use ``1.1`` (deprecated now), ``2`` or ``2.X``
(where X is a microversion).
Alternatively, you can create a client instance using the keystoneauth
session API. See "The harborclient Python API" page at
python-harborclient's doc.
"""
api_version, client_class = _get_client_class_and_version(version)
kwargs.pop("direct_use", None)
return client_class(
username=username,
password=password,
project=project,
baseurl=baseurl,
api_version=api_version,
insecure=insecure,
cacert=cacert,
*args,
**kwargs)

View File

@ -1,194 +0,0 @@
"""
Exception definitions.
"""
class UnsupportedVersion(Exception):
"""Unsupport API version.
Indicates that the user is trying to use an unsupported version of the API.
"""
pass
class UnsupportedAttribute(AttributeError):
"""Unsupport attribute
Indicates that the user is trying to transmit the argument to a method,
which is not supported by selected version.
"""
def __init__(self, argument_name, start_version, end_version=None):
if end_version:
self.message = (
"'%(name)s' argument is only allowed for microversions "
"%(start)s - %(end)s." % {
"name": argument_name,
"start": start_version,
"end": end_version
})
else:
self.message = (
"'%(name)s' argument is only allowed since microversion "
"%(start)s." % {
"name": argument_name,
"start": start_version
})
class CommandError(Exception):
pass
class AuthorizationFailure(Exception):
pass
class ClientException(Exception):
"""The base exception class for all exceptions this library raises."""
message = 'Unknown Error'
def __init__(self,
code,
message=None,
details=None,
request_id=None,
url=None,
method=None):
self.code = code
self.message = message or self.__class__.message
self.details = details
self.request_id = request_id
self.url = url
self.method = method
def __str__(self):
formatted_string = "%s (HTTP %s)" % (self.message, self.code)
if self.request_id:
formatted_string += " (Request-ID: %s)" % self.request_id
return formatted_string
class RetryAfterException(ClientException):
"""Retry exception
The base exception class for ClientExceptions that use Retry-After header.
"""
def __init__(self, *args, **kwargs):
try:
self.retry_after = int(kwargs.pop('retry_after'))
except (KeyError, ValueError):
self.retry_after = 0
super(RetryAfterException, self).__init__(*args, **kwargs)
class BadRequest(ClientException):
"""HTTP 400 - Bad request: you sent some malformed data."""
http_status = 400
message = "Bad request"
class Unauthorized(ClientException):
"""HTTP 401 - Unauthorized: bad credentials."""
http_status = 401
message = "Unauthorized"
class Forbidden(ClientException):
"""HTTP 403 - Forbidden
HTTP 403 - Forbidden: your credentials don't give you access to this
resource.
"""
http_status = 403
message = "Forbidden"
class NotFound(ClientException):
"""HTTP 404 - Not found"""
http_status = 404
message = "Not found"
class MethodNotAllowed(ClientException):
"""HTTP 405 - Method Not Allowed"""
http_status = 405
message = "Method Not Allowed"
class NotAcceptable(ClientException):
"""HTTP 406 - Not Acceptable"""
http_status = 406
message = "Not Acceptable"
class Conflict(ClientException):
"""HTTP 409 - Conflict"""
http_status = 409
message = "Conflict"
class OverLimit(RetryAfterException):
"""HTTP 413 - Over limit
You're over the API limits for this time period.
"""
http_status = 413
message = "Over limit"
class RateLimit(RetryAfterException):
"""HTTP 429 - Rate limit
you've sent too many requests for this time period.
"""
http_status = 429
message = "Rate limit"
# NotImplemented is a python keyword.
class HTTPNotImplemented(ClientException):
"""HTTP 501 - Not Implemented
the server does not support this operation.
"""
http_status = 501
message = "Not Implemented"
# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__()
# so we can do this:
# _code_map = dict((c.http_status, c)
# for c in ClientException.__subclasses__())
#
# Instead, we have to hardcode it:
_error_classes = [
BadRequest, Unauthorized, Forbidden, NotFound, MethodNotAllowed,
NotAcceptable, Conflict, OverLimit, RateLimit, HTTPNotImplemented
]
_code_map = dict((c.http_status, c) for c in _error_classes)
def from_response(response, body, url, method=None):
"""Extract exception from response
Return an instance of an ClientException or subclass baseda
on a requests response.
Usage::
resp, body = requests.request(...)
if resp.status_code != 200:
raise exception_from_response(resp, rest.text)
"""
cls = _code_map.get(response.status_code, ClientException)
kwargs = {
'code': response.status_code,
'method': method,
'url': url,
'message': body.strip(),
}
return cls(**kwargs)

View File

@ -1,414 +0,0 @@
"""
Command-line interface to the Harbor API.
"""
from __future__ import print_function
import argparse
import getpass
import logging
import os
import sys
from oslo_utils import encodeutils
from oslo_utils import importutils
import harborclient
from harborclient import api_versions
from harborclient import client
from harborclient import exceptions as exc
from harborclient import utils
DEFAULT_API_VERSION = "2.0"
DEFAULT_MAJOR_OS_COMPUTE_API_VERSION = "2.0"
logger = logging.getLogger(__name__)
class HarborClientArgumentParser(argparse.ArgumentParser):
def __init__(self, *args, **kwargs):
super(HarborClientArgumentParser, self).__init__(*args, **kwargs)
def error(self, message):
"""error(message: string)
Prints a usage message incorporating the message to stderr and
exits.
"""
self.print_usage(sys.stderr)
# FIXME(lzyeval): if changes occur in argparse.ArgParser._check_value
choose_from = ' (choose from'
progparts = self.prog.partition(' ')
self.exit(2,
("error: %(errmsg)s\nTry '%(mainp)s help %(subp)s'"
" for more information.\n") % {
'errmsg': message.split(choose_from)[0],
'mainp': progparts[0],
'subp': progparts[2]})
def _get_option_tuples(self, option_string):
"""returns (action, option, value) candidates for an option prefix
Returns [first candidate] if all candidates refers to current and
deprecated forms of the same options parsing succeed.
"""
option_tuples = (super(HarborClientArgumentParser, self)
._get_option_tuples(option_string))
if len(option_tuples) > 1:
normalizeds = [
option.replace('_', '-')
for action, option, value in option_tuples
]
if len(set(normalizeds)) == 1:
return option_tuples[:1]
return option_tuples
class HarborShell(object):
times = []
def _append_global_identity_args(self, parser, argv):
# Register the CLI arguments that have moved to the session object.
parser.set_defaults(os_username=utils.env('HARBOR_USERNAME'))
parser.set_defaults(os_password=utils.env('HARBOR_PASSWORD'))
parser.set_defaults(os_project=utils.env('HARBOR_PROJECT'))
parser.set_defaults(os_baseurl=utils.env('HARBOR_URL'))
def get_base_parser(self, argv):
parser = HarborClientArgumentParser(
prog='harbor',
description=__doc__.strip(),
epilog='See "harbor help COMMAND" '
'for help on a specific command.',
add_help=False,
formatter_class=HarborHelpFormatter, )
# Global arguments
parser.add_argument(
'-h',
'--help',
action='store_true',
help=argparse.SUPPRESS, )
parser.add_argument(
'--debug',
default=False,
action='store_true',
help="Print debugging output.")
parser.add_argument(
'--timings',
default=False,
action='store_true',
help="Print call timing info.")
parser.add_argument(
'--version', action='version', version=harborclient.__version__)
parser.add_argument(
'--os-username',
dest='os_username',
metavar='<username>',
help='Username')
parser.add_argument(
'--os-password',
dest='os_password',
metavar='<password>',
help="User's password")
parser.add_argument(
'--os-project',
dest='os_project',
metavar='<project>',
help="Project Id")
parser.add_argument(
'--timeout',
metavar='<timeout>',
help="Set request timeout (in seconds).")
parser.add_argument(
'--os-baseurl',
metavar='<baseurl>',
help='API base url')
parser.add_argument(
'--insecure',
default=False,
action='store_true',
dest='insecure',
help='Explicitly allow client to perform '
'"insecure" TLS (https) requests. The '
'server\'s certificate will not be verified '
'against any certificate authorities. This '
'option should be used with caution.')
parser.add_argument(
'--os-cacert',
dest='os_cacert',
metavar='<ca-certificate>',
default=os.environ.get('OS_CACERT'),
help='Specify a CA bundle file to use in '
'verifying a TLS (https) server certificate. '
'Defaults to env[OS_CACERT].')
parser.add_argument(
'--os-api-version',
metavar='<api-version>',
default=utils.env(
'HARBOR_API_VERSION', default=DEFAULT_API_VERSION),
help=('Accepts X, X.Y (where X is major and Y is minor part) or '
'"X.latest", defaults to env[HARBOR_API_VERSION].'))
self._append_global_identity_args(parser, argv)
return parser
def get_subcommand_parser(self, version, do_help=False, argv=None):
parser = self.get_base_parser(argv)
self.subcommands = {}
subparsers = parser.add_subparsers(metavar='<subcommand>')
actions_module = importutils.import_module(
"harborclient.v%s.shell" % version.ver_major)
self._find_actions(subparsers, actions_module, version, do_help)
self._find_actions(subparsers, self, version, do_help)
self._add_bash_completion_subparser(subparsers)
return parser
def _add_bash_completion_subparser(self, subparsers):
subparser = subparsers.add_parser(
'bash_completion',
add_help=False,
formatter_class=HarborHelpFormatter)
self.subcommands['bash_completion'] = subparser
subparser.set_defaults(func=self.do_bash_completion)
def _find_actions(self, subparsers, actions_module, version, do_help):
msg = " (Supported by API versions '%(start)s' - '%(end)s')"
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
# I prefer to be hyphen-separated instead of underscores.
command = attr[3:].replace('_', '-')
callback = getattr(actions_module, attr)
desc = callback.__doc__ or ''
if hasattr(callback, "versioned"):
additional_msg = ""
subs = api_versions.get_substitutions(
utils.get_function_name(callback))
if do_help:
additional_msg = msg % {
'start': subs[0].start_version.get_string(),
'end': subs[-1].end_version.get_string()
}
subs = [
versioned_method for versioned_method in subs
if version.matches(versioned_method.start_version,
versioned_method.end_version)
]
if subs:
# use the "latest" substitution
callback = subs[-1].func
else:
# there is no proper versioned method
continue
desc = callback.__doc__ or desc
desc += additional_msg
action_help = desc.strip()
arguments = getattr(callback, 'arguments', [])
subparser = subparsers.add_parser(
command,
help=action_help,
description=desc,
add_help=False,
formatter_class=HarborHelpFormatter)
subparser.add_argument(
'-h',
'--help',
action='help',
help=argparse.SUPPRESS, )
self.subcommands[command] = subparser
for (args, kwargs) in arguments:
start_version = kwargs.get("start_version", None)
if start_version:
start_version = api_versions.APIVersion(start_version)
end_version = kwargs.get("end_version", None)
if end_version:
end_version = api_versions.APIVersion(end_version)
else:
end_version = api_versions.APIVersion(
"%s.latest" % start_version.ver_major)
if do_help:
kwargs["help"] = kwargs.get("help", "") + (
msg % {
"start": start_version.get_string(),
"end": end_version.get_string()
})
if not version.matches(start_version, end_version):
continue
kw = kwargs.copy()
kw.pop("start_version", None)
kw.pop("end_version", None)
subparser.add_argument(*args, **kw)
subparser.set_defaults(func=callback)
def setup_debugging(self, debug):
if not debug:
return
streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
logging.basicConfig(level=logging.DEBUG, format=streamformat)
logging.getLogger('iso8601').setLevel(logging.WARNING)
def main(self, argv):
# Parse args once to find version and debug settings
parser = self.get_base_parser(argv)
(args, args_list) = parser.parse_known_args(argv)
self.setup_debugging(args.debug)
do_help = ('help' in argv) or ('--help' in argv) or (
'-h' in argv) or not argv
# bash-completion should not require authentication
if not args.os_api_version:
api_version = api_versions.get_api_version(
DEFAULT_MAJOR_OS_COMPUTE_API_VERSION)
else:
api_version = api_versions.get_api_version(args.os_api_version)
os_username = args.os_username
os_password = args.os_password
os_project = args.os_project
os_baseurl = args.os_baseurl
subcommand_parser = self.get_subcommand_parser(
api_version, do_help=do_help, argv=argv)
self.parser = subcommand_parser
if args.help or not argv:
subcommand_parser.print_help()
return 0
args = subcommand_parser.parse_args(argv)
# Short-circuit and deal with help right away.
if args.func == self.do_help:
self.do_help(args)
return 0
elif args.func == self.do_bash_completion:
self.do_bash_completion(args)
return 0
insecure = args.insecure
cacert = args.os_cacert
if not os_baseurl:
print(("ERROR (CommandError): You must provide harbor url via "
"either --os-baseurl or env[HARBOR_URL]."))
return 1
if not os_username:
print(("ERROR (CommandError): You must provide username via "
"either --os-username or env[HARBOR_USERNAME]."))
return 1
if not os_project:
print(("ERROR (CommandError): You must provide project via "
"either --os-project or env[HARBOR_PROJECT]."))
return 1
while not os_password:
os_password = getpass.getpass("password: ")
self.cs = client.Client(
api_version,
os_username,
os_password,
os_project,
os_baseurl,
timings=args.timings,
http_log_debug=args.debug,
insecure=insecure,
cacert=cacert,
timeout=args.timeout)
try:
self.cs.authenticate()
except exc.Unauthorized:
raise exc.CommandError("Invalid Harbor credentials.")
except exc.AuthorizationFailure as e:
raise exc.CommandError("Unable to authorize user '%s': %s"
% (os_username, e))
args.func(self.cs, args)
if args.timings:
self._dump_timings(self.times + self.cs.get_timings())
def _dump_timings(self, timings):
results = [{
"url": url,
"seconds": end - start
} for url, start, end in timings]
total = 0.0
for tyme in results:
total += tyme['seconds']
results.append({"url": "Total", "seconds": total})
utils.print_list(results, ["url", "seconds"], align='l')
print("Total: %s seconds" % total)
def do_bash_completion(self, _args):
"""Print bash completion
Prints all of the commands and options to stdout so that the
harbor.bash_completion script doesn't have to hard code them.
"""
commands = list()
options = list()
for sc_str, sc in self.subcommands.items():
commands.append(sc_str)
for option in sc._optionals._option_string_actions.keys():
options.append(option)
options.extend(self.parser._option_string_actions.keys())
print(' '.join(set(commands + options)))
@utils.arg(
'command',
metavar='<subcommand>',
nargs='?',
help='Display help for <subcommand>.')
def do_help(self, args):
"""Display help about this program or one of its subcommands."""
if args.command:
if args.command in self.subcommands:
self.subcommands[args.command].print_help()
else:
raise exc.CommandError(
("'%s' is not a valid subcommand") % args.command)
else:
self.parser.print_help()
# I'm picky about my shell help.
class HarborHelpFormatter(argparse.HelpFormatter):
def __init__(self,
prog,
indent_increment=2,
max_help_position=32,
width=None):
super(HarborHelpFormatter, self).__init__(prog, indent_increment,
max_help_position, width)
def start_section(self, heading):
# Title-case the headings
heading = '%s%s' % (heading[0].upper(), heading[1:])
super(HarborHelpFormatter, self).start_section(heading)
def main():
try:
argv = [encodeutils.safe_decode(a) for a in sys.argv[1:]]
HarborShell().main(argv)
except KeyboardInterrupt:
print("... terminating harbor client", file=sys.stderr)
sys.exit(130)
except exc.CommandError as e:
print("CommandError: %s" % e)
sys.exit(127)
if __name__ == "__main__":
main()

View File

@ -1,165 +0,0 @@
import contextlib
import os
import textwrap
import time
from oslo_serialization import jsonutils
from oslo_utils import encodeutils
import prettytable
import six
def env(*args, **kwargs):
"""Returns the first environment variable set.
If all are empty, defaults to '' or keyword arg `default`.
"""
for arg in args:
value = os.environ.get(arg)
if value:
return value
return kwargs.get('default', '')
def arg(*args, **kwargs):
"""Decorator for CLI args.
Example:
>>> @arg("name", help="Name of the new entity")
... def entity_create(args):
... pass
"""
def _decorator(func):
add_arg(func, *args, **kwargs)
return func
return _decorator
def add_arg(func, *args, **kwargs):
"""Bind CLI arguments to a shell.py `do_foo` function."""
if not hasattr(func, 'arguments'):
func.arguments = []
# NOTE(sirp): avoid dups that can occur when the module is shared across
# tests.
if (args, kwargs) not in func.arguments:
# Because of the semantics of decorator composition if we just append
# to the options list positional options will appear to be backwards.
func.arguments.insert(0, (args, kwargs))
def pretty_choice_list(l):
return ', '.join("'%s'" % i for i in l)
def pretty_choice_dict(d):
"""Returns a formatted dict as 'key=value'."""
return pretty_choice_list(['%s=%s' % (k, d[k]) for k in sorted(d.keys())])
def print_list(objs, fields, formatters={}, sortby=None, align='c'):
pt = prettytable.PrettyTable([f for f in fields], caching=False)
pt.align = align
for o in objs:
row = []
for field in fields:
if field in formatters:
if callable(formatters[field]):
row.append(formatters[field](o))
else:
row.append(o.get(formatters[field], None))
else:
data = o.get(field, None)
if data is None or data == "":
data = '-'
data = six.text_type(data).replace("\r", "")
row.append(data)
pt.add_row(row)
if sortby is not None and sortby in fields:
result = encodeutils.safe_encode(pt.get_string(sortby=sortby))
else:
result = encodeutils.safe_encode(pt.get_string())
if six.PY3:
result = result.decode()
print(result)
def print_dict(d, dict_property="Property", dict_value="Value", wrap=0):
pt = prettytable.PrettyTable([dict_property, dict_value], caching=False)
pt.align = 'l'
for k, v in sorted(d.items()):
# convert dict to str to check length
if isinstance(v, (dict, list)):
v = jsonutils.dumps(v)
if wrap > 0:
v = textwrap.fill(six.text_type(v), wrap)
# if value has a newline, add in multiple rows
# e.g. fault with stacktrace
if v and isinstance(v, six.string_types) and (r'\n' in v or '\r' in v):
# '\r' would break the table, so remove it.
if '\r' in v:
v = v.replace('\r', '')
lines = v.strip().split(r'\n')
col1 = k
for line in lines:
pt.add_row([col1, line])
col1 = ''
else:
if v is None:
v = '-'
pt.add_row([k, v])
result = encodeutils.safe_encode(pt.get_string())
if six.PY3:
result = result.decode()
print(result)
def safe_issubclass(*args):
"""Like issubclass, but will just return False if not a class."""
try:
if issubclass(*args):
return True
except TypeError:
pass
return False
@contextlib.contextmanager
def record_time(times, enabled, *args):
"""Record the time of a specific action.
:param times: A list of tuples holds time data.
:type times: list
:param enabled: Whether timing is enabled.
:type enabled: bool
:param *args: Other data to be stored besides time data, these args
will be joined to a string.
"""
if not enabled:
yield
else:
start = time.time()
yield
end = time.time()
times.append((' '.join(args), start, end))
def get_function_name(func):
if six.PY2:
if hasattr(func, "im_class"):
return "%s.%s" % (func.im_class, func.__name__)
else:
return "%s.%s" % (func.__module__, func.__name__)
else:
return "%s.%s" % (func.__module__, func.__qualname__)

View File

@ -1 +0,0 @@
from harborclient.v2.client import Client # noqa

View File

@ -1,73 +0,0 @@
from harborclient import client
from harborclient.v2 import configurations
from harborclient.v2 import jobs
from harborclient.v2 import logs
from harborclient.v2 import projects
from harborclient.v2 import repositories
from harborclient.v2 import searcher
from harborclient.v2 import statistics
from harborclient.v2 import systeminfo
from harborclient.v2 import targets
from harborclient.v2 import users
class Client(object):
"""Top-level object to access the Harbor API.
.. warning:: All scripts and projects should not initialize this class
directly. It should be done via `harborclient.client.Client` interface.
"""
def __init__(self,
username=None,
password=None,
project=None,
baseurl=None,
insecure=False,
cacert=None,
api_version=None,
*argv,
**kwargs):
"""Initialization of Client object.
:param str username: Username
:param str password: Password
:param str project: Project
"""
self.baseurl = baseurl
self.users = users.UserManager(self)
self.projects = projects.ProjectManager(self)
self.jobs = jobs.JobManager(self)
self.repositories = repositories.RepositoryManager(self)
self.searcher = searcher.SearchManager(self)
self.statistics = statistics.StatisticsManager(self)
self.logs = logs.LogManager(self)
self.targets = targets.TargetManager(self)
self.systeminfo = systeminfo.SystemInfoManager(self)
self.configurations = configurations.ConfigurationManager(self)
self.client = client._construct_http_client(
username=username,
password=password,
project=project,
baseurl=baseurl,
insecure=insecure,
cacert=cacert,
api_version=api_version,
**kwargs)
def get_timings(self):
return self.client.get_timings()
def reset_timings(self):
self.client.reset_timings()
def authenticate(self):
"""Authenticate against the server.
Normally this is called automatically when you first access the API,
but you can call this method to force authentication right now.
Returns on success; raises :exc:`exceptions.Unauthorized` if the
credentials are wrong.
"""
self.client.authenticate()

View File

@ -1,7 +0,0 @@
from harborclient import base
class ConfigurationManager(base.Manager):
def get(self):
"""Get system configurations."""
return self._get("/configurations")

View File

@ -1,11 +0,0 @@
from harborclient import base
class JobManager(base.Manager):
def list(self, policy_id=None):
"""List filters jobs according to the policy and repository."""
return self._list("/jobs/replication?policy_id=%s" % policy_id)
def get_log(self, job_id):
"""Get job logs."""
return self._get("/jobs/replication/%s/log" % job_id)

View File

@ -1,7 +0,0 @@
from harborclient import base
class LogManager(base.Manager):
def list(self):
"""Get recent logs of the projects which the user is a member of."""
return self._list("/logs")

View File

@ -1,44 +0,0 @@
from harborclient import base
from harborclient import exceptions as exp
class ProjectManager(base.Manager):
def is_id(self, key):
return key.isdigit()
def get(self, id):
"""Return specific project detail infomation."""
return self._get("/projects/%s" % id)
def list(self):
"""List projects."""
return self._list("/projects")
def get_id_by_name(self, name):
"""Return specific project detail infomation by name."""
projects = self.list()
for p in projects:
if p['name'] == name:
return p['project_id']
raise exp.NotFound("Project '%s' not Found." % name)
def get_name_by_id(self, id):
"""Return specific project detail infomation by id."""
projects = self.list()
for p in projects:
if p['project_id'] == id:
return p['name']
raise exp.NotFound("Project '%s' not Found." % id)
def create(self, name, public=True):
"""Create a new project."""
project = {"project_name": name, "public": 1 if public else 0}
return self._create("/projects", project)
def delete(self, id):
"""Delete project by id."""
return self._delete("/projects/%s" % id)
def get_members(self, id):
"""Return a project's relevant role members."""
return self._list("/projects/%s/members/" % id)

View File

@ -1,27 +0,0 @@
from harborclient import base
class RepositoryManager(base.Manager):
def get(self, id):
"""Get a Repository."""
return self._get("/repositories/%s" % id)
def list(self, project):
"""Get repositories accompany with relevant project and repo name."""
repositories = self._list("/repositories?project_id=%s" % project)
return repositories
def list_tags(self, repo_name):
"""Get the tag of the repository."""
return self.api.client.get(
"/repositories/%s/tags" % repo_name)
def get_manifests(self, repo_name, tag):
"""Get manifests of a relevant repository."""
return self.api.client.get(
"/repositories/%(repo_name)s/tags/%(tag)s/manifest"
% {"repo_name": repo_name, "tag": tag})
def get_top(self, count):
"""Get public repositories which are accessed most."""
return self._list("/repositories/top?count=%s" % count)

View File

@ -1,7 +0,0 @@
from harborclient import base
class SearchManager(base.Manager):
def search(self, query):
"""Search for projects and repositories."""
return self.api.client.get("/search?q=%s" % query)

View File

@ -1,607 +0,0 @@
from __future__ import print_function
import getpass
import logging
from oslo_utils import strutils
from harborclient import exceptions as exp
from harborclient import utils
logger = logging.getLogger(__name__)
def is_id(obj):
try:
int(obj)
return True
except ValueError:
return False
@utils.arg(
'--sortby',
metavar='<sortby>',
dest="sortby",
default="user_id",
help='Sort key.')
def do_user_list(cs, args):
"""Get registered users of Harbor."""
try:
users = cs.users.list()
except exp.Forbidden as e:
raise exp.CommandError(e.message)
# Get admin user
try:
admin = cs.users.get(1)
users.append(admin)
except Exception:
pass
fields = ['user_id', 'username', 'is_admin',
'email', 'realname', 'comment']
formatters = {"is_admin": 'has_admin_role'}
utils.print_list(users, fields, formatters=formatters, sortby=args.sortby)
@utils.arg(
'user',
metavar='<user>',
help='User name or id')
def do_set_admin(cs, args):
"""Update a registered user to change to be an administrator of Harbor."""
try:
user = cs.users.find(args.user)
except exp.NotFound:
print("User '%s' not found." % args.user)
cs.users.set_admin(user['user_id'], True)
print("Set user '%s' as administrator successfully." % args.user)
@utils.arg(
'user',
metavar='<user>',
help='User name or id')
def do_revoke_admin(cs, args):
"""Update a registered user to be a non-admin of Harbor."""
try:
user = cs.users.find(args.user)
except exp.NotFound:
print("User '%s' not found." % args.user)
cs.users.set_admin(user['user_id'], False)
print("Revoke admin privilege from user '%s' successfully." % args.user)
@utils.arg(
'user',
metavar='<user>',
help='User name or id')
@utils.arg(
'--email',
metavar='<email>',
dest='email',
help='Email of the user')
@utils.arg(
'--realname',
metavar='<realname>',
dest='realname',
help='Email of the user')
@utils.arg(
'--comment',
metavar='<comment>',
dest='comment',
help='Comment of the user')
def do_user_update(cs, args):
"""Update a registered user to change his profile."""
try:
user = cs.users.find(args.user)
except exp.NotFound:
print("User '%s' not found." % args.user)
realname = args.realname or user['realname']
email = args.email or user['email']
comment = args.comment or user['comment']
cs.users.update(user['user_id'], realname, email, comment)
user = cs.users.get(user['user_id'])
utils.print_dict(user)
@utils.arg(
'user',
metavar='<user>',
help='User name or id')
def do_change_password(cs, args):
"""Change the password on a user that already exists."""
try:
user = cs.users.find(args.user)
except exp.NotFound:
print("User '%s' not found." % args.user)
old_password = getpass.getpass('Old password: ')
new_password = getpass.getpass('New Password: ')
try:
cs.users.change_password(user['user_id'], old_password, new_password)
print("Update password successfully.")
except exp.Forbidden as e:
print(e.message.replace("_", ' '))
return 1
@utils.arg('user', metavar='<user>', help='ID or name of user.')
def do_user_show(cs, args):
"""Get a user's profile."""
key = args.user
if cs.users.is_id(key):
id = key
else:
id = cs.users.get_id_by_name(key)
user = cs.users.get(id)
utils.print_dict(user)
@utils.arg(
'--detail',
'-d',
dest="detail",
action="store_true",
help='show detail info.')
def do_whoami(cs, args):
"""Get current user info."""
user = cs.users.current()
if args.detail:
utils.print_dict(user)
else:
print(user['username'])
@utils.arg(
'--username',
metavar='<username>',
dest='username',
required=True,
help='Unique name of the new user')
@utils.arg(
'--password',
metavar='<password>',
dest='password',
required=True,
help='Password of the new user')
@utils.arg(
'--email',
metavar='<email>',
dest='email',
required=True,
help='Email of the new user')
@utils.arg(
'--realname',
metavar='<realname>',
dest='realname',
default=None,
help='Email of the new user')
@utils.arg(
'--comment',
metavar='<comment>',
dest='comment',
default=None,
help='Comment of the new user')
def do_user_create(cs, args):
"""Creates a new user account."""
cs.users.create(args.username, args.password,
args.email, args.realname,
args.comment)
print("Create user '%s' successfully." % args.username)
@utils.arg('user', metavar='<user>', help='ID or name of user.')
def do_user_delete(cs, args):
"""Mark a registered user as be removed."""
key = args.user
if cs.users.is_id(key):
id = key
else:
id = cs.users.get_id_by_name(key)
cs.users.delete(id)
print("Delete user '%s' sucessfully." % key)
@utils.arg(
'--sortby',
metavar='<sortby>',
dest="sortby",
default="project_id",
help='Sort key.')
def do_project_list(cs, args):
"""List projects."""
projects = cs.projects.list()
fields = [
'project_id',
'name',
'owner_id',
'current_user_role_id',
'repo_count',
'creation_time',
'public',
]
utils.print_list(projects, fields, formatters={}, sortby=args.sortby)
@utils.arg(
'--project-id',
'-p',
dest='project_id',
metavar='<project_id>',
default=None,
help='ID of project.')
def do_member_list(cs, args):
"""List a project's relevant role members."""
project = args.project_id
if not project:
project = cs.client.project
members = cs.projects.get_members(project)
fields = [
'username',
'role_name',
'user_id',
'role_id',
]
utils.print_list(members, fields, formatters={}, sortby='user_id')
@utils.arg('project', metavar='<project>', help='ID or name of project.')
def do_project_show(cs, args):
"""Show specific project detail infomation."""
key = args.project
if cs.projects.is_id(key):
project_id = key
else:
project_id = cs.projects.get_id_by_name(key)
projects = cs.projects.list()
for project in projects:
if str(project['project_id']) == str(project_id):
utils.print_dict(project)
return
raise exp.NotFound("Project '%s' not found" % args.project)
@utils.arg('project', metavar='<project>', help='ID or name of project.')
def do_project_delete(cs, args):
"""Delete project by Id or name."""
key = args.project
if cs.projects.is_id(key):
id = key
else:
try:
id = cs.projects.get_id_by_name(key)
except exp.NotFound:
print("Project '%s' not found." % args.project)
return 1
try:
cs.projects.delete(id)
print("Delete Project '%s' successfully." % key)
return 0
except exp.NotFound:
print("Project '%s' not Found." % args.project)
return 1
@utils.arg(
'name',
metavar='<name>',
help='Name of new project.')
@utils.arg(
'--is-public',
metavar='<is-public>',
default=True,
help='Make project accessible to the public (default true).')
def do_project_create(cs, args):
"""Create a new project."""
is_public = strutils.bool_from_string(args.is_public, strict=True)
try:
cs.projects.create(args.name, is_public)
print("Create project '%s' successfully." % args.name)
except exp.Conflict:
print("Project name '%s' already exists." % args.name)
@utils.arg(
'--project-id',
'-p',
dest='project_id',
metavar='<project_id>',
default=None,
help='ID of project.')
@utils.arg(
'--sortby',
dest='sortby',
metavar='<sortby>',
default='Id',
help='Sort key.')
def do_list(cs, args):
"""Get repositories accompany with relevant project and repo name."""
project_id = args.project_id
if not project_id:
project_id = cs.client.project
repositories = cs.repositories.list(project_id)
data = []
for repo in repositories:
tags = cs.repositories.list_tags(repo['name'])
for tag in tags:
item = repo.copy()
manifest = cs.repositories.get_manifests(item['name'],
tag['name'])
size = 0
for layer in manifest['manifest']['layers']:
size += layer['size']
item['size'] = size
if tag['name'] != 'latest':
item['name'] = repo['name'] + ":" + tag['name']
data.append(item)
fields = [
"name", 'project_id', 'size',
"tags_count", "star_count", "pull_count",
"update_time"
]
utils.print_list(data, fields, sortby=args.sortby)
@utils.arg('repository', metavar='<repository>', help='Name of repository.')
def do_list_tags(cs, args):
"""Get tags of a relevant repository."""
tags = cs.repositories.list_tags(args.repository)
fields = ["name", 'author', 'architecture',
'os', 'docker_version', 'created']
utils.print_list(tags, fields, sortby="name")
@utils.arg(
'--project-id',
'-p',
dest='project_id',
metavar='<project_id>',
default=None,
help='ID of project.')
@utils.arg(
'repository',
metavar='<repository>',
help="Repository name, for example: int32bit/ubuntu:14.04.")
def do_show(cs, args):
"""Show specific repository detail infomation."""
project = args.project_id
if not project:
project = cs.client.project
repo = args.repository
tag_index = repo.find(':')
if tag_index != -1:
tag = repo[tag_index + 1:]
repo = repo[:tag_index]
else:
tag = "latest"
if repo.find('/') == -1:
repo = "library/" + repo
repos = cs.repositories.list(project)
found_repo = None
for r in repos:
if r['name'] == repo:
found_repo = r
break
if not found_repo:
print("Image '%s' not found." % repo)
return
tags = cs.repositories.list_tags(found_repo['name'])
found_tag = None
for t in tags:
if t['name'] == tag:
found_tag = t
break
if not found_tag:
print("Image '%s' with tag '%s' not found." % (repo, tag))
return
for key in found_tag:
found_repo['tag_' + key] = found_tag[key]
utils.print_dict(found_repo)
@utils.arg(
'--count',
'-c',
metavar='<count>',
dest='count',
default=5,
help='Count.')
def do_top(cs, args):
"""Get public repositories which are accessed most."""
try:
count = int(args.count)
except ValueError:
print("'%s' is not a valid number." % args.count)
return 1
if count < 1:
print("invalid count %s, count must > 0." % args.count)
return 1
data = cs.repositories.get_top(count)
utils.print_list(data,
['name', 'pull_count', 'star_count'],
sortby='pull_count')
@utils.arg(
'query',
metavar='<query>',
help='Search parameter for project and repository name.')
def do_search(cs, args):
"""Search for projects and repositories."""
data = cs.searcher.search(args.query)
project_fields = ['project_id', 'name', 'public',
'repo_count', 'creation_time']
print("Find %d Projects: " % len(data['project']))
utils.print_list(
data['project'], project_fields, formatters={}, sortby='id')
repository_fields = [
'repository_name', 'project_name', 'project_id', 'project_public'
]
print("\n")
print("Find %d Repositories: " % len(data['repository']))
utils.print_list(
data['repository'],
repository_fields,
formatters={},
sortby='repository_name')
def do_usage(cs, args):
"""Get projects number and repositories number relevant to the user."""
data = cs.statistics.list()
utils.print_dict(data)
@utils.arg(
'--sortby',
dest='sortby',
metavar='<sortby>',
default='op_time',
help='Sort key.')
def do_logs(cs, args):
"""Get recent logs of the projects which the user is a member of."""
logs = cs.logs.list() or []
for log in logs:
repo = log['repo_name']
tag = None
if log['repo_tag'] != 'N/A':
tag = log['repo_tag']
if tag:
repo += ":%s" % tag
log['repository'] = repo
fields = ['log_id', 'op_time', 'username',
'project_id', 'operation', 'repository']
utils.print_list(logs, fields, sortby=args.sortby)
def do_info(cs, args):
"""Get general system info."""
info = cs.systeminfo.get()
try:
volumes = cs.systeminfo.get_volumes()
info['disk_total'] = volumes['storage']['total']
info['disk_free'] = volumes['storage']['free']
except exp.Forbidden:
# Only admin can get volumes
pass
utils.print_dict(info)
def do_get_cert(cs, args):
"""Get default root cert under OVA deployment."""
try:
certs = cs.systeminfo.get_cert()
print(certs)
except exp.NotFound:
print("No certificate found")
except exp.Forbidden:
print("Only admin can perform this operation.")
def do_version(cs, args):
"""Get harbor version."""
info = cs.systeminfo.get()
print(info['harbor_version'])
def do_get_conf(cs, args):
"""Get system configurations."""
try:
configurations = cs.configurations.get()
except exp.Forbidden:
raise exp.CommandError("Only admin can perform this operation.")
data = []
for key in configurations:
item = {}
item['name'] = key
item['value'] = configurations[key]['value']
item['editable'] = configurations[key]['editable']
data.append(item)
utils.print_list(data, ['name', 'value', 'editable'], sortby='name')
def do_target_list(cs, args):
"""List filters targets."""
targets = cs.targets.list()
fields = ['id', 'name', 'endpoint',
'username', 'password', 'creation_time']
utils.print_list(targets, fields)
@utils.arg(
'target',
metavar='<target>',
help="The target name or id.")
def do_target_ping(cs, args):
"""Ping validates target."""
target = None
if is_id(args.target):
target = args.target
else:
targets = cs.targets.list()
for t in targets:
if t['name'] == args.target:
target = t['id']
break
if not target:
print("target '%s' not found!" % args.target)
return 1
try:
cs.targets.ping(target)
print("OK")
except Exception as e:
print("Can not ping target: %s" % e)
@utils.arg(
'target',
metavar='<target>',
help="The target name or id.")
def do_policy_list(cs, args):
"""List filters policies by name and project_id."""
target = None
if is_id(args.target):
target = args.target
else:
targets = cs.targets.list()
for t in targets:
if t['name'] == args.target:
target = t['id']
break
if not target:
print("target '%s' not found!" % args.target)
return 1
try:
policies = cs.targets.list_policies(target)
except exp.NotFound:
print("target '%s' not found!" % args.target)
return 1
if not policies:
policies = []
fields = ["id", "name", "description",
"enabled", "start_time", "cron_str",
"creation_time"]
utils.print_list(policies, fields, sortby='id')
@utils.arg(
'policy_id',
metavar='<policy_id>',
help="The policy id.")
def do_job_list(cs, args):
"""List filters jobs according to the policy and repository."""
jobs = cs.jobs.list(args.policy_id)
for job in jobs:
if job['tags']:
job['name'] += ":" + job['tags']
fields = ['id', 'repository', 'operation', 'status', 'update_time']
utils.print_list(jobs, fields, sortby='id')
@utils.arg(
'job_id',
metavar='<job_id>',
help="The job id.")
def do_job_log(cs, args):
"""Get job logs."""
log = cs.jobs.get_log(args.job_id)
print(log)

View File

@ -1,7 +0,0 @@
from harborclient import base
class StatisticsManager(base.Manager):
def list(self):
"""Get projects number and repositories number relevant to the user."""
return self._list("/statistics")

View File

@ -1,15 +0,0 @@
from harborclient import base
class SystemInfoManager(base.Manager):
def get(self):
"""Get general system info."""
return self._get("/systeminfo")
def get_volumes(self):
"""Get system volume info (total/free size)."""
return self._get("/systeminfo/volumes")
def get_cert(self):
"""Get default root certificate under OVA deployment."""
return self._get("/systeminfo/getcert")

View File

@ -1,18 +0,0 @@
from harborclient import base
class TargetManager(base.Manager):
def list(self, name=None):
"""List filters targets by name."""
if name:
return self._list("/targets?name=%s" % name)
return self._list("/targets")
def ping(self, id):
"""Ping validates target."""
return self._create("/targets/%s/ping" % id)
def list_policies(self, id):
"""List the target relevant policies."""
return self._list("/targets/%s/policies" % id)

View File

@ -1,72 +0,0 @@
from harborclient import base
from harborclient import exceptions as exp
class UserManager(base.Manager):
def is_id(self, key):
return key.isdigit()
def get(self, id):
"""Get a user's profile."""
return self._get("/users/%s" % id)
def current(self):
"""Get current user info."""
return self._get("/users/current")
def list(self):
"""Get registered users of Harbor."""
return self._list("/users")
def get_id_by_name(self, name):
users = self.list()
for u in users:
if u['username'] == name:
return u['user_id']
raise exp.NotFound("User '%s' Not Found!" % name)
def find(self, key):
if self.is_id(key):
return self.get(key)
else:
users = self.list()
for user in users:
if user['username'] == key:
return user
raise exp.NotFound("User '%s' Not Found!" % key)
def create(self, username, password, email, realname=None, comment=None):
"""Creates a new user account."""
data = {
"username": username,
"password": password,
"email": email,
"realname": realname or username,
"comment": comment or "",
}
return self._create("/users", data)
def update(self, id, realname, email, comment):
"""Update a registered user to change his profile."""
profile = {"realname": realname,
"email": email,
"comment": comment}
return self._update("/users/%s" % id, profile)
def delete(self, id):
"""Mark a registered user as be removed."""
return self._delete("/users/%s" % id)
def change_password(self, id, old_password, new_password):
"""Change the password on a user that already exists."""
profile = {"old_password": old_password,
"new_password": new_password}
return self._update("/users/%s/password" % id, profile)
def set_admin(self, id, is_admin):
"""Update a registered user to change to be an admin of Harbor."""
if is_admin:
profile = {"has_admin_role": 1}
else:
profile = {"has_admin_role": 0}
return self._update("/users/%s/sysadmin" % id, profile)

View File

@ -1,5 +0,0 @@
export HARBOR_USERNAME=admin
export HARBOR_PASSWORD=Harbor12345
export HARBOR_URL=https://localhost
export HARBOR_PROJECT=2
complete -W "$(harbor bash-completion)" harbor

View File

@ -1,10 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr>=1.6 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0
oslo.utils>=3.11.0 # Apache-2.0
PrettyTable<0.8,>=0.7 # BSD
requests>=2.10.0 # Apache-2.0
simplejson>=2.2.0 # MIT
six>=1.9.0 # MIT

View File

@ -1 +0,0 @@
complete -W $(harbor bash-completion) harbor

View File

@ -1,35 +0,0 @@
[metadata]
name = python-harborclient
summary = A CLI tool for the Docker Registry Harbor
description = A CLI tool for the Docker Registry Harbor
license = Apache License, Version 2.0
author = int32bit
author-email = krystism@gmail.com
maintainer = int32bit
maintainer-email = krystism@gmail.com
home-page = https://github.com/int32bit/python-harborclient
url = 'https://github.com/int32bit/python-harborclient'
version = 1.2.2
keywords = 'docker registry distribution harbor python sdk'
install_requires = ['requests>2.2.0', 'oslo.serialization>=1.10.0', 'oslo.utils>=3.11.0', 'PrettyTable', 'simplejson', 'six'],
classifier =
Development Status :: 3 - Alpha
Intended Audience :: Developers
License :: OSI Approved :: Apache Software License
Operating System :: OS Independent
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
[files]
packages =
harborclient
[entry_points]
console_scripts =
harbor = harborclient.shell:main
[wheel]
universal = 1
[pbr]
warnerrors = true

View File

@ -1,8 +0,0 @@
import setuptools
try:
import multiprocessing # noqa
except ImportError:
pass
setuptools.setup(setup_requires=['pbr>=1.8'], pbr=True)

View File

@ -1,3 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.

View File

@ -1,16 +0,0 @@
#!/usr/bin/env bash
set -o pipefail
TESTRARGS=$1
# --until-failure is not compatible with --subunit see:
#
# https://bugs.launchpad.net/testrepository/+bug/1411804
#
# this work around exists until that is addressed
if [[ "$TESTARGS" =~ "until-failure" ]]; then
python setup.py testr --slowest --testr-args="$TESTRARGS"
else
python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | subunit-trace -f
fi

View File

@ -1,23 +0,0 @@
[tox]
envlist = pep8
minversion = 1.6
skipsdist = True
[testenv]
usedevelop = True
# tox is silly... these need to be separated by a newline....
whitelist_externals = find
bash
install_command = pip install -U {opts} {packages}
setenv = VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands =
find . -type f -name "*.pyc" -delete
bash tools/pretty_tox.sh '{posargs}'
# there is also secret magic in pretty_tox.sh which lets you run in a fail only
# mode. To do this define the TRACE_FAILONLY environmental variable.
[testenv:pep8]
commands = flake8 {posargs}

View File

@ -1,23 +0,0 @@
## Make use of pre-built images of Harbor
Community members have helped building Harbor's docker images. If you want to save time from building Harbor from source, please follow the below instructions to quickly pull Harbor's pre-built images for installation.
### Steps
Run the command `update_compose.sh` :
```
$ ./update_compose.sh
Please enter the registry service you want to pull the pre-built images from.
Enter 1 for Docker Hub.
Enter 2 for Daocloud.io (recommended for Chinese users).
or enter other registry URL such as https://my_registry/harbor/ .
The default is 1 (Docker Hub):
```
Enter **1** to pull images from Docker Hub,
Enter **2** to pull image from Daocloud.io, recommended for Chinese users.
or Enter other registry URL like `https://my_registry/harbor/` . Do not forget the "/" and the end.
This command backs up and updates the file `Deploy/docker-compose.yml` . Next, just follow the [Harbor Installation Guide](../../docs/installation_guide.md) to install Harbor.

View File

@ -1,41 +0,0 @@
#/bin/bash
echo " "
echo "Please enter the registry service you want to pull the pre-built images from."
echo "Enter 1 for Docker Hub."
echo "Enter 2 for Daocloud.io (recommended for Chinese users)."
echo "or enter other registry URL such as https://my_registry/harbor/ ."
read -p "The default is 1 (Docker Hub): " choice
cd ../../Deploy
template_file="docker-compose.yml.template"
yml_file='docker-compose.yml'
if test -e $template_file
then
cp $template_file $yml_file
else
cp $yml_file $template_file
fi
platform=''
choice=${choice:-1}
if [ $choice == '1' ]
then
platform='prjharbor/'
elif [ $choice == '2' ]
then
platform='daocloud.io/harbor/'
else
platform=$choice
fi
version='0.3.0'
log='deploy_log:'
db='deploy_mysql:'
job_service='deploy_jobservice:'
ui='deploy_ui:'
sed -i -- '/build: .\/log\//c\ image: '$platform$log$version'' $yml_file
sed -i -- '/build: .\/db\//c\ image: '$platform$db$version'' $yml_file
sed -i -- '/ui:/{n;N;N;d}' $yml_file && sed -i -- '/ui:/a\\ image: '$platform$ui$version'' $yml_file
sed -i -- '/jobservice:/{n;N;N;d}' $yml_file && sed -i -- '/jobservice:/a\\ image: '$platform$job_service$version'' $yml_file
echo "Succeeded! "
echo "Please follow the normal installation process to install Harbor."

View File

@ -1,94 +0,0 @@
# Temporary files
files.txt
*.swp
*~
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# IPython Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject

View File

@ -1,71 +0,0 @@
## Introduction
[harbor](https://github.com/vmware/harbor) is the enterprise-class registry server for docker distribution.
harbor-py is the native and compatible python SDK for harbor and is included in the harbor git repository under `contrib/sdk/harbor-py`.
The supported APIs are:
- [x] Projects APIs
- [x] [Get projects](./examples/get_projects.py)
- [x] [Create project](./examples/create_project.py)
- [x] [Check project exist](./examples/check_project_exist.py)
- [x] [Set project publicity](./examples/set_project_publicity.py)
- [x] [Get project id from name](./examples/get_project_id_from_name.py)
- [ ] Get project access logs
- [ ] Get project member
- [ ] Get project and user member
- [x] Users APIs
- [x] [Get users](./examples/get_users.py)
- [x] [Create user](./examples/create_user.py)
- [x] [Update user profile](./examples/update_user_profile.py)
- [x] [Delete user](./examples/delete_user.py)
- [x] [Change password](./examples/change_password.py)
- [x] [Promote as admin](./examples/promote_as_admin.py)
- [x] Repositories APIs
- [x] [Get repositories](./examples/get_repositories.py)
- [x] [Delete repository](./examples/delete_repository.py)
- [x] [Get repository tags](./examples/get_repository_tags.py)
- [x] [Get repository manifests](./examples/get_repository_manifests.py)
- [x] Others APIs
- [x] [Search](./examples/search.py)
- [x] [Get statistics](./examples/get_statistics.py)
- [x] [Get top accessed repositories](./examples/get_top_accessed_repositories.py)
- [x] [Get logs](./examples/get_logs.py)
## Installation
```
pip install harbor-py
```
## Usage
```
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
client.get_projects()
client.get_users()
client.get_statistics()
client.get_top_accessed_repositories()
client.search("library")
```
For more usage, please refer to the [examples](./examples/).
## Contribution
If you have suggestions, feel free to submit [issues](https://github.com/vmware/harbor/issues) or send [pull-requests](https://github.com/vmware/harbor/pulls) for `harbor-py`.
Publish `harbor-py` package to [pypi](https://pypi.python.org/pypi/harbor-py/) server with the following commands.
```
python setup.py register -r pypi
python setup.py sdist upload -r pypi
```

View File

@ -1,18 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Change password
user_id = 2
old_password = "test-password"
new_password = "new-password"
client.change_password(user_id, old_password, new_password)

View File

@ -1,16 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Check if project exists
project_name = "test-project"
print(client.check_project_exist(project_name))

View File

@ -1,17 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Create project
project_name = "test-project"
is_public = True
client.create_project(project_name, is_public)

View File

@ -1,20 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Create user
username = "test-username"
email = "test-email@gmail.com"
password = "test-password"
realname = "test-realname"
comment = "test-comment"
client.create_user(username, email, password, realname, comment)

View File

@ -1,16 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Delete repository
repo_name = "library/cirros"
client.delete_repository(repo_name)

View File

@ -1,16 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Delete user
user_id = 2
print(client.delete_user(user_id))

View File

@ -1,15 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Get logs
print(client.get_logs())

View File

@ -1,16 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Get project id from name
project_name = "library"
print(client.get_project_id_from_name(project_name))

View File

@ -1,15 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Get all projects
print(client.get_projects())

View File

@ -1,16 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Get all repositories
project_id = 1
print(client.get_repositories(project_id))

View File

@ -1,17 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Get repository manifests
repo_name = "library/cirros"
tag = "latest"
client.get_repository_manifests(repo_name, tag)

View File

@ -1,16 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Get repository tags
repo_name = "library/cirros"
client.get_repository_tags(repo_name)

View File

@ -1,13 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
print(client.get_statistics())

View File

@ -1,19 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Get top accessed respositories
print(client.get_top_accessed_repositories())
# Get top accessed respositories with count
count = 1
print(client.get_top_accessed_repositories(count))

View File

@ -1,15 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Get all users
print(client.get_users())

View File

@ -1,16 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Promote as admin
user_id = 2
client.promote_as_admin(user_id)

View File

@ -1,16 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Search with query string
query_string = "library"
print(client.search(query_string))

View File

@ -1,17 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Set project publicity
project_id = 1
is_public = True
client.set_project_publicity(project_id, is_public)

View File

@ -1,12 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)

View File

@ -1,19 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append("../")
from harborclient import harborclient
host = "127.0.0.1"
user = "admin"
password = "Harbor12345"
client = harborclient.HarborClient(host, user, password)
# Update user profile
user_id = 2
email = "new@gmail.com"
realname = "new_realname"
comment = "new_comment"
client.update_user_profile(user_id, email, realname, comment)

View File

@ -1,5 +0,0 @@
#!/bin/bash
set -x
find ./ -name "*.py" |xargs sudo yapf -i

View File

@ -1,462 +0,0 @@
#!/usr/bin/env python
import json
import logging
import requests
logger = logging.getLogger(__name__)
class HarborClient(object):
def __init__(self, host, user, password, protocol="http"):
self.host = host
self.user = user
self.password = password
self.protocol = protocol
def __del__(self):
self.logout()
def login(self):
login_data = requests.post('%s://%s/login' %(self.protocol, self.host),
data={'principal': self.user,
'password': self.password}, verify=False)
if login_data.status_code == 200:
session_id = login_data.cookies.get('beegosessionID')
self.session_id = session_id
logger.debug("Successfully login, session id: {}".format(
session_id))
else:
logger.error("Fail to login, please try again")
def logout(self):
requests.get('%s://%s/log_out' % (self.protocol, self.host),
cookies={'beegosessionID': self.session_id}, verify=False)
logger.debug("Successfully logout")
# GET /search
def search(self, query_string):
result = None
path = '%s://%s/api/search?q=%s' % (self.protocol, self.host,
query_string)
response = requests.get(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = response.json()
logger.debug("Successfully get search result: {}".format(result))
else:
logger.error("Fail to get search result")
return result
# GET /projects
def get_projects(self):
result = None
path = '%s://%s/api/projects' % (self.protocol, self.host)
response = requests.get(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = response.json()
logger.debug("Successfully get projects result: {}".format(
result))
else:
logger.error("Fail to get projects result")
return result
# HEAD /projects
def check_project_exist(self, project_name):
result = False
path = '%s://%s/api/projects?project_name=%s' % (
self.protocol, self.host, project_name)
response = requests.head(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = True
logger.debug(
"Successfully check project exist, result: {}".format(result))
elif response.status_code == 404:
result = False
logger.debug(
"Successfully check project exist, result: {}".format(result))
else:
logger.error("Fail to check project exist")
return result
# POST /projects
def create_project(self, project_name, is_public=0):
result = False
path = '%s://%s/api/projects' % (self.protocol, self.host)
request_body = json.dumps({'project_name': project_name,
'public': is_public})
response = requests.post(path,
cookies={'beegosessionID': self.session_id},
data=request_body, verify=False)
if response.status_code == 201:
result = True
logger.debug(
"Successfully create project with project name: {}".format(
project_name))
else:
logger.error(
"Fail to create project with project name: {}, response code: {}".format(
project_name, response.status_code))
return result
# GET /projects/{project_id}/members
def get_project_members(self, project_id):
result = None
path = '%s://%s/api/projects/%s/members' % (self.protocol, self.host, project_id)
response = requests.get(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = response.json()
logger.debug(
"Successfully create project with project id: {}".format(
project_id))
else:
logger.error(
"Fail to create project with project id: {}, response code: {}".format(
project_id, response.status_code))
return result
# POST /projects/{project_id}/members
def add_project_member(self, project_id, username, role_id):
result = False
path = '%s://%s/api/projects/%s/members' % (self.protocol, self.host, project_id)
request_str = '{"username": "%s","roles": [%s]}' % (username, role_id)
request_body = json.dumps(json.loads(request_str))
response = requests.post(path,
cookies={'beegosessionID': self.session_id},
data=request_body, verify=False)
if response.status_code == 200:
result = True
logger.debug(
"Successfully add project member with project id: {}".format(
project_id))
else:
logger.error(
"Fail to add project member with project id: {}, response code: {}".format(
project_id, response.status_code))
return result
# DELETE /projects/{project_id}/members/{user_id}
def delete_member_from_project(self, project_id, user_id):
result = False
path = '%s://%s/api/projects/%s/members/%s' % (self.protocol, self.host,
project_id, user_id)
response = requests.delete(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = True
logger.debug("Successfully delete member with id: {}".format(
user_id))
else:
logger.error("Fail to delete member with id: {}, response code: {}"
.format(user_id, response.status_code))
return result
# PUT /projects/{project_id}/publicity
def set_project_publicity(self, project_id, is_public):
result = False
path = '%s://%s/api/projects/%s/publicity' % (
self.protocol, self.host, project_id)
request_body = json.dumps({'public': is_public})
response = requests.put(path,
cookies={'beegosessionID': self.session_id},
data=request_body, verify=False)
if response.status_code == 200:
result = True
logger.debug(
"Success to set project id: {} with publicity: {}".format(
project_id, is_public))
else:
logger.error(
"Fail to set publicity to project id: {} with status code: {}".format(
project_id, response.status_code))
return result
# GET /statistics
def get_statistics(self):
result = None
path = '%s://%s/api/statistics' % (self.protocol, self.host)
response = requests.get(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = response.json()
logger.debug("Successfully get statistics: {}".format(result))
else:
logger.error("Fail to get statistics result with status code: {}"
.format(response.status_code))
return result
# GET /users
def get_users(self):
# TODO: support parameter
result = None
path = '%s://%s/api/users' % (self.protocol, self.host)
response = requests.get(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = response.json()
logger.debug("Successfully get users result: {}".format(result))
else:
logger.error("Fail to get users result with status code: {}"
.format(response.status_code))
return result
# GET /users/current
def get_user_info(self):
result = None
path = '%s://%s/api/users/current' % (self.protocol, self.host)
response = requests.get(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = response.json()
logger.debug("Successfully get users result: {}".format(result))
else:
logger.error("Fail to get users result with status code: {}"
.format(response.status_code))
return result
# POST /users
def create_user(self, username, email, password, realname, comment):
result = False
path = '%s://%s/api/users' % (self.protocol, self.host)
request_body = json.dumps({'username': username,
'email': email,
'password': password,
'realname': realname,
'comment': comment})
response = requests.post(path,
cookies={'beegosessionID': self.session_id},
data=request_body, verify=False)
if response.status_code == 201:
result = True
logger.debug("Successfully create user with username: {}".format(
username))
else:
logger.error(
"Fail to create user with username: {}, response code: {}".format(
username, response.status_code))
return result
# PUT /users/{user_id}
def update_user_profile(self, user_id, email, realname, comment):
# TODO: support not passing comment
result = False
path = '%s://%s/api/users/%s' % (self.protocol, self.host,
user_id)
request_body = json.dumps({'email': email,
'realname': realname,
'comment': comment})
response = requests.put(path,
cookies={'beegosessionID': self.session_id},
data=request_body, verify=False)
if response.status_code == 200:
result = True
logger.debug(
"Successfully update user profile with user id: {}".format(
user_id))
else:
logger.error(
"Fail to update user profile with user id: {}, response code: {}".format(
user_id, response.status_code))
return result
# DELETE /users/{user_id}
def delete_user(self, user_id):
result = False
path = '%s://%s/api/users/%s' % (self.protocol, self.host,
user_id)
response = requests.delete(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = True
logger.debug("Successfully delete user with id: {}".format(
user_id))
else:
logger.error("Fail to delete user with id: {}, response code: {}"
.format(user_id, response.status_code))
return result
# PUT /users/{user_id}/password
def change_password(self, user_id, old_password, new_password):
result = False
path = '%s://%s/api/users/%s/password' % (
self.protocol, self.host, user_id)
request_body = json.dumps({'old_password': old_password,
'new_password': new_password})
response = requests.put(path,
cookies={'beegosessionID': self.session_id},
data=request_body, verify=False)
if response.status_code == 200:
result = True
logger.debug(
"Successfully change password for user id: {}".format(user_id))
else:
logger.error("Fail to change password for user id: {}".format(
user_id))
return result
# PUT /users/{user_id}/sysadmin
def promote_as_admin(self, user_id, has_admin_role):
result = False
path = '%s://%s/api/users/%s/sysadmin' % (
self.protocol, self.host, user_id)
request_body = json.dumps({'has_admin_role': has_admin_role,
'user_id': user_id})
response = requests.put(path,
cookies={'beegosessionID': self.session_id},
data=request_body, verify=False)
if response.status_code == 200:
result = True
logger.debug(
"Successfully promote user as admin with user id: {}".format(
user_id))
else:
logger.error(
"Fail to promote user as admin with user id: {}, response code: {}".format(
user_id, response.status_code))
return result
# GET /repositories
def get_repositories(self, project_id, query_string=None):
# TODO: support parameter
result = None
path = '%s://%s/api/repositories?project_id=%s' % (
self.protocol, self.host, project_id)
response = requests.get(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = response.json()
logger.debug(
"Successfully get repositories with id: {}, result: {}".format(
project_id, result))
else:
logger.error("Fail to get repositories result with id: {}, response code: {}".format(
project_id, response.status_code))
return result
# DELETE /repositories/{repo_name}/tags/{tag}
def delete_tag_of_repository(self, repo_name, tag):
result = False
path = '%s://%s/api/repositories/%s/tags/%s' % (self.protocol,self.host,
repo_name, tag)
response = requests.delete(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = True
logger.debug("Successfully delete a tag of repository: {}".format(
repo_name))
else:
logger.error("Fail to delete repository with name: {}, response code: {}".format(
repo_name, response.status_code))
return result
# DELETE /repositories/{repo_name}/tags
def delete_tags_of_repository(self, repo_name):
result = False
path = '%s://%s/api/repositories/%s/tags' % (self.protocol,
self.host, repo_name)
response = requests.delete(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = True
logger.debug("Successfully delete repository: {}".format(
repo_name))
else:
logger.error("Fail to delete repository with name: {}, response code: {}".format(
repo_name, response.status_code))
return result
# Get /repositories/{repo_name}/tags
def get_repository_tags(self, repo_name):
result = None
path = '%s://%s/api/repositories/%s/tags' % (
self.protocol, self.host, repo_name)
response = requests.get(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = response.json()
logger.debug(
"Successfully get tag with repo name: {}, result: {}".format(
repo_name, result))
else:
logger.error("Fail to get tags with repo name: {}, response code: {}".format(
repo_name, response.status_code))
return result
# GET /repositories/{repo_name}/tags/{tag}/manifest
def get_repository_manifest(self, repo_name, tag):
result = None
path = '%s://%s/api/repositories/%s/tags/%s/manifest' % (
self.protocol, self.host, repo_name, tag)
response = requests.get(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = response.json()
logger.debug(
"Successfully get manifests with repo name: {}, tag: {}, result: {}".format(
repo_name, tag, result))
else:
logger.error(
"Fail to get manifests with repo name: {}, tag: {}".format(
repo_name, tag))
return result
# GET /repositories/top
def get_top_accessed_repositories(self, count=None):
result = None
path = '%s://%s/api/repositories/top' % (self.protocol, self.host)
if count:
path += "?count=%s" % (count)
response = requests.get(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = response.json()
logger.debug(
"Successfully get top accessed repositories, result: {}".format(
result))
else:
logger.error("Fail to get top accessed repositories")
return result
# GET /logs
def get_logs(self, lines=None, start_time=None, end_time=None):
result = None
path = '%s://%s/api/logs' % (self.protocol, self.host)
response = requests.get(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = response.json()
logger.debug("Successfully get logs")
else:
logger.error("Fail to get logs and response code: {}".format(
response.status_code))
return result
# Get /systeminfo
def get_systeminfo(self):
result = None
path = '%s://%s/api/systeminfo' % (self.protocol, self.host)
response = requests.get(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = response.json()
logger.debug(
"Successfully get systeminfo, result: {}".format(result))
else:
logger.error("Fail to get systeminfo, response code: {}".format(response.status_code))
return result
# Get /configurations
def get_configurations(self):
result = None
path = '%s://%s/api/configurations' % (self.protocol, self.host)
response = requests.get(path,
cookies={'beegosessionID': self.session_id}, verify=False)
if response.status_code == 200:
result = response.json()
logger.debug(
"Successfully get configurations, result: {}".format(result))
else:
logger.error("Fail to get configurations, response code: {}".format(response.status_code))
return result

View File

@ -1,3 +0,0 @@
#!/usr/bin/env python
import harborclient

View File

@ -1 +0,0 @@
requests>2.2.0

View File

@ -1,103 +0,0 @@
""" The missing harbor python SDK
See: https://github.com/vmware/harbor/tree/master/contrib/sdk/harbor-py
"""
from setuptools import setup, find_packages
from codecs import open
from os import path
setup(
name='harbor-py',
# Versions should comply with PEP440. For a discussion on single-sourcing
# the version across setup.py and the project code, see
# https://packaging.python.org/en/latest/single_source_version.html
version='1.2.0',
description='The missing harbor python SDK',
# The project's main homepage.
url='https://github.com/vmware/harbor',
# Author details
author='tobe',
author_email='tobeg3oogle@gmail.com',
# Choose your license
license='Apache Software',
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
# How mature is this project? Common values are
# 3 - Alpha
# 4 - Beta
# 5 - Production/Stable
'Development Status :: 3 - Alpha',
# Indicate who your project is intended for
'Intended Audience :: Developers',
'Topic :: Software Development :: Build Tools',
# Pick your license as you wish (should match "license" above)
'License :: OSI Approved :: Apache Software License',
# Specify the Python versions you support here. In particular, ensure
# that you indicate whether you support Python 2, Python 3 or both.
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
],
# What does your project relate to?
keywords='docker registry distribution harbor python sdk',
# You can just specify the packages manually here if your project is
# simple. Or you can use find_packages().
#packages=find_packages(exclude=['contrib', 'docs', 'tests']),
packages=['harborclient'],
# Alternatively, if you want to distribute just a my_module.py, uncomment
# this:
# py_modules=["my_module"],
# List run-time dependencies here. These will be installed by pip when
# your project is installed. For an analysis of "install_requires" vs pip's
# requirements files see:
# https://packaging.python.org/en/latest/requirements.html
install_requires=['requests>2.2.0'],
# List additional groups of dependencies here (e.g. development
# dependencies). You can install these using the following syntax,
# for example:
# $ pip install -e .[dev,test]
#extras_require={
# 'dev': ['check-manifest'],
# 'test': ['coverage'],
#},
# If there are data files included in your packages that need to be
# installed, specify them here. If using Python 2.6 or less, then these
# have to be included in MANIFEST.in as well.
#package_data={
# 'sample': ['package_data.dat'],
#},
# Although 'package_data' is the preferred approach, in some case you may
# need to place data files outside of your packages. See:
# http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa
# In this case, 'data_file' will be installed into '<sys.prefix>/my_data'
#data_files=[('my_data', ['data/data_file'])],
# To provide executable scripts, use entry points in preference to the
# "scripts" keyword. Entry points provide cross-platform support and allow
# pip to create the appropriate form of executable for the target platform.
#entry_points={
# 'console_scripts': [
# 'harbor=harborclient:main',
# ],
#},
)

View File

@ -1,7 +0,0 @@
#!/bin/sh
set -x
set -e
python setup.py install --record files.txt
cat files.txt | xargs rm -rf