diff --git a/Atomic/backends/_docker.py b/Atomic/backends/_docker.py index 769d2d78..72055c63 100644 --- a/Atomic/backends/_docker.py +++ b/Atomic/backends/_docker.py @@ -324,7 +324,9 @@ def delete_image(self, image, force=False): assert(image is not None) try: return self.d.remove_image(image, force=force) - except errors.NotFound: + except errors.APIError as e: + raise ValueError(str(e)) + except errors.NotFound: # pylint: disable=bad-except-order pass except HTTPError: pass @@ -364,6 +366,7 @@ def install(self, image, name, **kwargs): def uninstall(self, iobject, name=None, **kwargs): atomic = kwargs.get('atomic') + ignore = kwargs.get('ignore') assert(isinstance(atomic, Atomic)) args = atomic.args con_obj = None if not name else self.has_container(name) @@ -395,8 +398,12 @@ def uninstall(self, iobject, name=None, **kwargs): return 0 if cmd: return util.check_call(cmd, env=atomic.cmd_env()) + + # Delete the entry in the install data + util.InstallData.delete_by_id(iobject.id, ignore=ignore) return self.delete_image(iobject.image, force=args.force) + def validate_layer(self, layer): pass @@ -450,6 +457,10 @@ def add_string_or_list_to_list(list_item, value): else: return self._start(iobject, args, atomic) + if iobject.get_label('INSTALL') and not args.ignore and not util.InstallData.image_installed(iobject): + raise ValueError("The image '{}' appears to have not been installed and has an INSTALL label. You " + "should install this image first. Re-run with --ignore to bypass this " + "error.".format(iobject.name or iobject.image)) # The object is an image command = [] if iobject.run_command: diff --git a/Atomic/install.py b/Atomic/install.py index 855f1370..572f77d3 100644 --- a/Atomic/install.py +++ b/Atomic/install.py @@ -4,6 +4,8 @@ from .util import add_opt from .syscontainers import OSTREE_PRESENT from Atomic.backendutils import BackendUtils +from Atomic.discovery import RegistryInspectError +from time import gmtime, strftime try: from . import Atomic @@ -129,6 +131,15 @@ def install(self): self.display(cmd) if not self.args.display: + try: + name = img_obj.fq_name + except RegistryInspectError: + name = img_obj.input_name + install_data = {} + install_data[name] = {'id': img_obj.id, + 'install_date': strftime("%Y-%m-%d %H:%M:%S", gmtime()) + } + util.InstallData.write_install_data(install_data) return util.check_call(cmd) @staticmethod diff --git a/Atomic/uninstall.py b/Atomic/uninstall.py index 8493e904..88a03c96 100644 --- a/Atomic/uninstall.py +++ b/Atomic/uninstall.py @@ -59,7 +59,7 @@ def uninstall(self): if not img_obj: raise ValueError(e) be = ost - be.uninstall(img_obj, name=self.args.name, atomic=self) + be.uninstall(img_obj, name=self.args.name, atomic=self, ignore=self.args.ignore) return 0 diff --git a/Atomic/util.py b/Atomic/util.py index 797fdf69..9eb9f46c 100644 --- a/Atomic/util.py +++ b/Atomic/util.py @@ -21,6 +21,9 @@ import ipaddress import socket from Atomic.backends._docker_errors import NoDockerDaemon +import fcntl +import time + # Atomic Utility Module ReturnTuple = collections.namedtuple('ReturnTuple', @@ -29,6 +32,9 @@ ATOMIC_CONFD = os.environ.get('ATOMIC_CONFD', '/etc/atomic.d/') ATOMIC_LIBEXEC = os.environ.get('ATOMIC_LIBEXEC', '/usr/libexec/atomic') ATOMIC_VAR_LIB = os.environ.get('ATOMIC_VAR_LIB', '/var/lib/atomic') +if not os.path.exists(ATOMIC_VAR_LIB): + os.makedirs(ATOMIC_VAR_LIB) +ATOMIC_INSTALL_JSON = os.environ.get('ATOMIC_INSTALL_JSON', os.path.join(ATOMIC_VAR_LIB, 'install.json')) GOMTREE_PATH = "/usr/bin/gomtree" BWRAP_OCI_PATH = "/usr/bin/bwrap-oci" @@ -794,6 +800,117 @@ def load_scan_result_file(file_name): """ return json.loads(open(os.path.join(file_name), "r").read()) + +def file_lock(func): + lock_file_name = "{}.lock".format(os.path.join(os.path.dirname(ATOMIC_INSTALL_JSON), "." + os.path.basename(ATOMIC_INSTALL_JSON))) + + # Create the temporary lockfile if it doesn't exist + if not os.path.exists(lock_file_name): + open(lock_file_name, 'a').close() + + install_data_file = open(lock_file_name, "r") + + def get_lock(): + ''' + Obtains a read-only file lock on the install data + :return: + ''' + time_out = 0 + f_lock = False + while time_out < 10.5: # Ten second attempt to get a lock + try: + fcntl.flock(install_data_file, fcntl.LOCK_EX | fcntl.LOCK_NB) + f_lock = True + break + except IOError: + time.sleep(.5) + time_out += .5 + if not f_lock: + raise ValueError("Unable to get file lock for {}".format(ATOMIC_INSTALL_JSON)) + + def release_lock(): + fcntl.flock(install_data_file, fcntl.LOCK_UN) + + def wrapper(*args, **kwargs): + get_lock() + ret = func(*args, **kwargs) + release_lock() + return ret + + return wrapper + + +class InstallData(object): + if not os.path.exists(ATOMIC_INSTALL_JSON): + open(ATOMIC_INSTALL_JSON, 'a').close() + + install_file_handle = open(ATOMIC_INSTALL_JSON, 'r') + + @staticmethod + def _read_install_data(file_handle): + try: + return json.loads(file_handle.read()) + except ValueError: + return {} + + @classmethod + def _write_install_data(cls, new_data): + install_data = cls._read_install_data(cls.install_file_handle) + for x in new_data: + install_data[x] = new_data[x] + temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False) + json.dump(install_data, temp_file) + temp_file.close() + shutil.move(temp_file.name, ATOMIC_INSTALL_JSON) + + @classmethod + @file_lock + def read_install_data(cls): + if os.path.exists(ATOMIC_INSTALL_JSON): + read_data = cls._read_install_data(cls.install_file_handle) + return read_data + return {} + + @classmethod + @file_lock + def write_install_data(cls, new_data): + cls._write_install_data(new_data) + + @classmethod + def get_install_name_by_id(cls, iid, install_data=None): + if not install_data: + install_data = cls._read_install_data(cls.install_file_handle) + for installed_image in install_data: + if install_data[installed_image]['id'] == iid: + return installed_image + raise ValueError("Unable to find {} in installed image data ({}). Re-run command with -i to ignore".format(id, ATOMIC_INSTALL_JSON)) + + @classmethod + @file_lock + def delete_by_id(cls, iid, ignore=False): + install_data = cls._read_install_data(cls.install_file_handle) + try: + id_key = InstallData.get_install_name_by_id(iid, install_data=install_data) + except ValueError as e: + if not ignore: + raise ValueError(str(e)) + return + del install_data[id_key] + return cls._write_install_data(install_data) + + @classmethod + def image_installed(cls, img_object): + install_data = cls.read_install_data() + if install_data.get(img_object.id, None): + return True + if install_data.get(img_object.input_name, None): + return True + if install_data.get(img_object.name, None): + return True + if install_data.get(img_object.image, None): + return True + return False + class Decompose(object): """ Class for decomposing an input string in its respective parts like registry, diff --git a/atomic b/atomic index 6b0b3d46..04265113 100755 --- a/atomic +++ b/atomic @@ -124,6 +124,8 @@ def create_parser(help_text): help=_("show atomic version and exit")) parser.add_argument('--debug', default=False, action='store_true', help=_("show debug messages")) + parser.add_argument('-i', '--ignore', default=False, action='store_true', + help=_("ignore install-first requirement")) parser.add_argument('-y', '--assumeyes', default=False, action='store_true', help=_("automatically answer yes for all questions")) subparser = parser.add_subparsers(help=_("commands")) diff --git a/atomic_dbus.py b/atomic_dbus.py index 44b66915..b6f1f9ae 100755 --- a/atomic_dbus.py +++ b/atomic_dbus.py @@ -65,6 +65,7 @@ def __init__(self): self.graph = None self.heading = False self.hotfix = False + self.ignore = False self.image = None self.images = [] self.import_location = None @@ -392,8 +393,8 @@ def UnmountImage(self, mountpoint): # The Run method will run the specified image @slip.dbus.polkit.require_auth("org.atomic.readwrite") # Return a 0 or 1 for success. Errors result in exceptions. - @dbus.service.method("org.atomic", in_signature='ssbbas', out_signature='i') - def Run(self, image, name='', spc=False, detach=False, command=''): + @dbus.service.method("org.atomic", in_signature='ssbbbas', out_signature='i') + def Run(self, image, name='', spc=False, detach=False, ignore=False, command=''): r = Run() args = self.Args() args.image = image @@ -401,6 +402,7 @@ def Run(self, image, name='', spc=False, detach=False, command=''): args.spc = spc args.detach = detach args.command = command if command is not '' else [] + args.ignore = ignore r.set_args(args) try: return r.run() @@ -644,13 +646,14 @@ def TrustDefaultPolicy(self, default_policy): # atomic uninstall section # The Uninstall method will uninstall the specified image @slip.dbus.polkit.require_auth("org.atomic.readwrite") - @dbus.service.method("org.atomic", in_signature='ssbsas', out_signature='i') - def Uninstall(self, image, name, force, storage, extra_args): + @dbus.service.method("org.atomic", in_signature='ssbsbas', out_signature='i') + def Uninstall(self, image, name, force, storage, ignore, extra_args): i = Uninstall() args = self.Args() args.image = image args.name = name if name is not '' else None args.force = force + args.ignore = ignore args.storage = storage if storage is not '' else None args.extra_args = [] if not extra_args else extra_args i.set_args(args) diff --git a/atomic_dbus_client.py b/atomic_dbus_client.py index 52e6770f..4333febb 100755 --- a/atomic_dbus_client.py +++ b/atomic_dbus_client.py @@ -118,14 +118,14 @@ def MountImage(self, src, dest, options="", live=False, shared=False): # The Run method will create and run a container on the specified image @polkit.enable_proxy - def Run(self, image, name=None, spc=False, detach=False, command=None): + def Run(self, image, name=None, spc=False, detach=False, ignore=False, command=None): if not name: name = image if not command: command = [] if not isinstance(command, (list, tuple)): command = [ command ] - return self.dbus_object.Run(image, name, spc, detach, command, dbus_interface="org.atomic", timeout = 2147400) + return self.dbus_object.Run(image, name, spc, detach, ignore, command, dbus_interface="org.atomic", timeout = 2147400) @polkit.enable_proxy def Scan(self, scan_targets, scanner, scan_type, rootfs, _all, images, containers): @@ -167,14 +167,14 @@ def TrustShow(self): return self.dbus_object.TrustShow(dbus_interface="org.atomic") @polkit.enable_proxy - def Uninstall(self, image, name=None, force=False, storage=None, extra_args=None): + def Uninstall(self, image, name=None, force=False, storage=None, ignore=False, extra_args=None): if not name: name = image if not extra_args: extra_args = [] if not isinstance(extra_args, (list, tuple)): extra_args = [ extra_args ] - return self.dbus_object.Install(image, name=name, force=force, storage=storage, extra_args=extra_args, dbus_interface="org.atomic", timeout = 2147400) + return self.dbus_object.Uninstall(image, name, force, storage, ignore, extra_args, dbus_interface="org.atomic", timeout = 2147400) @polkit.enable_proxy def UnmountImage(self, dest): diff --git a/bash/atomic b/bash/atomic index ec10780a..ae90d669 100644 --- a/bash/atomic +++ b/bash/atomic @@ -43,7 +43,7 @@ __atomic_scanners() { _atomic_atomic() { local boolean_options=" - --help -h --version -v --debug --assumeyes -y + --help -h --version -v --debug --assumeyes -y -i --ignore " case "$prev" in $main_options_with_args_glob ) diff --git a/test.sh b/test.sh index 987b939b..9a7df69c 100755 --- a/test.sh +++ b/test.sh @@ -142,6 +142,11 @@ if [ ! -n "${PYTHON+ }" ]; then fi fi +# Add images with INSTALL labels to /var/lib/atomic/install.json +INSTALL_DATA_FILE="$(pwd)/install.json" +INSTALL_DATA=`docker images --no-trunc | awk '/atomic-test-/ {printf "\"%s\": {\"install_id\": \"%s\"},\n", $1, $3}' | sed 's/sha256://g' | sed '$ s/,$//'` +echo "{$INSTALL_DATA}" > ${INSTALL_DATA_FILE} +export ATOMIC_INSTALL_JSON=$INSTALL_DATA_FILE echo "UNIT TESTS:" diff --git a/tests/integration/test_dbus.py b/tests/integration/test_dbus.py index 729dd2d4..23afcf63 100755 --- a/tests/integration/test_dbus.py +++ b/tests/integration/test_dbus.py @@ -105,11 +105,12 @@ def test_pull_already_present(self): @integration_test def test_run(self): - self.dbus_object.Run('atomic-test-3', name='atomic-dbus-3') + self.dbus_object.Run('atomic-test-3', 'atomic-dbus-3', False, False, True) self.cid = TestDBus.run_cmd('docker ps -aq -l').decode('utf-8').rstrip() TestDBus.add_cleanup_cmd('docker rm {}'.format(self.cid)) container_inspect = json.loads(TestDBus.run_cmd('docker inspect {}'.format(self.cid)).decode('utf-8'))[0] - assert(container_inspect['Name'] == '/atomic-test-3') + print(container_inspect) + assert(container_inspect['Name'] == '/atomic-dbus-3') @integration_test def test_container_delete(self): @@ -140,7 +141,7 @@ def test_install(self): @integration_test def test_uninstall(self): - results = self.dbus_object.Uninstall('dbus-test-3', '', True, '', '') + results = self.dbus_object.Uninstall('dbus-test-3', '', True, '', True, '') TestDBus.remove_cleanup_cmd('docker rm {}'.format(self.cid)) TestDBus.remove_cleanup_cmd('docker rmi atomic-dbus-3') try: diff --git a/tests/unit/test_util.py b/tests/unit/test_util.py index d552cbd9..ea40a8b7 100644 --- a/tests/unit/test_util.py +++ b/tests/unit/test_util.py @@ -1,7 +1,22 @@ +#pylint: skip-file import unittest import selinux import sys from Atomic import util +from Atomic.backends._docker import DockerBackend +import time + +no_mock = True +try: + from unittest.mock import MagicMock, patch + no_mock = False +except ImportError: + try: + from mock import MagicMock, patch + no_mock = False + except ImportError: + # Mock is already set to False + pass def _new_enough(): py_version = sys.version_info @@ -11,6 +26,20 @@ def _new_enough(): new_enough = _new_enough() +if no_mock: + # If there is no mock, we need need to create a fake + # patch decorator + def fake_patch(a, new=''): + def foo(func): + def wrapper(*args, **kwargs): + ret = func(*args, **kwargs) + return ret + return wrapper + return foo + + patch = fake_patch + + class TestAtomicUtil(unittest.TestCase): def test_image_by_name(self): @@ -102,5 +131,117 @@ def test_valid_uri(self): exception_raised = True self.assertTrue(exception_raised) + +class MockIO(object): + original_data = {"install_test": {"install_date": "2017-03-22 17:19:41", "id": "49779293ca711789a77bbdc35547a6b9ecb193a51b4e360fea95c4d206605d18"}} + new_data_fq = {"install_date": "2017-04-22 17:19:41","id": "16e9fdecc1febc87fb1ca09271009cf5f28eb8d4aec5515922ef298c145a6726"} + new_data_name= {"install_date": "2017-04-22 17:19:41","id": "16e9fdecc1febc87fb1ca09271009cf5f28eb8d4aec5515922ef298c145a6726"} + install_data = original_data + + @classmethod + def read_mock(cls): + return cls.install_data + + @classmethod + def write_mock(cls, val): + cls.install_data = val + + @classmethod + def reset_data(cls): + cls.install_data = {} + cls.install_data = cls.original_data + + @classmethod + def grow_data(cls, var_name, name): + cls.install_data[name] = getattr(cls, var_name) + +local_centos_inspect = {'Id': '16e9fdecc1febc87fb1ca09271009cf5f28eb8d4aec5515922ef298c145a6726', 'RepoDigests': ['docker.io/centos@sha256:7793b39617b28c6cd35774c00383b89a1265f3abf6efcaf0b8f4aafe4e0662d2'], 'Parent': '', 'GraphDriver': {'Name': 'devicemapper', 'Data': {'DeviceSize': '10737418240', 'DeviceName': 'docker-253:2-5900125-3fb5b406e6a53142129237c9e2c3a1ce8b6cf269b5f8071fcd62107c41544cd2', 'DeviceId': '779'}}, 'Created': '2016-08-30T18:20:19.39890162Z', 'Comment': '', 'DockerVersion': '1.12.1', 'VirtualSize': 210208812, 'Author': 'The CentOS Project - ami_creator', 'Os': 'linux', 'RootFS': {'Type': 'layers', 'Layers': ['5fa0fa02637842ab1ddc8b3a17b86691c87c87d20800e6a95a113343f6ffd84c']}, 'Container': 'a5b0819aa82c224095e1a18e9df0776a7b38d32bacca073f054723b65fb54f0e', 'Architecture': 'amd64', 'RepoTags': ['docker.io/centos:centos7.0.1406'], 'Config': {'Labels': {}, 'Entrypoint': None, 'StdinOnce': False, 'OnBuild': None, 'Env': ['PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'], 'Volumes': None, 'Cmd': None, 'User': '', 'AttachStdin': False, 'AttachStderr': False, 'AttachStdout': False, 'WorkingDir': '', 'Tty': False, 'Image': '20ae10d641a0af6f25ceaa75fdcf591d171e3c521a54a3f3a2868b602d735e11', 'Hostname': 'a5b0819aa82c', 'Domainname': '', 'OpenStdin': False}, 'Size': 210208812, 'ContainerConfig': {'Labels': {}, 'Entrypoint': None, 'StdinOnce': False, 'OnBuild': None, 'Env': ['PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'], 'Volumes': None, 'Cmd': ['/bin/sh', '-c', '#(nop) ADD file:6a409eac27f0c7e04393da096dbeff01b929405e79b15222a0dc06a2084d3df3 in / '], 'User': '', 'AttachStdin': False, 'AttachStderr': False, 'AttachStdout': False, 'WorkingDir': '', 'Tty': False, 'Image': '20ae10d641a0af6f25ceaa75fdcf591d171e3c521a54a3f3a2868b602d735e11', 'Hostname': 'a5b0819aa82c', 'Domainname': '', 'OpenStdin': False}} +rhel_docker_inspect = {u'Comment': u'', u'Container': u'', u'DockerVersion': u'1.9.1', u'Parent': u'', u'Created': u'2016-10-26T12:02:33.368772Z', u'Config': {u'Tty': False, u'Cmd': [u'/bin/bash'], u'Volumes': None, u'Domainname': u'', u'WorkingDir': u'', u'Image': u'f6f6121b053b2312688c87d3a1d32d06a984dc01d2ea7738508a50581cddb6b4', u'Hostname': u'', u'StdinOnce': False, u'Labels': {u'com.redhat.component': u'rhel-server-docker', u'authoritative-source-url': u'registry.access.redhat.com', u'distribution-scope': u'public', u'Vendor': u'Red Hat, Inc.', u'Name': u'rhel7/rhel', u'Build_Host': u'rcm-img01.build.eng.bos.redhat.com', u'vcs-type': u'git', u'name': u'rhel7/rhel', u'vcs-ref': u'7eeaf203cf909c2c056fba7066db9c1073a28d97', u'release': u'45', u'Version': u'7.3', u'Architecture': u'x86_64', u'version': u'7.3', u'Release': u'45', u'vendor': u'Red Hat, Inc.', u'BZComponent': u'rhel-server-docker', u'build-date': u'2016-10-26T07:54:17.037911Z', u'com.redhat.build-host': u'ip-10-29-120-48.ec2.internal', u'architecture': u'x86_64'}, u'AttachStdin': False, u'User': u'', u'Env': [u'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', u'container=docker'], u'Entrypoint': None, u'OnBuild': [], u'AttachStderr': False, u'AttachStdout': False, u'OpenStdin': False}, u'Author': u'Red Hat, Inc.', u'GraphDriver': {u'Data': {u'DeviceName': u'docker-253:2-5900125-a2bce97a4fd7ea12dce9865caa461ead8d1caf51ef452aba2f1b9d98efdf968f', u'DeviceSize': u'10737418240', u'DeviceId': u'623'}, u'Name': u'devicemapper'}, u'VirtualSize': 192508958, u'Os': u'linux', u'Architecture': u'amd64', u'RootFS': {u'Layers': [u'34d3e0e77091d9d51c6f70a7a7a4f7536aab214a55e02a8923af8f80cbe60d30', u'ccd6fc81ec49bd45f04db699401eb149b1945bb7292476b390ebdcdd7d975697'], u'Type': u'layers'}, u'ContainerConfig': {u'Tty': False, u'Cmd': None, u'Volumes': None, u'Domainname': u'', u'WorkingDir': u'', u'Image': u'', u'Hostname': u'', u'StdinOnce': False, u'Labels': None, u'AttachStdin': False, u'User': u'', u'Env': None, u'Entrypoint': None, u'OnBuild': None, u'AttachStderr': False, u'AttachStdout': False, u'OpenStdin': False}, u'Size': 192508958, u'RepoDigests': [u'registry.access.redhat.com/rhel7@sha256:da8a3e9297da7ccd1948366103d13c45b7e77489382351a777a7326004b63a21'], u'Id': u'f98706e16e41e56c4beaeea9fa77cd00fe35693635ed274f128876713afc0a1e', u'RepoTags': [u'registry.access.redhat.com/rhel7:latest']} + + +@unittest.skipIf(no_mock, "Mock not found") +class InstallData(unittest.TestCase): + + class Args(): + def __init__(self): + self.storage = None + self.debug = False + self.name = None + self.image = None + + @patch('Atomic.util.InstallData.read_install_data', new=MockIO.read_mock) + @patch('Atomic.util.InstallData._read_install_data', new=MockIO.read_mock) + @patch('Atomic.util.InstallData.write_install_data', new=MockIO.write_mock) + def test_read(self): + MockIO.reset_data() + self.assertEqual(util.InstallData.read_install_data(), MockIO.install_data) + + @patch('Atomic.util.InstallData.read_install_data', new=MockIO.read_mock) + @patch('Atomic.util.InstallData._read_install_data', new=MockIO.read_mock) + @patch('Atomic.util.InstallData.write_install_data', new=MockIO.write_mock) + def test_write(self): + MockIO.reset_data() + install_data = util.InstallData.read_install_data() + install_data['docker.io/library/centos:latest'] = MockIO.new_data_fq + util.InstallData.write_install_data(install_data) + self.assertTrue('docker.io/library/centos:latest' in util.InstallData.read_install_data()) + + @patch('Atomic.util.InstallData.read_install_data', new=MockIO.read_mock) + @patch('Atomic.util.InstallData._read_install_data', new=MockIO.read_mock) + @patch('Atomic.util.InstallData.write_install_data', new=MockIO.write_mock) + def test_get_install_name_by_id(self): + MockIO.reset_data() + MockIO.grow_data('new_data_fq', 'docker.io/library/centos:latest') + self.assertEqual(util.InstallData.get_install_name_by_id('16e9fdecc1febc87fb1ca09271009cf5f28eb8d4aec5515922ef298c145a6726', install_data=MockIO.install_data), 'docker.io/library/centos:latest') + + @patch('Atomic.util.InstallData.read_install_data', new=MockIO.read_mock) + @patch('Atomic.util.InstallData._read_install_data', new=MockIO.read_mock) + @patch('Atomic.util.InstallData.write_install_data', new=MockIO.write_mock) + def test_fail_get_install_name_by_id(self): + MockIO.reset_data() + self.assertRaises(ValueError, util.InstallData.get_install_name_by_id, 1, MockIO.install_data) + + @patch('Atomic.util.InstallData.read_install_data', new=MockIO.read_mock) + @patch('Atomic.util.InstallData._read_install_data', new=MockIO.read_mock) + @patch('Atomic.util.InstallData.write_install_data', new=MockIO.write_mock) + def test_image_installed_name(self): + MockIO.reset_data() + MockIO.grow_data('new_data_fq', 'docker.io/library/centos:latest') + args = self.Args() + args.storage = 'docker' + args.image = 'docker.io/library/centos:latest' + db = DockerBackend() + db._inspect_image = MagicMock(return_value=local_centos_inspect) + local_image_object = db.inspect_image(args.image) + self.assertTrue(util.InstallData.image_installed(local_image_object)) + + @patch('Atomic.util.InstallData.read_install_data', new=MockIO.read_mock) + @patch('Atomic.util.InstallData._read_install_data', new=MockIO.read_mock) + @patch('Atomic.util.InstallData.write_install_data', new=MockIO.write_mock) + def test_image_installed_id(self): + MockIO.reset_data() + MockIO.grow_data('new_data_fq', '16e9fdecc1febc87fb1ca09271009cf5f28eb8d4aec5515922ef298c145a6726') + args = self.Args() + args.storage = 'docker' + args.image = 'docker.io/library/centos:latest' + db = DockerBackend() + db._inspect_image = MagicMock(return_value=local_centos_inspect) + local_image_object = db.inspect_image(args.image) + self.assertTrue(util.InstallData.image_installed(local_image_object)) + + @patch('Atomic.util.InstallData.read_install_data', new=MockIO.read_mock) + @patch('Atomic.util.InstallData._read_install_data', new=MockIO.read_mock) + @patch('Atomic.util.InstallData.write_install_data', new=MockIO.write_mock) + def test_image_not_installed(self): + MockIO.reset_data() + args = self.Args() + args.storage = 'docker' + args.image = 'registry.access.redhat.com/rhel7' + db = DockerBackend() + db._inspect_image = MagicMock(return_value=rhel_docker_inspect) + local_image_object = db.inspect_image(args.image) + self.assertFalse(util.InstallData.image_installed(local_image_object)) + + if __name__ == '__main__': unittest.main()