Add Commands For Docker Registry Image Cleanup
Add commands for registry-image-list, to list the existing images in the repo registry-image-tags, to list the tags of a specified image registry-image-delete, to delete a tag of a specified image registry-garbage-collect, to delete space on the filesystem from images that do not have any tags referencing them It is currently impossible to delete images from the local Docker registry on the controllers. This can be an issue if images are continuously pushed. The only solution was to grow the file system. This commit provides commands for a user to find unwanted images and to delete them from the controller local Docker registry in order to free up space on the file system. There is a possibility of the registry getting stuck in read-only mode due to garbage collect if puppet dies or there is a swact while garbage collect is running. In this situation, the user should run garbage collect again. Getting stuck in read-only mode does not prevent image pulls or kubernetes deployments from existing images from starting or completing. Story: 2002840 Task: 28621 Change-Id: I4bb9301a1165db8b860418c413aa3238169bab03 Signed-off-by: Jerry Sun <jerry.sun@windriver.com>
This commit is contained in:
parent
a8b23796fe
commit
92f1658c29
|
@ -10741,3 +10741,137 @@ itemNotFound (404)
|
|||
}
|
||||
|
||||
This operation does not accept a request body.
|
||||
|
||||
---------------
|
||||
Docker Registry
|
||||
---------------
|
||||
|
||||
These APIs allow the display and delete of images in the local Docker registry
|
||||
|
||||
*****************************************
|
||||
List images in the local Docker registry
|
||||
*****************************************
|
||||
|
||||
.. rest_method:: GET /v1/registry_image
|
||||
|
||||
**Normal response codes**
|
||||
|
||||
200
|
||||
|
||||
**Error response codes**
|
||||
|
||||
computeFault (400, 500, ...), serviceUnavailable (503), badRequest (400),
|
||||
unauthorized (401), forbidden (403), badMethod (405), overLimit (413),
|
||||
itemNotFound (404)
|
||||
|
||||
**Response parameters**
|
||||
|
||||
.. csv-table::
|
||||
:header: "Parameter", "Style", "Type", "Description"
|
||||
:widths: 20, 20, 20, 60
|
||||
|
||||
"registry_images (Optional)", "plain", "xsd:list", "The list of images in the local Docker registry."
|
||||
"tag (Optional)", "plain", "xsd:string", "The image tag for the image"
|
||||
"name (Optional)", "plain", "xsd:string", "The name of the image, including full repo"
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"registry_images": [
|
||||
{
|
||||
"tag": null,
|
||||
"name": "docker.io/port/ceph-config-helper"
|
||||
},
|
||||
{
|
||||
"tag": null,
|
||||
"name": "quay.io/external_storage/rbd-provisioner"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
This operation does not accept a request body.
|
||||
|
||||
********************************************************
|
||||
List tags of a given image in the local Docker registry
|
||||
********************************************************
|
||||
|
||||
.. rest_method:: GET /v1/registry_image
|
||||
|
||||
**Normal response codes**
|
||||
|
||||
200
|
||||
|
||||
**Error response codes**
|
||||
|
||||
computeFault (400, 500, ...), serviceUnavailable (503), badRequest (400),
|
||||
unauthorized (401), forbidden (403), badMethod (405), overLimit (413),
|
||||
itemNotFound (404)
|
||||
|
||||
**Request parameters**
|
||||
|
||||
.. csv-table::
|
||||
:header: "Parameter", "Style", "Type", "Description"
|
||||
:widths: 20, 20, 20, 60
|
||||
|
||||
"image_name", "plain", "csapi:string", "The name of the image, including full repo"
|
||||
|
||||
**Response parameters**
|
||||
|
||||
.. csv-table::
|
||||
:header: "Parameter", "Style", "Type", "Description"
|
||||
:widths: 20, 20, 20, 60
|
||||
|
||||
"registry_images (Optional)", "plain", "xsd:list", "The list of images in the local Docker registry."
|
||||
"tag (Optional)", "plain", "xsd:string", "The image tag for the image"
|
||||
"name (Optional)", "plain", "xsd:string", "The name of the image, including full repo"
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"registry_images": [
|
||||
{
|
||||
"tag": "v1.10.3",
|
||||
"name": "docker.io/port/ceph-config-helper"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
**********************************************
|
||||
Deletes an image in the local Docker registry
|
||||
**********************************************
|
||||
|
||||
.. rest_method:: DELETE /v1/registry_image
|
||||
|
||||
**Normal response codes**
|
||||
|
||||
204
|
||||
|
||||
**Request parameters**
|
||||
|
||||
.. csv-table::
|
||||
:header: "Parameter", "Style", "Type", "Description"
|
||||
:widths: 20, 20, 20, 60
|
||||
|
||||
"image_name_and_tag", "plain", "csapi:string", "The image name and tag of the form name:tag"
|
||||
|
||||
This operation does not accept a request body.
|
||||
|
||||
******************************************
|
||||
Run the Docker registry garbage collector
|
||||
******************************************
|
||||
|
||||
.. rest_method:: POST /v1/registry_image
|
||||
|
||||
**Normal response codes**
|
||||
|
||||
204
|
||||
|
||||
**Request parameters**
|
||||
|
||||
.. csv-table::
|
||||
:header: "Parameter", "Style", "Type", "Description"
|
||||
:widths: 20, 20, 20, 60
|
||||
|
||||
"garbage_collect", "plain", "csapi:bool", "run the garbage collect?"
|
||||
|
||||
This operation does not accept a request body.
|
||||
|
|
|
@ -2,6 +2,20 @@ class platform::dockerdistribution::params (
|
|||
$registry_ks_endpoint = undef,
|
||||
) {}
|
||||
|
||||
define platform::dockerdistribution::write_config (
|
||||
$registry_readonly = false,
|
||||
$file_path = '/etc/docker-distribution/registry/runtime_config.yml',
|
||||
$docker_registry_ip = undef,
|
||||
){
|
||||
file { $file_path:
|
||||
ensure => present,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => template('platform/dockerdistribution.conf.erb'),
|
||||
}
|
||||
}
|
||||
|
||||
class platform::dockerdistribution::config
|
||||
inherits ::platform::dockerdistribution::params {
|
||||
include ::platform::params
|
||||
|
@ -11,6 +25,8 @@ class platform::dockerdistribution::config
|
|||
include ::platform::docker::params
|
||||
|
||||
$docker_registry_ip = $::platform::network::mgmt::params::controller_address
|
||||
$runtime_config = '/etc/docker-distribution/registry/runtime_config.yml'
|
||||
$used_config = '/etc/docker-distribution/registry/config.yml'
|
||||
|
||||
# check insecure registries
|
||||
if $::platform::docker::params::insecure_registry {
|
||||
|
@ -35,12 +51,18 @@ class platform::dockerdistribution::config
|
|||
content => template('platform/insecuredockerregistry.conf.erb'),
|
||||
}
|
||||
|
||||
file { '/etc/docker-distribution/registry/config.yml':
|
||||
ensure => present,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => template('platform/dockerdistribution.conf.erb'),
|
||||
platform::dockerdistribution::write_config { 'runtime_config':
|
||||
docker_registry_ip => $docker_registry_ip
|
||||
}
|
||||
|
||||
-> exec { 'use runtime config file':
|
||||
command => "ln -fs ${runtime_config} ${used_config}",
|
||||
}
|
||||
|
||||
platform::dockerdistribution::write_config { 'readonly_config':
|
||||
registry_readonly => true,
|
||||
file_path => '/etc/docker-distribution/registry/readonly_config.yml',
|
||||
docker_registry_ip => $docker_registry_ip,
|
||||
}
|
||||
|
||||
file { '/etc/docker-distribution/registry/token_server.conf':
|
||||
|
@ -236,6 +258,32 @@ class platform::dockerdistribution::runtime {
|
|||
}
|
||||
}
|
||||
|
||||
class platform::dockerdistribution::garbagecollect {
|
||||
$runtime_config = '/etc/docker-distribution/registry/runtime_config.yml'
|
||||
$readonly_config = '/etc/docker-distribution/registry/readonly_config.yml'
|
||||
$used_config = '/etc/docker-distribution/registry/config.yml'
|
||||
|
||||
exec { 'turn registry read only':
|
||||
command => "ln -fs ${readonly_config} ${used_config}",
|
||||
}
|
||||
|
||||
# it doesn't like 2 platform::sm::restart with the same name
|
||||
# so we have to do 1 as a command
|
||||
-> exec { 'restart docker-distribution in read only':
|
||||
command => 'sm-restart-safe service docker-distribution',
|
||||
}
|
||||
|
||||
-> exec { 'run garbage collect':
|
||||
command => "/usr/bin/registry garbage-collect ${used_config}",
|
||||
}
|
||||
|
||||
-> exec { 'turn registry back to read write':
|
||||
command => "ln -fs ${runtime_config} ${used_config}",
|
||||
}
|
||||
|
||||
-> platform::sm::restart {'docker-distribution': }
|
||||
}
|
||||
|
||||
class platform::dockerdistribution::bootstrap
|
||||
inherits ::platform::dockerdistribution::params {
|
||||
|
||||
|
|
|
@ -7,6 +7,9 @@ storage:
|
|||
blobdescriptor: inmemory
|
||||
filesystem:
|
||||
rootdirectory: /var/lib/docker-distribution
|
||||
maintenance:
|
||||
readonly:
|
||||
enabled: <%= @registry_readonly %>
|
||||
http:
|
||||
addr: <%= @docker_registry_ip %>:9001
|
||||
tls:
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
SRC_DIR="cgts-client"
|
||||
TIS_PATCH_VER=64
|
||||
TIS_PATCH_VER=65
|
||||
|
|
|
@ -62,6 +62,7 @@ from cgtsclient.v1 import partition
|
|||
from cgtsclient.v1 import pci_device
|
||||
from cgtsclient.v1 import port
|
||||
from cgtsclient.v1 import ptp
|
||||
from cgtsclient.v1 import registry_image
|
||||
from cgtsclient.v1 import remotelogging
|
||||
from cgtsclient.v1 import route
|
||||
from cgtsclient.v1 import sdn_controller
|
||||
|
@ -143,6 +144,7 @@ class Client(http.HTTPClient):
|
|||
self.sm_service = sm_service.SmServiceManager(self)
|
||||
self.sm_servicegroup = sm_servicegroup.SmServiceGroupManager(self)
|
||||
self.health = health.HealthManager(self)
|
||||
self.registry_image = registry_image.RegistryImageManager(self)
|
||||
self.remotelogging = remotelogging.RemoteLoggingManager(self)
|
||||
self.sdn_controller = sdn_controller.SDNControllerManager(self)
|
||||
self.partition = partition.partitionManager(self)
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
#
|
||||
# Copyright (c) 2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
|
||||
from cgtsclient.common import base
|
||||
from cgtsclient.v1 import options
|
||||
|
||||
|
||||
class RegistryImage(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<registry_image %s>" % self._info
|
||||
|
||||
|
||||
class RegistryImageManager(base.Manager):
|
||||
resource_class = RegistryImage
|
||||
|
||||
@staticmethod
|
||||
def _path(name=None):
|
||||
return '/v1/registry_image/%s' % name if name else '/v1/registry_image'
|
||||
|
||||
def list(self):
|
||||
"""Retrieve the list of images from the registry."""
|
||||
|
||||
return self._list(self._path(), 'registry_images')
|
||||
|
||||
def tags(self, image_name):
|
||||
"""Retrieve the list of tags from the registry for a specified image.
|
||||
|
||||
:param image_name: image name
|
||||
"""
|
||||
path = options.build_url(self._path(), None, ['image_name=%s' % image_name])
|
||||
return self._list(path, 'registry_images')
|
||||
|
||||
def delete(self, image_name_and_tag):
|
||||
"""Delete registry image given name and tag
|
||||
|
||||
:param image_name_and_tag: a string of the form name:tag
|
||||
"""
|
||||
path = options.build_url(self._path(), None, ['image_name_and_tag=%s' % image_name_and_tag])
|
||||
return self._delete(path)
|
||||
|
||||
def garbage_collect(self):
|
||||
path = options.build_url(self._path(), None, ['garbage_collect=%s' % True])
|
||||
return self._create(path, {})
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright (c) 2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
from cgtsclient.common import utils
|
||||
from cgtsclient import exc
|
||||
|
||||
|
||||
def do_registry_image_list(cc, args):
|
||||
"""List all images in local docker registry"""
|
||||
images = cc.registry_image.list()
|
||||
labels = ['Image Name']
|
||||
fields = ['name']
|
||||
utils.print_list(images, fields, labels, sortby=0)
|
||||
|
||||
|
||||
@utils.arg('name', metavar='<image name>',
|
||||
help="Name of an image")
|
||||
def do_registry_image_tags(cc, args):
|
||||
"""List all tags for a Docker image from the local registry"""
|
||||
images = cc.registry_image.tags(args.name)
|
||||
labels = ['Image Tag']
|
||||
fields = ['tag']
|
||||
utils.print_list(images, fields, labels, sortby=0)
|
||||
|
||||
|
||||
@utils.arg('name_and_tag', metavar='<image name and tag>',
|
||||
help="Name and tag of an image, in the form name:tag")
|
||||
def do_registry_image_delete(cc, args):
|
||||
"""Remove the specified Docker image from the local registry"""
|
||||
try:
|
||||
cc.registry_image.delete(args.name_and_tag)
|
||||
print('Image %s deleted, please run garbage collect to free disk space.' % args.name_and_tag)
|
||||
except exc.HTTPNotFound:
|
||||
raise exc.CommandError('Image not found: %s' % args.name_and_tag)
|
||||
|
||||
|
||||
def do_registry_garbage_collect(cc, args):
|
||||
"""Run the registry garbage collector"""
|
||||
|
||||
cc.registry_image.garbage_collect()
|
||||
print('Running docker registry garbage collect')
|
|
@ -50,6 +50,7 @@ from cgtsclient.v1 import partition_shell
|
|||
from cgtsclient.v1 import pci_device_shell
|
||||
from cgtsclient.v1 import port_shell
|
||||
from cgtsclient.v1 import ptp_shell
|
||||
from cgtsclient.v1 import registry_image_shell
|
||||
from cgtsclient.v1 import remotelogging_shell
|
||||
from cgtsclient.v1 import route_shell
|
||||
from cgtsclient.v1 import sdn_controller_shell
|
||||
|
@ -106,6 +107,7 @@ COMMAND_MODULES = [
|
|||
lldp_agent_shell,
|
||||
lldp_neighbour_shell,
|
||||
health_shell,
|
||||
registry_image_shell,
|
||||
remotelogging_shell,
|
||||
sdn_controller_shell,
|
||||
partition_shell,
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
SRC_DIR="sysinv"
|
||||
TIS_PATCH_VER=317
|
||||
TIS_PATCH_VER=318
|
||||
|
|
|
@ -59,6 +59,7 @@ from sysinv.api.controllers.v1 import port
|
|||
from sysinv.api.controllers.v1 import profile
|
||||
from sysinv.api.controllers.v1 import ptp
|
||||
from sysinv.api.controllers.v1 import pv
|
||||
from sysinv.api.controllers.v1 import registry_image
|
||||
from sysinv.api.controllers.v1 import remotelogging
|
||||
from sysinv.api.controllers.v1 import route
|
||||
from sysinv.api.controllers.v1 import sdn_controller
|
||||
|
@ -223,6 +224,9 @@ class V1(base.APIBase):
|
|||
health = [link.Link]
|
||||
"Links to the system health resource"
|
||||
|
||||
registry_image = [link.Link]
|
||||
"Links to the Docker registry image resource"
|
||||
|
||||
remotelogging = [link.Link]
|
||||
"Links to the remotelogging resource"
|
||||
|
||||
|
@ -686,6 +690,15 @@ class V1(base.APIBase):
|
|||
'health', '', bookmark=True)
|
||||
]
|
||||
|
||||
v1.registry_image = [link.Link.make_link('self',
|
||||
pecan.request.host_url,
|
||||
'registry_image', ''),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
'registry_image', '',
|
||||
bookmark=True)
|
||||
]
|
||||
|
||||
v1.remotelogging = [link.Link.make_link('self',
|
||||
pecan.request.host_url,
|
||||
'remotelogging', ''),
|
||||
|
@ -809,6 +822,7 @@ class Controller(rest.RestController):
|
|||
servicenodes = servicenode.SMServiceNodeController()
|
||||
servicegroup = servicegroup.SMServiceGroupController()
|
||||
health = health.HealthController()
|
||||
registry_image = registry_image.RegistryImageController()
|
||||
remotelogging = remotelogging.RemoteLoggingController()
|
||||
sdn_controller = sdn_controller.SDNControllerController()
|
||||
license = license.LicenseController()
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
#
|
||||
# Copyright (c) 2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from sysinv.api.controllers.v1 import base
|
||||
from sysinv.api.controllers.v1 import collection
|
||||
from sysinv.common import utils as cutils
|
||||
from oslo_log import log
|
||||
from sysinv.openstack.common.gettextutils import _
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
LOCK_NAME = 'RegistryImageController'
|
||||
|
||||
|
||||
class RegistryImage(base.APIBase):
|
||||
"""API representation of a docker registry image"""
|
||||
|
||||
name = wtypes.text
|
||||
"The Docker image name"
|
||||
|
||||
tag = wtypes.text
|
||||
"The Docker image tag"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.fields = []
|
||||
|
||||
# set fields manually since Registry image comes from docker registry
|
||||
# and not sysinv database
|
||||
for fp in ['name', 'tag']:
|
||||
self.fields.append(fp)
|
||||
setattr(self, fp, kwargs.get(fp, None))
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, rpc_app, expand=True):
|
||||
app = RegistryImage(**rpc_app)
|
||||
if not expand:
|
||||
app.unset_fields_except(['name', 'tag'])
|
||||
|
||||
return app
|
||||
|
||||
|
||||
class RegistryImageCollection(collection.Collection):
|
||||
"""API representation of a collection of registry images."""
|
||||
|
||||
registry_images = [RegistryImage]
|
||||
"A list containing RegistryImage objects"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._type = 'registry_images'
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, rpc_apps, expand=False):
|
||||
collection = RegistryImageCollection()
|
||||
collection.registry_images = [RegistryImage.convert_with_links(n, expand)
|
||||
for n in rpc_apps]
|
||||
return collection
|
||||
|
||||
|
||||
class RegistryImageController(rest.RestController):
|
||||
"""REST controller for Docker registry image."""
|
||||
|
||||
@wsme_pecan.wsexpose(RegistryImageCollection, wtypes.text)
|
||||
def get_all(self, image_name=None):
|
||||
|
||||
# no image_name provided, list images
|
||||
if image_name is None:
|
||||
images = pecan.request.rpcapi.docker_registry_image_list(pecan.request.context)
|
||||
# image_name provided, list tags of provided image
|
||||
else:
|
||||
images = pecan.request.rpcapi.docker_registry_image_tags(pecan.request.context, image_name)
|
||||
return RegistryImageCollection.convert_with_links(images)
|
||||
|
||||
@cutils.synchronized(LOCK_NAME)
|
||||
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
|
||||
def delete(self, image_name_and_tag):
|
||||
"""Delete the image with the given name
|
||||
|
||||
:param name: image name and tag of the form name:tag
|
||||
"""
|
||||
|
||||
if len(image_name_and_tag.split(":")) != 2:
|
||||
raise wsme.exc.ClientSideError(_("Image name and tag must be of form name:tag"))
|
||||
|
||||
return pecan.request.rpcapi.docker_registry_image_delete(pecan.request.context, image_name_and_tag)
|
||||
|
||||
@cutils.synchronized(LOCK_NAME)
|
||||
@wsme_pecan.wsexpose(None, wtypes.text)
|
||||
def post(self, garbage_collect=None):
|
||||
"""Run the registry garbage collector"""
|
||||
if garbage_collect is not None:
|
||||
pecan.request.rpcapi.docker_registry_garbage_collect(pecan.request.context)
|
|
@ -1313,6 +1313,7 @@ MURANO_CERT_KEY_FILE = os.path.join(CERT_MURANO_DIR, CERT_KEY_FILE)
|
|||
MURANO_CERT_FILE = os.path.join(CERT_MURANO_DIR, CERT_FILE)
|
||||
MURANO_CERT_CA_FILE = os.path.join(CERT_MURANO_DIR, CERT_CA_FILE)
|
||||
|
||||
DOCKER_REGISTRY_PORT = '9001'
|
||||
DOCKER_REGISTRY_CERT_FILE = os.path.join(SSL_CERT_DIR, "registry-cert.crt")
|
||||
DOCKER_REGISTRY_KEY_FILE = os.path.join(SSL_CERT_DIR, "registry-cert.key")
|
||||
DOCKER_REGISTRY_PKCS1_KEY_FILE = os.path.join(SSL_CERT_DIR,
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
#
|
||||
# Copyright (c) 2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
import base64
|
||||
import keyring
|
||||
import requests
|
||||
|
||||
from sysinv.common import exception
|
||||
|
||||
CERT_PATH = '/etc/ssl/private/registry-cert.crt'
|
||||
KEYRING_SERVICE = 'CGCS'
|
||||
REGISTRY_USERNAME = 'admin'
|
||||
|
||||
|
||||
def get_registry_password():
|
||||
registry_password = keyring.get_password(
|
||||
KEYRING_SERVICE, REGISTRY_USERNAME)
|
||||
if not registry_password:
|
||||
raise exception.DockerRegistryCredentialNotFound(
|
||||
name=REGISTRY_USERNAME)
|
||||
return registry_password
|
||||
|
||||
|
||||
def docker_registry_authenticate(www_authenticate):
|
||||
"""
|
||||
returns a dictionary of headers to add as part of original request
|
||||
including access_token
|
||||
takes the Www-Authenticate header from the 401 response of a
|
||||
registry request
|
||||
like 'Bearer realm="https://192.168.204.2:9002/token/",
|
||||
service="192.168.204.2:9001",scope="registry:catalog:*"'
|
||||
|
||||
:param www_authenticate: a Www-Authenticate header as described above
|
||||
"""
|
||||
|
||||
# additional headers from the result of authentication
|
||||
# for example, access_token
|
||||
# send these along with the request to the docker registry
|
||||
auth_headers = {'connection': 'close'}
|
||||
|
||||
# take off the "Bearer"
|
||||
auth_params = www_authenticate.split(' ')
|
||||
# unsupported www_authenticate header
|
||||
if len(auth_params) != 2 or auth_params[0] != 'Bearer':
|
||||
return {}
|
||||
|
||||
auth_params = auth_params[1].split(',')
|
||||
# each auth_params should be an entry like
|
||||
# service="192.168.204.2:9001"
|
||||
for auth_param in auth_params:
|
||||
auth_param = auth_param.split('=')
|
||||
# we need to strip quotes from the auth challenge
|
||||
# if we send the "scope" field in quotes, we will get
|
||||
# "token intended for another audience" errors
|
||||
auth_headers[auth_param[0]] = auth_param[1].strip('\"')
|
||||
|
||||
# 'realm' specifies a token server to authenticate to
|
||||
if 'realm' not in auth_headers:
|
||||
return {}
|
||||
|
||||
# make a request to the token server
|
||||
# the credentials are passed as a header while the rest
|
||||
# are passed as params
|
||||
auth_string = base64.b64encode("%s:%s" % (REGISTRY_USERNAME, get_registry_password()))
|
||||
token_server_request_headers = {"authorization": "Basic %s" % auth_string}
|
||||
token_server_response = requests.get(auth_headers['realm'], verify=CERT_PATH,
|
||||
params=auth_headers,
|
||||
headers=token_server_request_headers)
|
||||
|
||||
if token_server_response.status_code == 200:
|
||||
auth_headers['Authorization'] = "Bearer %s" % token_server_response.json().get("access_token")
|
||||
|
||||
return auth_headers
|
||||
|
||||
|
||||
def docker_registry_get(path, registry_addr):
|
||||
# we need to have this header to get the correct digest when giving the tag
|
||||
headers = {"Accept": "application/vnd.docker.distribution.manifest.v2+json"}
|
||||
|
||||
resp = requests.get("%s%s" % (registry_addr, path), verify=CERT_PATH, headers=headers)
|
||||
|
||||
# authenticated registry, need to do auth with token server
|
||||
if resp.status_code == 401:
|
||||
auth_headers = docker_registry_authenticate(resp.headers["Www-Authenticate"])
|
||||
headers.update(auth_headers)
|
||||
resp = requests.get("%s%s" % (registry_addr, path), verify=CERT_PATH, headers=headers)
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
def docker_registry_delete(path, registry_addr):
|
||||
headers = {}
|
||||
|
||||
resp = requests.delete("%s%s" % (registry_addr, path), verify=CERT_PATH, headers=headers)
|
||||
|
||||
# authenticated registry, need to do auth with token server
|
||||
if resp.status_code == 401:
|
||||
auth_headers = docker_registry_authenticate(resp.headers["Www-Authenticate"])
|
||||
headers.update(auth_headers)
|
||||
resp = requests.delete("%s%s" % (registry_addr, path), verify=CERT_PATH, headers=headers)
|
||||
|
||||
return resp
|
|
@ -64,7 +64,6 @@ TARFILE_TRANSFER_CHUNK_SIZE = 1024 * 512
|
|||
DOCKER_REGISTRY_USER = 'admin'
|
||||
DOCKER_REGISTRY_SERVICE = 'CGCS'
|
||||
DOCKER_REGISTRY_SECRET = 'default-registry-key'
|
||||
DOCKER_REGISTRY_PORT = '9001'
|
||||
|
||||
|
||||
# Helper functions
|
||||
|
@ -1500,7 +1499,7 @@ class DockerHelper(object):
|
|||
cutils.format_address_name(constants.CONTROLLER_HOSTNAME,
|
||||
constants.NETWORK_TYPE_MGMT)
|
||||
).address
|
||||
registry_server = '{}:{}'.format(registry_ip, DOCKER_REGISTRY_PORT)
|
||||
registry_server = '{}:{}'.format(registry_ip, constants.DOCKER_REGISTRY_PORT)
|
||||
return registry_server
|
||||
|
||||
def _get_img_tag_with_registry(self, pub_img_tag):
|
||||
|
|
|
@ -85,6 +85,7 @@ from cephclient import wrapper as ceph
|
|||
from sysinv.conductor import ceph as iceph
|
||||
from sysinv.conductor import kube_app
|
||||
from sysinv.conductor import openstack
|
||||
from sysinv.conductor import docker_registry
|
||||
from sysinv.db import api as dbapi
|
||||
from sysinv.objects import base as objects_base
|
||||
from sysinv.objects import kube_app as kubeapp_obj
|
||||
|
@ -1421,6 +1422,104 @@ class ConductorManager(service.PeriodicService):
|
|||
}
|
||||
self._config_apply_runtime_manifest(context, config_uuid, config_dict)
|
||||
|
||||
def _get_docker_registry_addr(self):
|
||||
registry_ip = self.dbapi.address_get_by_name(
|
||||
cutils.format_address_name(constants.CONTROLLER_HOSTNAME,
|
||||
constants.NETWORK_TYPE_MGMT)
|
||||
).address
|
||||
registry_server = 'https://{}:{}/v2/'.format(
|
||||
registry_ip, constants.DOCKER_REGISTRY_PORT)
|
||||
return registry_server
|
||||
|
||||
def docker_registry_image_list(self, context):
|
||||
image_list_response = docker_registry.docker_registry_get(
|
||||
"_catalog", self._get_docker_registry_addr())
|
||||
|
||||
if image_list_response.status_code != 200:
|
||||
LOG.error("Bad response from docker registry: %s"
|
||||
% image_list_response.status_code)
|
||||
return []
|
||||
|
||||
image_list_response = image_list_response.json()
|
||||
images = []
|
||||
# responses from the registry looks like this
|
||||
# {u'repositories': [u'meliodas/satesatesate', ...]}
|
||||
# we need to turn that into what we want to return:
|
||||
# [{'name': u'meliodas/satesatesate'}]
|
||||
if 'repositories' not in image_list_response:
|
||||
return images
|
||||
|
||||
image_list_response = image_list_response['repositories']
|
||||
for image in image_list_response:
|
||||
images.append({'name': image})
|
||||
|
||||
return images
|
||||
|
||||
def docker_registry_image_tags(self, context, image_name):
|
||||
image_tags_response = docker_registry.docker_registry_get(
|
||||
"%s/tags/list" % image_name, self._get_docker_registry_addr())
|
||||
|
||||
if image_tags_response.status_code != 200:
|
||||
LOG.error("Bad response from docker registry: %s"
|
||||
% image_tags_response.status_code)
|
||||
return []
|
||||
|
||||
image_tags_response = image_tags_response.json()
|
||||
tags = []
|
||||
|
||||
if 'tags' not in image_tags_response:
|
||||
return tags
|
||||
|
||||
image_tags_response = image_tags_response['tags']
|
||||
# in the case where all tags of an image is deleted but not
|
||||
# garbage collected
|
||||
# the response will contain "tags:null"
|
||||
if image_tags_response is not None:
|
||||
for tag in image_tags_response:
|
||||
tags.append({'name': image_name, 'tag': tag})
|
||||
|
||||
return tags
|
||||
|
||||
# assumes image_name_and_tag is already error checked to contain "name:tag"
|
||||
def docker_registry_image_delete(self, context, image_name_and_tag):
|
||||
image_name_and_tag = image_name_and_tag.split(":")
|
||||
|
||||
# first get the image digest for the image name and tag provided
|
||||
digest_resp = docker_registry.docker_registry_get("%s/manifests/%s"
|
||||
% (image_name_and_tag[0], image_name_and_tag[1]),
|
||||
self._get_docker_registry_addr())
|
||||
|
||||
if digest_resp.status_code != 200:
|
||||
LOG.error("Bad response from docker registry: %s"
|
||||
% digest_resp.status_code)
|
||||
return
|
||||
|
||||
image_digest = digest_resp.headers['Docker-Content-Digest']
|
||||
|
||||
# now delete the image
|
||||
image_delete_response = docker_registry.docker_registry_delete(
|
||||
"%s/manifests/%s" % (image_name_and_tag[0], image_digest),
|
||||
self._get_docker_registry_addr())
|
||||
|
||||
if image_delete_response.status_code != 202:
|
||||
LOG.error("Bad response from docker registry: %s"
|
||||
% digest_resp.status_code)
|
||||
return
|
||||
|
||||
def docker_registry_garbage_collect(self, context):
|
||||
"""Run garbage collector"""
|
||||
active_controller = utils.HostHelper.get_active_controller(self.dbapi)
|
||||
personalities = [constants.CONTROLLER]
|
||||
config_uuid = self._config_update_hosts(context, personalities,
|
||||
[active_controller.uuid])
|
||||
|
||||
config_dict = {
|
||||
"personalities": personalities,
|
||||
"host_uuids": [active_controller.uuid],
|
||||
"classes": ['platform::dockerdistribution::garbagecollect']
|
||||
}
|
||||
self._config_apply_runtime_manifest(context, config_uuid, config_dict)
|
||||
|
||||
def get_magnum_cluster_count(self, context):
|
||||
return self._openstack.get_magnum_cluster_count()
|
||||
|
||||
|
|
|
@ -843,6 +843,39 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy):
|
|||
return self.call(context,
|
||||
self.make_msg('update_remotelogging_config'), timeout=timeout)
|
||||
|
||||
def docker_registry_image_list(self, context):
|
||||
"""Synchronously, request a list of images from Docker Registry API
|
||||
|
||||
:param context: request context.
|
||||
"""
|
||||
return self.call(context,
|
||||
self.make_msg('docker_registry_image_list'))
|
||||
|
||||
def docker_registry_image_tags(self, context, image_name):
|
||||
"""Synchronously, request a list of tags from Docker Registry API for a given image
|
||||
|
||||
:param context: request context.
|
||||
"""
|
||||
return self.call(context,
|
||||
self.make_msg('docker_registry_image_tags', image_name=image_name))
|
||||
|
||||
def docker_registry_image_delete(self, context, image_name_and_tag):
|
||||
"""Synchronously, delete the given image tag from the local docker registry
|
||||
|
||||
:param context: request context.
|
||||
"""
|
||||
return self.call(context,
|
||||
self.make_msg('docker_registry_image_delete',
|
||||
image_name_and_tag=image_name_and_tag))
|
||||
|
||||
def docker_registry_garbage_collect(self, context):
|
||||
"""Asynchronously, run the docker registry garbage collector
|
||||
|
||||
:param context: request context.
|
||||
"""
|
||||
return self.cast(context,
|
||||
self.make_msg('docker_registry_garbage_collect'))
|
||||
|
||||
def get_magnum_cluster_count(self, context):
|
||||
"""Synchronously, have the conductor get magnum cluster count
|
||||
configuration.
|
||||
|
|
Loading…
Reference in New Issue