1
0
mirror of https://github.com/projectatomic/atomic.git synced 2026-02-05 18:45:01 +01:00

Disconnect backends

Ideally, the atomic CLI should be able to operate independently
of the backends it supports.  For example, if dockerd is inactive,
the ostree backend and atomic cli should still work.

This requires some tweaking to the backendutils code and the work
flow.  We also need to specifically know if the user passes
--storage so that we treat that as an explicit override.  The work
flow is now roughly:

* a default storage can be defined in atomic.conf (was always this way)
* if not defined, defaults to docker.
* if --storage is passed, treat explictly and fail if cannot execute
* if no --storage is specified, use default.  if default is not available, move
 onto the next backend.
This commit is contained in:
Brent Baude
2017-01-23 15:25:38 -06:00
parent 17710ad45e
commit f79d56bd13
16 changed files with 115 additions and 36 deletions

View File

@@ -22,7 +22,14 @@ class DockerBackend(Backend):
def __init__(self):
self.input = None
self._d = None
self._ping()
@property
def available(self):
try:
_ = self.d
return True
except util.NoDockerDaemon:
return False
@property
def d(self):

View File

@@ -24,6 +24,9 @@ class OSTreeBackend(Backend):
def backend(self):
return "ostree"
def available(self):
return self.syscontainers.available
def _make_container(self, info):
container_id = info['Id']
runtime = self.syscontainers.get_container_runtime_info(container_id)

View File

@@ -144,3 +144,8 @@ class Backend(object): #pylint: disable=metaclass-assignment
pass
@abstractproperty
def available(self):
pass

View File

@@ -1,6 +1,9 @@
from Atomic.backends._docker import DockerBackend
from Atomic.backends._ostree import OSTreeBackend
from Atomic.util import write_out, get_atomic_config
ATOMIC_CONFIG = get_atomic_config()
default_storage = ATOMIC_CONFIG.get('default_storage', "docker")
class BackendUtils(object):
"""
@@ -9,13 +12,40 @@ class BackendUtils(object):
BACKENDS = [DockerBackend, OSTreeBackend]
def get_backend_from_string(self, str_backend):
@property
def available_backends(self):
return self._set_available_backends()
def _set_available_backends(self):
bes = []
for x in self.BACKENDS:
be = x()
if be.available:
bes.append(x)
if len(bes) < 1:
raise ValueError("No backends are enabled for Atomic.")
return bes
def dump_backends(self):
backends = ''
for i in self.available_backends:
be = i()
backends += "{}: Active, ".format(be.backend)
write_out("Backends({})\n".format(backends))
def get_backend_from_string(self, str_backend, init=True):
for _backend in self.BACKENDS:
backend = _backend
backend_obj = _backend()
if backend_obj.backend == str_backend:
return backend_obj
if init:
return backend_obj
return backend
raise ValueError("Unable to associate string '{}' with backend".format(str_backend))
def _get_backend(self, backend):
return self.get_backend_from_string(backend, init=False)
def _get_backend_index_from_string(self, str_backend):
return [x().backend for x in self.BACKENDS].index(str_backend)
@@ -27,7 +57,7 @@ class BackendUtils(object):
def backend_has_container(backend, container):
return True if backend.has_container(container) else False
def get_backend_and_image_obj(self, img, str_preferred_backend=None):
def get_backend_and_image_obj(self, img, str_preferred_backend=None, required=False):
"""
Given an image name (str) and optionally a str reference to a backend,
this method looks for the image firstly on the preferred backend and
@@ -37,14 +67,18 @@ class BackendUtils(object):
:param str_preferred_backend: i.e. 'docker'
:return: backend object and image object
"""
backends = list(self.BACKENDS)
backends = list(self.available_backends)
if str_preferred_backend and self._get_backend(str_preferred_backend) not in self.available_backends and required:
raise ValueError("The '{}' backend appears unavailable/inactive".format(str_preferred_backend))
# Check preferred backend first
if str_preferred_backend:
if str_preferred_backend and self._get_backend(str_preferred_backend) in self.available_backends:
be = self.get_backend_from_string(str_preferred_backend)
img_obj = be.has_image(img)
if img_obj:
return be, img_obj
if required:
raise ValueError("Unable to find {} in the {} backend".format(img, str_preferred_backend))
# Didnt find in preferred, need to remove it from the list now
del backends[self._get_backend_index_from_string(str_preferred_backend)]
@@ -59,11 +93,11 @@ class BackendUtils(object):
if len(img_in_backends) == 1:
return img_in_backends[0]
if len(img_in_backends) == 0:
raise ValueError("Unable to find backend associated with image '{}'".format(img))
raise ValueError("Unable to find '{}' in the following backends: {}".format(img, ", ".join([x().backend for x in self.available_backends])))
raise ValueError("Found {} in multiple storage backends: {}".
format(img, ', '.join([x.backend for x in img_in_backends])))
format(img, ', '.join([x.backend for x, _ in img_in_backends])))
def get_backend_and_container_obj(self, container_name, str_preferred_backend=None):
def get_backend_and_container_obj(self, container_name, str_preferred_backend=None, required=False):
"""
Given a container name (str) and optionally a str reference to a backend,
this method looks for the container firstly on the preferred backend and
@@ -73,13 +107,19 @@ class BackendUtils(object):
:param str_preferred_backend: i.e. 'docker'
:return: backend object and container object
"""
backends = list(self.BACKENDS)
if str_preferred_backend and self._get_backend(str_preferred_backend) not in self.available_backends and required:
raise ValueError("The '{}' backend appears unavailable/inactive".format(str_preferred_backend))
backends = list(self.available_backends)
# Check preferred backend first
if str_preferred_backend:
if str_preferred_backend and self._get_backend(str_preferred_backend) in self.available_backends:
be = self.get_backend_from_string(str_preferred_backend)
con_obj = be.has_container(container_name)
if con_obj:
return be, con_obj
if required:
raise ValueError("Unable to find {} in the {} backend".format(container_name, str_preferred_backend))
# Didnt find in preferred, need to remove it from the list now
del backends[self._get_backend_index_from_string(str_preferred_backend)]
@@ -97,7 +137,7 @@ class BackendUtils(object):
format(container_name, ', '.join([x.backend for x in container_in_backends])))
def get_images(self, get_all=False):
backends = self.BACKENDS
backends = self.available_backends
img_objs = []
for backend in backends:
be = backend()
@@ -105,7 +145,7 @@ class BackendUtils(object):
return img_objs
def get_containers(self):
backends = self.BACKENDS
backends = self.available_backends
con_objs = []
for backend in backends:
be = backend()

View File

@@ -33,6 +33,10 @@ def cli(subparser):
delete_parser.add_argument("-a", "--all", action='store_true',dest="all",
default=False,
help=_("Delete all containers"))
delete_parser.add_argument("--storage", default=None, dest="storage",
help=_("Specify the storage from which to delete the container from. "
"If not specified and there are containers with the same name in "
"different storages, you will be prompted to specify."))
delete_parser.add_argument("containers", nargs='*',
help=_("Specify one or more containers. Must be final arguments."))
delete_parser.set_defaults(_class=Containers, func='delete')
@@ -86,6 +90,10 @@ class Containers(Atomic):
FILTER_KEYWORDS= {"container": "id", "image": "image_name", "command": "command",
"created": "created", "state": "state", "runtime": "runtime", "backend" : "backend.backend"}
def __init__(self):
super(Containers, self).__init__()
self.beu = BackendUtils()
def fstrim(self):
with AtomicDocker() as client:
for container in client.containers():
@@ -120,6 +128,8 @@ class Containers(Atomic):
def ps_tty(self):
if self.args.debug:
util.write_out(str(self.args))
self.beu.dump_backends()
container_objects = self._ps()
if not any([x.running for x in container_objects]) and not self.args.all:
@@ -179,8 +189,7 @@ class Containers(Atomic):
raise ValueError("The filter {} is not valid. "
"Please choose from {}".format(_filter, [x for x in self.FILTER_KEYWORDS]))
_check_filters()
beu = BackendUtils()
containers = self.filter_container_objects(beu.get_containers())
containers = self.filter_container_objects(self.beu.get_containers())
self._mark_vulnerable(containers)
if self.args.all:
return containers
@@ -207,17 +216,17 @@ class Containers(Atomic):
def delete(self):
if self.args.debug:
util.write_out(str(self.args))
self.beu.dump_backends()
if len(self.args.containers) > 0 and self.args.all:
raise ValueError("You must select --all or provide a list of containers to delete.")
beu = BackendUtils()
if self.args.all:
container_objects = beu.get_containers()
container_objects = self.beu.get_containers()
else:
container_objects = []
for con in self.args.containers:
_, con_obj = beu.get_backend_and_container_obj(con, str_preferred_backend=storage)
_, con_obj = self.beu.get_backend_and_container_obj(con, str_preferred_backend=self.args.storage or storage, required=True if self.args.storage else False)
container_objects.append(con_obj)
four_col = " {0:12} {1:20} {2:25} {3:10}"

View File

@@ -3,6 +3,9 @@ from . import util
import sys
from Atomic.backendutils import BackendUtils
ATOMIC_CONFIG = util.get_atomic_config()
storage = ATOMIC_CONFIG.get('default_storage', "docker")
class Delete(Atomic):
def __init__(self):
super(Delete, self).__init__()
@@ -31,7 +34,7 @@ class Delete(Atomic):
delete_objects = beu.get_images(get_all=True)
else:
for image in self.args.delete_targets:
_, img_obj = beu.get_backend_and_image_obj(image, str_preferred_backend=self.args.storage)
_, img_obj = beu.get_backend_and_image_obj(image, str_preferred_backend=self.args.storage or storage, required=True if self.args.storage else False)
delete_objects.append(img_obj)
if self.args.remote:

View File

@@ -55,7 +55,7 @@ class RegistryInspect():
fqdn += ":{}".format(self.tag)
return fqdn
def find_image_on_registry(self):
def find_image_on_registry(self, quiet=False):
"""
Find the fully qualified image name for given input when
registry is unknown
@@ -68,13 +68,15 @@ class RegistryInspect():
registries = [i['name'] for i in [x for x in self.registries if x['search']]]
for registry in registries:
fqdn = self.assemble_fqdn(registry=registry, include_tag=True)
util.write_out("Trying {}...".format(fqdn))
if not quiet:
util.write_out("Trying {}...".format(fqdn))
try:
result = util.skopeo_inspect("docker://{}".format(fqdn), return_json=True)
self._remote_inspect = result
return fqdn
except ValueError as e:
util.write_err("Failed: {}".format(e))
if not quiet:
util.write_err("Failed: {}".format(e))
continue
raise RegistryInspectError("Unable to resolve {}".format(self.orig_input))

View File

@@ -49,7 +49,7 @@ def cli(subparser):
action="store_true",
help=_("Delete image from remote repository"))
delete_parser.add_argument("--storage", default=storage, dest="storage",
delete_parser.add_argument("--storage", default=None, dest="storage",
help=_("Specify the storage from which to delete the image from. "
"If not specified and there are images with the same name in "
"different storages, you will be prompted to specify."))
@@ -137,6 +137,7 @@ class Images(Atomic):
if self.args.debug:
util.write_out(str(self.args))
self.be_utils.dump_backends()
_images = self._get_images()
for i in _images:

View File

@@ -34,7 +34,7 @@ def cli(subparser, hidden=False):
infop.add_argument("--remote", dest="force",
action='store_true', default=False,
help=_('ignore local images and only scan registries'))
infop.add_argument("--storage", default=storage, dest="storage",
infop.add_argument("--storage", default=None, dest="storage",
help=_("Specify the storage of the image. "
"If not specified and there are images with the same name in "
"different storages, you will be prompted to specify."))
@@ -53,7 +53,7 @@ def cli_version(subparser, hidden=False):
versionp.add_argument("-r", "--recurse", default=False, dest="recurse",
action="store_true",
help=_("recurse through all layers"))
versionp.add_argument("--storage", default=storage, dest="storage",
versionp.add_argument("--storage", default=None, dest="storage",
help=_("Specify the storage of the image. "
"If not specified and there are images with the same name in "
"different storages, you will be prompted to specify."))
@@ -87,7 +87,7 @@ class Info(Atomic):
write_func("")
def get_layer_objects(self):
_, img_obj = self.beu.get_backend_and_image_obj(self.image, str_preferred_backend=self.args.storage)
_, img_obj = self.beu.get_backend_and_image_obj(self.image, str_preferred_backend=self.args.storage or storage, required=True if self.args.storage else False)
return img_obj.layers
def dbus_version(self):
@@ -117,7 +117,7 @@ class Info(Atomic):
img_obj = be.make_remote_image(self.image)
else:
# The image is local
be, img_obj = self.beu.get_backend_and_image_obj(self.image, str_preferred_backend=self.args.storage)
be, img_obj = self.beu.get_backend_and_image_obj(self.image, str_preferred_backend=self.args.storage or storage, required=True if self.args.storage else False)
with closing(StringIO()) as buf:
try:
@@ -125,7 +125,8 @@ class Info(Atomic):
except RegistryInspectError:
info_name = img_obj.input_name
buf.write("Image Name: {}\n".format(info_name))
buf.writelines(sorted(["{}: {}\n".format(k, v) for k,v in list(img_obj.labels.items())]))
if img_obj.labels:
buf.writelines(sorted(["{}: {}\n".format(k, v) for k,v in list(img_obj.labels.items())]))
if img_obj.template_variables_set:
buf.write("\n\nTemplate variables with default value, but overridable with --set:\n")
buf.writelines(["{}: {}\n".format(k, v) for k,v in

View File

@@ -117,7 +117,7 @@ class Image(object):
if not self.registry:
ri = RegistryInspect(registry=self.registry, repo=self.repo, image=self.image,
tag=self.tag, orig_input=self.input_name)
self._fq_name = ri.find_image_on_registry()
self._fq_name = ri.find_image_on_registry(quiet=True)
propagate(self._fq_name)
return self._fq_name

View File

@@ -89,7 +89,7 @@ class Run(Atomic):
db.pull_image(self.image)
img_object = db.has_image(self.image)
except RegistryInspectError:
util.write_err("Unable to find image {}".format(self.image))
raise ValueError("Unable to find image {}".format(self.image))
db.run(img_object, atomic=self, args=self.args)

View File

@@ -84,6 +84,10 @@ class SystemContainers(object):
stdout=DEVNULL,
stderr=DEVNULL)
@property
def available(self):
return OSTREE_PRESENT
def _checkout_layer(self, repo, rootfs_fd, rootfs, rev):
# ostree 2016.8 has a glib introspection safe API for checkout, use it
# when available.

View File

@@ -25,7 +25,7 @@ def cli(subparser, hidden=False):
updatep.add_argument("-f", "--force", default=False, dest="force",
action="store_true",
help=_("remove all containers based on this image"))
updatep.add_argument("--storage", default=storage, dest="storage",
updatep.add_argument("--storage", default=None, dest="storage",
help=_("Specify the storage of the image. Defaults to: %s" % storage))
updatep.add_argument("image", help=_("container image"))
@@ -38,7 +38,7 @@ class Update(Atomic):
write_out(str(self.args))
beu = BackendUtils()
try:
be, img_obj = beu.get_backend_and_image_obj(self.image, str_preferred_backend=self.args.storage)
be, img_obj = beu.get_backend_and_image_obj(self.image, str_preferred_backend=self.args.storage or storage, required=True if self.args.storage else False)
input_name = img_obj.input_name
except ValueError:
raise ValueError("{} not found locally. Unable to update".format(self.image))

View File

@@ -57,7 +57,10 @@ input, is_python2 = check_if_python2() # pylint: disable=redefined-builtin
def get_registries():
registries = []
with AtomicDocker() as c:
dconf = c.info()
try:
dconf = c.info()
except requests.exceptions.ConnectionError:
raise ValueError("This Atomic function requires an active docker daemon.")
search_regs = [x['Name'] for x in dconf['Registries']]
rconf = dconf['RegistryConfig']['IndexConfigs']
# docker.io is special

View File

@@ -33,7 +33,7 @@ def cli(subparser, hidden=False):
verifyp.add_argument("--no-validate", default=False, dest="no_validate",
action="store_true",
help=_("disable validating system images"))
verifyp.add_argument("--storage", default=storage, dest="storage",
verifyp.add_argument("--storage", default=None, dest="storage",
help=_("Specify the storage of the image. "
"If not specified and there are images with the same name in "
"different storages, you will be prompted to specify."))
@@ -86,7 +86,7 @@ class Verify(Atomic):
return layers
def _verify(self):
be, img_obj = self.backend_utils.get_backend_and_image_obj(self.image, self.args.storage)
be, img_obj = self.backend_utils.get_backend_and_image_obj(self.image, str_preferred_backend=self.args.storage or storage, required=True if self.args.storage else False)
remote_img_name = "{}:latest".format(util.Decompose(img_obj.fq_name).no_tag)
remote_img_obj = be.make_remote_image(remote_img_name)
return img_obj.layers, remote_img_obj.layers

View File

@@ -483,6 +483,7 @@ _atomic_containers_delete() {
local all_options="$options_with_args
--all -a
--force -f
--storage
--help
"