Coverage for dxf/__init__.py : 93%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
""" Module for accessing a Docker v2 Registry """
from urllib.parse import urlencode # pylint: disable=import-error,no-name-in-module,wrong-import-order
except ImportError: import urllib3
# pylint: disable=wildcard-import
else: _binary_type = bytes # pylint: disable=redefined-builtin long = int
""" Hash bytes using the same method the registry uses (currently SHA-256).
:param buf: Bytes to hash :type buf: binary str
:rtype: str :returns: Hex-encoded hash of file's content (prefixed by ``sha256:``) """
""" Hash a file using the same method the registry uses (currently SHA-256).
:param filename: Name of file to hash :type filename: str
:rtype: str :returns: Hex-encoded hash of file's content (prefixed by ``sha256:``) """
# pylint: disable=no-member
raise exceptions.DXFUnexpectedDigestMethodError(method, 'sha256')
# define __iter__ so requests thinks we're a stream # (models.py, PreparedRequest.prepare_body) assert not "called" # define fileno, tell and mode so requests can find length # (utils.py, super_len) def mode(self):
# pylint: disable=too-few-public-methods
# pylint: disable=too-few-public-methods
# pylint: disable=protected-access
# pylint: disable=too-many-instance-attributes """ Class for communicating with a Docker v2 registry. Contains only operations which aren't related to repositories.
Can act as a context manager. For each context entered, a new `requests.Session <http://docs.python-requests.org/en/latest/user/advanced/#session-objects>`_ is obtained. Connections to the same host are shared by the session. When the context exits, all the session's connections are closed.
If you don't use :class:`DXFBase` as a context manager, each request uses an ephemeral session. If you don't read all the data from an iterator returned by :meth:`DXF.pull_blob` then the underlying connection won't be closed until Python garbage collects the iterator. """ # pylint: disable=too-many-arguments """ :param host: Host name of registry. Can contain port numbers. e.g. ``registry-1.docker.io``, ``localhost:5000``. :type host: str
:param auth: Authentication function to be called whenever authentication to the registry is required. Receives the :class:`DXFBase` object and a HTTP response object. It should call :meth:`authenticate` with a username, password and ``response`` before it returns. :type auth: function(dxf_obj, response)
:param insecure: Use HTTP instead of HTTPS (which is the default) when connecting to the registry. :type insecure: bool
:param auth_host: Host to use for token authentication. If set, overrides host returned by then registry. :type auth_host: str
:param tlsverify: When set to False, do not verify TLS certificate. When pointed to a `<ca bundle>.crt` file use this for TLS verification. See `requests.verify <http://docs.python-requests.org/en/latest/user/advanced/#ssl-cert-verification>`_ for more details. :type tlsverify: bool or str """
def token(self): """ str: Authentication token. This will be obtained automatically when you call :meth:`authenticate`. If you've obtained a token previously, you can also set it but be aware tokens expire quickly. """
def token(self, value): 'Authorization': 'Bearer ' + value }
# pylint: disable=no-member
username=None, password=None, actions=None, response=None, authorization=None): # pylint: disable=too-many-arguments,too-many-locals """ Authenticate to the registry using a username and password, an authorization header or otherwise as the anonymous user.
:param username: User name to authenticate as. :type username: str
:param password: User's password. :type password: str
:param actions: If you know which types of operation you need to make on the registry, specify them here. Valid actions are ``pull``, ``push`` and ``*``. :type actions: list
:param response: When the ``auth`` function you passed to :class:`DXFBase`'s constructor is called, it is passed a HTTP response object. Pass it back to :meth:`authenticate` to have it automatically detect which actions are required. :type response: requests.Response
:param authorization: ``Authorization`` header value. :type authorization: str
:rtype: str :returns: Authentication token, if the registry supports bearer tokens. Otherwise ``None``, and HTTP Basic auth is used (if the registry requires authentication). """
# pylint: disable=no-member raise exceptions.DXFUnexpectedStatusCodeError(response.status_code, requests.codes.unauthorized)
'Authorization': 'Basic ' + base64.b64encode(_to_bytes_2and3(username + ':' + password)).decode('utf-8') } 'Authorization': authorization } else:
else: scope = '' 'service': info['service'], 'scope': scope })
""" List all repositories in the registry.
:param batch_size: Number of repository names to ask the server for at a time. :type batch_size: int
:param iterate: Whether to return iterator over the names or a list of all the names. :type iterate: bool
:rtype: list or iterator of strings :returns: Repository names. """ '_catalog', 'repositories', params={'n': batch_size})
""" Class for operating on a Docker v2 repositories. """ # pylint: disable=too-many-arguments """ :param host: Host name of registry. Can contain port numbers. e.g. ``registry-1.docker.io``, ``localhost:5000``. :type host: str
:param repo: Name of the repository to access on the registry. Typically this is of the form ``username/reponame`` but for your own registries you don't actually have to stick to that. :type repo: str
:param auth: Authentication function to be called whenever authentication to the registry is required. Receives the :class:`DXF` object and a HTTP response object. It should call :meth:`DXFBase.authenticate` with a username, password and ``response`` before it returns. :type auth: function(dxf_obj, response)
:param insecure: Use HTTP instead of HTTPS (which is the default) when connecting to the registry. :type insecure: bool
:param auth_host: Host to use for token authentication. If set, overrides host returned by then registry. :type auth_host: str
:param tlsverify: When set to False, do not verify TLS certificate. When pointed to a `<ca bundle>.crt` file use this for TLS verification. See `requests.verify <http://docs.python-requests.org/en/latest/user/advanced/#ssl-cert-verification>`_ for more details. :type tlsverify: bool or str """
self._repo_path + path, **kwargs)
filename=None, progress=None, data=None, digest=None, check_exists=True): # pylint: disable=too-many-arguments """ Upload a file to the registry and return its (SHA-256) hash.
The registry is content-addressable so the file's content (aka blob) can be retrieved later by passing the hash to :meth:`pull_blob`.
:param filename: File to upload. :type filename: str
:param data: Data to upload if ``filename`` isn't given. The data is uploaded in chunks and you must also pass ``digest``. :type data: Generator or iterator
:param digest: Hash of the data to be uploaded in ``data``, if specified. :type digest: str (hex-encoded SHA-256, prefixed by ``sha256:``)
:param progress: Optional function to call as the upload progresses. The function will be called with the hash of the file's content (or ``digest``), the blob just read from the file (or chunk from ``data``) and if ``filename`` is specified the total size of the file. :type progress: function(dgst, chunk, size)
:param check_exists: Whether to check if a blob with the same hash already exists in the registry. If so, it won't be uploaded again. :type check_exists: bool
:rtype: str :returns: Hash of file's content. """ else: # pylint: disable=no-member raise else:
# pylint: disable=no-self-use """ Download a blob from the registry given the hash of its content.
:param digest: Hash of the blob's content (prefixed by ``sha256:``). :type digest: str
:param size: Whether to return the size of the blob too. :type size: bool
:param chunk_size: Number of bytes to download at a time. Defaults to 8192. :type chunk_size: int
:rtype: iterator :returns: If ``size`` is falsey, a byte string iterator over the blob's content. If ``size`` is truthy, a tuple containing the iterator and the blob's size. """ # pylint: disable=too-few-public-methods
""" Return the size of a blob in the registry given the hash of its content.
:param digest: Hash of the blob's content (prefixed by ``sha256:``). :type digest: str
:rtype: long :returns: Whether the blob exists. """
""" Delete a blob from the registry given the hash of its content.
:param digest: Hash of the blob's content (prefixed by ``sha256:``). :type digest: str """
# For dtuf; highly unlikely anyone else will want this 'mediaType': 'application/octet-stream', 'size': self.blob_size(dgst), 'digest': dgst } for dgst in digests] 'schemaVersion': 2, 'mediaType': 'application/vnd.docker.distribution.manifest.v2+json', # V2 Schema 2 insists on a config dependency. We're just using the # registry as a blob store so to save us uploading extra blobs, # use the first layer. 'config': { 'mediaType': 'application/octet-stream', 'size': layers[0]['size'], 'digest': layers[0]['digest'] }, 'layers': layers }, sort_keys=True)
""" Give a name (alias) to a manifest.
:param alias: Alias name :type alias: str
:param manifest_json: A V2 Schema 2 manifest JSON string :type digests: list """ 'manifests/' + alias, data=manifest_json, headers={'Content-Type': _schema2_mimetype})
# pylint: disable=too-many-locals """ Give a name (alias) to a set of blobs. Each blob is specified by the hash of its content.
:param alias: Alias name :type alias: str
:param digests: List of blob hashes (prefixed by ``sha256:``). :type digests: list of strings
:rtype: str :returns: The registry manifest used to define the alias. You almost definitely won't need this. """ # pylint: disable=no-member raise
""" Request the manifest for an alias and return the manifest and the response.
:param alias: Alias name. :type alias: str
:rtype: tuple :returns: Tuple containing the manifest as a string (JSON) and the `requests.Response <http://docs.python-requests.org/en/master/api/#requests.Response>`_ """ 'manifests/' + alias, headers={'Accept': _schema2_mimetype + ', ' + _schema1_mimetype})
""" Get the manifest for an alias
:param alias: Alias name. :type alias: str
:rtype: str :returns: The manifest as string (JSON) """
# pylint: disable=too-many-arguments
# https://github.com/docker/distribution/issues/1662#issuecomment-213101772 # "A schema1 manifest should always produce the same image id but # defining the steps to produce directly from the manifest is not # straight forward."
parsed_manifest, dcd, verify)
raise exceptions.DXFDigestMismatchError( method + ':' + dgst, method + ':' + expected_dgst)
alias=None, manifest=None, verify=True, sizes=False, dcd=None): # pylint: disable=too-many-arguments """ Get the blob hashes assigned to an alias.
:param alias: Alias name. You almost definitely will only need to pass this argument. :type alias: str
:param manifest: If you previously obtained a manifest, specify it here instead of ``alias``. You almost definitely won't need to do this. :type manifest: str
:param verify: (v1 schema only) Whether to verify the integrity of the alias definition in the registry itself. You almost definitely won't need to change this from the default (``True``). :type verify: bool
:param sizes: Whether to return sizes of the blobs along with their hashes :type sizes: bool
:param dcd: (if ``manifest`` is specified) The Docker-Content-Digest header returned when getting the manifest. If present, this is checked against the manifest. :type dcd: str
:rtype: list :returns: If ``sizes`` is falsey, a list of blob hashes (strings) which are assigned to the alias. If ``sizes`` is truthy, a list of (hash,size) tuples for each blob. """
alias=None, manifest=None, verify=True, dcd=None): """ (v2 schema only) Get the hash of an alias's configuration blob.
For an alias created using ``dxf``, this is the hash of the first blob assigned to the alias.
For a Docker image tag, this is the same as ``docker inspect alias --format='{{.Id}}'``.
:param alias: Alias name. You almost definitely will only need to pass this argument. :type alias: str
:param manifest: If you previously obtained a manifest, specify it here instead of ``alias``. You almost definitely won't need to do this. :type manifest: str
:param verify: (v1 schema only) Whether to verify the integrity of the alias definition in the registry itself. You almost definitely won't need to change this from the default (``True``). :type verify: bool
:param dcd: (if ``manifest`` is specified) The Docker-Content-Digest header returned when getting the manifest. If present, this is checked against the manifest. :type dcd: str
:rtype: str :returns: Hash of the alias's configuration blob. """
""" Get the Docker-Content-Digest header for an alias.
:param alias: Alias name. :type alias: str
:rtype: str :returns: DCD header for the alias. """ # https://docs.docker.com/registry/spec/api/#deleting-an-image # Note When deleting a manifest from a registry version 2.3 or later, # the following header must be used when HEAD or GET-ing the manifest # to obtain the correct digest to delete: # Accept: application/vnd.docker.distribution.manifest.v2+json 'head', 'manifests/{}'.format(alias), headers={'Accept': _schema2_mimetype}, ).headers.get('Docker-Content-Digest')
""" Delete an alias from the registry. The blobs it points to won't be deleted. Use :meth:`del_blob` for that.
.. Note:: On private registry, garbage collection might need to be run manually; see: https://docs.docker.com/registry/garbage-collection/
:param alias: Alias name. :type alias: str
:rtype: list :returns: A list of blob hashes (strings) which were assigned to the alias. """
""" List all aliases defined in the repository.
:param batch_size: Number of alias names to ask the server for at a time. :type batch_size: int
:param iterate: Whether to return iterator over the names or a list of all the names. :type iterate: bool
:rtype: list or iterator of strings :returns: Alias names. """ 'tags/list', 'tags', params={'n': batch_size})
""" Performs API version check
:rtype: tuple :returns: verson check response as a string (JSON) and requests.Response """
def from_base(cls, base, repo): """ Create a :class:`DXF` object which uses the same host, settings and session as an existing :class:`DXFBase` object.
:param base: Existing :class:`DXFBase` object. :type base: :class:`DXFBase`
:param repo: Name of the repository to access on the registry. Typically this is of the form ``username/reponame`` but for your own registries you don't actually have to stick to that. :type repo: str
:returns: :class:`DXF` object which shares configuration and session with ``base`` but which can also be used to operate on the ``repo`` repository. :rtype: :class:`DXF` """ # pylint: disable=protected-access
# v1 schema support functions below
'schemaVersion': 1, 'name': self._repo, 'tag': alias, 'fsLayers': [{'blobSum': dgst} for dgst in digests], 'history': [{'v1Compatibility': '{}'} for dgst in digests] }, sort_keys=True)
raise exceptions.DXFUnexpectedKeyTypeError(expkey['kty'], 'EC') raise exceptions.DXFUnexpectedKeyTypeError(expkey['crv'], 'P-256')
# Docker expects 32 bytes for x and y 'formatLength': format_length, 'formatTail': _urlsafe_b64encode(format_tail) }, { 'jwk': jkey, 'alg': 'ES256' }) ', "signatures": [' + jwstoken.serialize() + ']' + \ format_tail
manifest, content_digest=None, verify=True): # pylint: disable=too-many-locals,too-many-branches
# Adapted from https://github.com/joyent/node-docker-registry-client
raise exceptions.DXFDisallowedSignatureAlgorithmError('none') raise exceptions.DXFSignatureChainNotImplementedError()
'alg': alg, 'signature': sig['signature'], 'protected64': protected64, 'key': _import_key(sig['header']['jwk']), 'format_length': format_length, 'format_tail': format_tail })
signatures[0]['format_tail'] else:
raise exceptions.DXFDigestMismatchError( method + ':' + dgst, method + ':' + expected_dgst)
'payload': payload64, 'protected': sig['protected64'], 'signature': sig['signature'] }), sig['key'], sig['alg'])
|