class DockerClient(object): def __init__(self, base_url): self.client = APIClient(base_url=base_url, version='auto', timeout=30) self.auth_config = { 'username': app.config['DOCKER_REGISTRY_AUTH'].get('username'), 'password': app.config['DOCKER_REGISTRY_AUTH'].get('password') } def __repr__(self): return '<DockerClient %r>' % self.client.base_url def __del__(self): self.client.close() @property def api_version(self): return self.client.api_version def docker_info(self): return self.client.info() def pull_image(self, image, tag, stream=False): if stream: return self.client.pull(image, tag, auth_config=self.auth_config, stream=True) rst = self.client.pull(image, tag, auth_config=self.auth_config) last_message = json.loads(rst.split('\r\n')[-2]) if last_message.get('error'): raise APIError(last_message['error']) def prune_images(self, filters=None): return self.client.prune_images(filters=filters)
class TestSystem(unittest.TestCase): podman = None # initialized podman configuration for tests service = None # podman service instance topContainerId = "" def setUp(self): super().setUp() self.client = APIClient(base_url="tcp://127.0.0.1:8080", timeout=15) TestSystem.podman.restore_image_from_cache(self.client) TestSystem.topContainerId = common.run_top_container(self.client) def tearDown(self): common.remove_all_containers(self.client) common.remove_all_images(self.client) self.client.close() return super().tearDown() @classmethod def setUpClass(cls): super().setUpClass() TestSystem.podman = Podman() TestSystem.service = TestSystem.podman.open( "system", "service", "tcp:127.0.0.1:8080", "--time=0" ) # give the service some time to be ready... time.sleep(2) returncode = TestSystem.service.poll() if returncode is not None: raise subprocess.CalledProcessError(returncode, "podman system service") @classmethod def tearDownClass(cls): TestSystem.service.terminate() stdout, stderr = TestSystem.service.communicate(timeout=0.5) if stdout: sys.stdout.write("\nImages Service Stdout:\n" + stdout.decode("utf-8")) if stderr: sys.stderr.write("\nImAges Service Stderr:\n" + stderr.decode("utf-8")) TestSystem.podman.tear_down() return super().tearDownClass() def test_Info(self): self.assertIsNotNone(self.client.info()) def test_info_container_details(self): info = self.client.info() self.assertEqual(info["Containers"], 1) self.client.create_container(image=constant.ALPINE) info = self.client.info() self.assertEqual(info["Containers"], 2) def test_version(self): self.assertIsNotNone(self.client.version())
class DockerBuilder(Builder[DockerBuildConfiguration, str, DockerChecksumCalculator]): """ Builder of Docker images. """ def __init__( self, managed_build_configurations: Iterable[BuildConfigurationType] = None, checksum_retriever: ChecksumRetriever = None, checksum_calculator_factory: Callable[ [], DockerChecksumCalculator] = DockerChecksumCalculator): super().__init__(managed_build_configurations, checksum_retriever, checksum_calculator_factory) self.checksum_calculator.managed_build_configurations = self.managed_build_configurations self._docker_client = APIClient() def __del__(self): self._docker_client.close() def _build(self, build_configuration: DockerBuildConfiguration) -> str: logger.info(f"Building Docker image: {build_configuration.identifier}") logger.debug( f"{build_configuration.identifier} to be built using dockerfile " f"\"{build_configuration.dockerfile_location}\" in context \"{build_configuration.context}\"" ) log_generator = self._docker_client.build( path=build_configuration.context, tag=build_configuration.identifier, dockerfile=build_configuration.dockerfile_location, decode=True) log = {} try: for log in log_generator: details = log.get("stream", "").strip() if len(details) > 0: logger.debug(details) except APIError as e: if e.status_code == 400 and "parse error" in e.explanation: dockerfile_location = build_configuration.dockerfile_location with open(dockerfile_location, "r") as file: raise InvalidDockerfileBuildError(build_configuration.name, dockerfile_location, file.read()) raise e if "error" in log: error_details = log["errorDetail"] raise BuildStepError(build_configuration.name, error_details["message"], error_details.get("code")) return build_configuration.identifier
class TestContainers(unittest.TestCase): podman = None # initialized podman configuration for tests service = None # podman service instance topContainerId = "" def setUp(self): super().setUp() self.client = APIClient(base_url="tcp://127.0.0.1:8080", timeout=15) TestContainers.podman.restore_image_from_cache(self.client) TestContainers.topContainerId = common.run_top_container(self.client) self.assertIsNotNone(TestContainers.topContainerId) def tearDown(self): common.remove_all_containers(self.client) common.remove_all_images(self.client) self.client.close() return super().tearDown() @classmethod def setUpClass(cls): super().setUpClass() TestContainers.podman = Podman() TestContainers.service = TestContainers.podman.open( "system", "service", "tcp:127.0.0.1:8080", "--time=0" ) # give the service some time to be ready... time.sleep(2) rc = TestContainers.service.poll() if rc is not None: raise subprocess.CalledProcessError(rc, "podman system service") @classmethod def tearDownClass(cls): TestContainers.service.terminate() stdout, stderr = TestContainers.service.communicate(timeout=0.5) if stdout: sys.stdout.write("\nContainers Service Stdout:\n" + stdout.decode("utf-8")) if stderr: sys.stderr.write("\nContainers Service Stderr:\n" + stderr.decode("utf-8")) TestContainers.podman.tear_down() return super().tearDownClass() def test_inspect_container(self): # Inspect bogus container with self.assertRaises(errors.NotFound) as error: self.client.inspect_container("dummy") self.assertEqual(error.exception.response.status_code, 404) # Inspect valid container by Id container = self.client.inspect_container(TestContainers.topContainerId) self.assertIn("top", container["Name"]) # Inspect valid container by name container = self.client.inspect_container("top") self.assertIn(TestContainers.topContainerId, container["Id"]) def test_create_container(self): # Run a container with detach mode container = self.client.create_container(image="alpine", detach=True) self.assertEqual(len(container), 2) def test_start_container(self): # Start bogus container with self.assertRaises(errors.NotFound) as error: self.client.start("dummy") self.assertEqual(error.exception.response.status_code, 404) # Podman docs says it should give a 304 but returns with no response # # Start a already started container should return 304 # response = self.client.start(container=TestContainers.topContainerId) # self.assertEqual(error.exception.response.status_code, 304) # Create a new container and validate the count self.client.create_container(image=constant.ALPINE, name="container2") containers = self.client.containers(quiet=True, all=True) self.assertEqual(len(containers), 2) def test_stop_container(self): # Stop bogus container with self.assertRaises(errors.NotFound) as error: self.client.stop("dummy") self.assertEqual(error.exception.response.status_code, 404) # Validate the container state container = self.client.inspect_container("top") self.assertEqual(container["State"]["Status"], "running") # Stop a running container and validate the state self.client.stop(TestContainers.topContainerId) container = self.client.inspect_container("top") self.assertIn( container["State"]["Status"], "stopped exited", ) def test_restart_container(self): # Restart bogus container with self.assertRaises(errors.NotFound) as error: self.client.restart("dummy") self.assertEqual(error.exception.response.status_code, 404) # Validate the container state self.client.stop(TestContainers.topContainerId) container = self.client.inspect_container("top") self.assertEqual(container["State"]["Status"], "stopped") # restart a running container and validate the state self.client.restart(TestContainers.topContainerId) container = self.client.inspect_container("top") self.assertEqual(container["State"]["Status"], "running") def test_remove_container(self): # Remove bogus container with self.assertRaises(errors.NotFound) as error: self.client.remove_container("dummy") self.assertEqual(error.exception.response.status_code, 404) # Remove container by ID with force self.client.remove_container(TestContainers.topContainerId, force=True) containers = self.client.containers() self.assertEqual(len(containers), 0) def test_remove_container_without_force(self): # Validate current container count containers = self.client.containers() self.assertTrue(len(containers), 1) # Remove running container should throw error with self.assertRaises(errors.APIError) as error: self.client.remove_container(TestContainers.topContainerId) self.assertEqual(error.exception.response.status_code, 500) # Remove container by ID with force self.client.stop(TestContainers.topContainerId) self.client.remove_container(TestContainers.topContainerId) containers = self.client.containers() self.assertEqual(len(containers), 0) def test_pause_container(self): # Pause bogus container with self.assertRaises(errors.NotFound) as error: self.client.pause("dummy") self.assertEqual(error.exception.response.status_code, 404) # Validate the container state container = self.client.inspect_container("top") self.assertEqual(container["State"]["Status"], "running") # Pause a running container and validate the state self.client.pause(container["Id"]) container = self.client.inspect_container("top") self.assertEqual(container["State"]["Status"], "paused") def test_pause_stopped_container(self): # Stop the container self.client.stop(TestContainers.topContainerId) # Pause exited container should trow error with self.assertRaises(errors.APIError) as error: self.client.pause(TestContainers.topContainerId) self.assertEqual(error.exception.response.status_code, 500) def test_unpause_container(self): # Unpause bogus container with self.assertRaises(errors.NotFound) as error: self.client.unpause("dummy") self.assertEqual(error.exception.response.status_code, 404) # Validate the container state self.client.pause(TestContainers.topContainerId) container = self.client.inspect_container("top") self.assertEqual(container["State"]["Status"], "paused") # Pause a running container and validate the state self.client.unpause(TestContainers.topContainerId) container = self.client.inspect_container("top") self.assertEqual(container["State"]["Status"], "running") def test_list_container(self): # Add container and validate the count self.client.create_container(image="alpine", detach=True) containers = self.client.containers(all=True) self.assertEqual(len(containers), 2) def test_filters(self): self.skipTest("TODO Endpoint does not yet support filters") # List container with filter by id filters = {"id": TestContainers.topContainerId} ctnrs = self.client.containers(all=True, filters=filters) self.assertEqual(len(ctnrs), 1) # List container with filter by name filters = {"name": "top"} ctnrs = self.client.containers(all=True, filters=filters) self.assertEqual(len(ctnrs), 1) def test_rename_container(self): # rename bogus container with self.assertRaises(errors.APIError) as error: self.client.rename(container="dummy", name="newname") self.assertEqual(error.exception.response.status_code, 404)
class TestImages(unittest.TestCase): podman = None # initialized podman configuration for tests service = None # podman service instance def setUp(self): super().setUp() self.client = APIClient(base_url="tcp://127.0.0.1:8080", timeout=15) TestImages.podman.restore_image_from_cache(self.client) def tearDown(self): common.remove_all_images(self.client) self.client.close() return super().tearDown() @classmethod def setUpClass(cls): super().setUpClass() TestImages.podman = Podman() TestImages.service = TestImages.podman.open("system", "service", "tcp:127.0.0.1:8080", "--time=0") # give the service some time to be ready... time.sleep(2) returncode = TestImages.service.poll() if returncode is not None: raise subprocess.CalledProcessError(returncode, "podman system service") @classmethod def tearDownClass(cls): TestImages.service.terminate() stdout, stderr = TestImages.service.communicate(timeout=0.5) if stdout: sys.stdout.write("\nImages Service Stdout:\n" + stdout.decode("utf-8")) if stderr: sys.stderr.write("\nImAges Service Stderr:\n" + stderr.decode("utf-8")) TestImages.podman.tear_down() return super().tearDownClass() def test_inspect_image(self): """Inspect Image""" # Check for error with wrong image name with self.assertRaises(errors.NotFound): self.client.inspect_image("dummy") alpine_image = self.client.inspect_image(constant.ALPINE) self.assertIn(constant.ALPINE, alpine_image["RepoTags"]) def test_tag_invalid_image(self): """Tag Image Validates if invalid image name is given a bad response is encountered """ with self.assertRaises(errors.NotFound): self.client.tag("dummy", "demo") def test_tag_valid_image(self): """Validates if the image is tagged successfully""" self.client.tag(constant.ALPINE, "demo", constant.ALPINE_SHORTNAME) alpine_image = self.client.inspect_image(constant.ALPINE) for x in alpine_image["RepoTags"]: self.assertIn("alpine", x) # @unittest.skip("doesn't work now") def test_retag_valid_image(self): """Validates if name updates when the image is retagged""" self.client.tag(constant.ALPINE_SHORTNAME, "demo", "rename") alpine_image = self.client.inspect_image(constant.ALPINE) self.assertNotIn("demo:test", alpine_image["RepoTags"]) def test_list_images(self): """List images""" all_images = self.client.images() self.assertEqual(len(all_images), 1) # Add more images self.client.pull(constant.BB) all_images = self.client.images() self.assertEqual(len(all_images), 2) # List images with filter filters = {"reference": "alpine"} all_images = self.client.images(filters=filters) self.assertEqual(len(all_images), 1) def test_search_image(self): """Search for image""" response = self.client.search("libpod/alpine") for i in response: self.assertIn("quay.io/libpod/alpine", i["Name"]) def test_remove_image(self): """Remove image""" # Check for error with wrong image name with self.assertRaises(errors.NotFound): self.client.remove_image("dummy") all_images = self.client.images() self.assertEqual(len(all_images), 1) alpine_image = self.client.inspect_image(constant.ALPINE) self.client.remove_image(alpine_image["Id"]) all_images = self.client.images() self.assertEqual(len(all_images), 0) def test_image_history(self): """Image history""" # Check for error with wrong image name with self.assertRaises(errors.NotFound): self.client.history("dummy") # NOTE: history() has incorrect return type hint history = self.client.history(constant.ALPINE) alpine_image = self.client.inspect_image(constant.ALPINE) image_id = (alpine_image["Id"][7:] if alpine_image["Id"].startswith("sha256:") else alpine_image["Id"]) found = False for change in history: found |= image_id in change.values() self.assertTrue(found, f"image id {image_id} not found in history") def test_get_image_exists_not(self): """Negative test for get image""" with self.assertRaises(errors.NotFound): response = self.client.get_image("image_does_not_exists") collections.deque(response) def test_export_image(self): """Export Image""" self.client.pull(constant.BB) image = self.client.get_image(constant.BB) file = os.path.join(TestImages.podman.image_cache, "busybox.tar") with open(file, mode="wb") as tarball: for frame in image: tarball.write(frame) sz = os.path.getsize(file) self.assertGreater(sz, 0) def test_import_image(self): """Import|Load Image""" all_images = self.client.images() self.assertEqual(len(all_images), 1) file = os.path.join(TestImages.podman.image_cache, constant.ALPINE_TARBALL) self.client.import_image_from_file(filename=file) all_images = self.client.images() self.assertEqual(len(all_images), 2)