Files
test/keywords/docker/images/docker_sync_images_keywords.py
Andrew Vaillancourt 0796e5062a PoC: manifest-driven image sync for test images
This patch introduces a foundational test and keyword framework
for syncing Docker images from external registries (e.g., DockerHub)
into the local StarlingX registry at registry.local:9001. Sync
behavior is driven by YAML manifests and resolved using the
JSON5-based ConfigurationManager system already used throughout
starlingx/test.

Key Features:
- Supports multiple image manifests and logical registry mappings
  defined in config/docker/files/default.json5.
- Registry resolution follows:
  1. source_registry field (per image in manifest)
  2. manifest_registry_map (per manifest)
  3. default_source_registry (global fallback)
- Test coverage verifies that registry resolution honors override
  order, ensuring images are pulled from the correct source based
  on per-image fields and per-manifest mappings, using
  default_source_registry only when no override is provided.

Forward Compatibility:
- The config and manifest format is designed to support future
  extensions such as digest pinning, curated test image sets,
  or internal registry mirroring.
- Test images currently reference stable tags from
  https://hub.docker.com/u/starlingx and will later be moved to
  a dedicated test image repo.

This patch lays the foundation for managing image dependencies
through versioned manifests rather than bundling image binaries
in the repository.

Change-Id: Ib0bdf8ade444f079b141baed680eb1e71ed7cd0a
Signed-off-by: Andrew Vaillancourt <andrew.vaillancourt@windriver.com>
2025-06-12 02:52:12 -04:00

123 lines
5.1 KiB
Python

import yaml
from config.configuration_manager import ConfigurationManager
from framework.exceptions.keyword_exception import KeywordException
from framework.logging.automation_logger import get_logger
from framework.ssh.ssh_connection import SSHConnection
from keywords.base_keyword import BaseKeyword
from keywords.docker.images.docker_images_keywords import DockerImagesKeywords
from keywords.docker.images.docker_load_image_keywords import DockerLoadImageKeywords
class DockerSyncImagesKeywords(BaseKeyword):
"""
Provides functionality for Docker image synchronization across registries.
Supports pulling from source, tagging, and pushing to the local registry
based on manifest-driven configuration.
"""
def __init__(self, ssh_connection: SSHConnection):
"""
Initialize DockerSyncImagesKeywords with an SSH connection.
Args:
ssh_connection (SSHConnection): Active SSH connection to the system under test.
"""
self.ssh_connection = ssh_connection
self.docker_images_keywords = DockerImagesKeywords(ssh_connection)
self.docker_load_keywords = DockerLoadImageKeywords(ssh_connection)
def sync_images_from_manifest(self, manifest_path: str) -> None:
"""
Syncs Docker images listed in a YAML manifest from a source registry into the local registry.
For each image:
- Pull from the resolved source registry.
- Tag for the local registry (e.g., registry.local:9001).
- Push to the local registry.
Registry credentials and mappings are resolved using ConfigurationManager.get_docker_config(),
which loads config from `config/docker/files/default.json5` or a CLI override.
Registry resolution priority (from most to least specific):
1. "source_registry" field on the individual image entry (in the manifest)
2. "manifest_registry_map" entry matching the full manifest path (in config)
3. "default_source_registry" defined globally in config
Expected manifest format:
```yaml
images:
- name: "starlingx/test-image"
tag: "tag-x"
# Optional: source_registry: "dockerhub"
```
Notes:
- Registry URLs and credentials must be defined in config, not in the manifest.
Any such values in the manifest are ignored.
- Each image entry must include "name" and "tag".
Args:
manifest_path (str): Full path to the YAML manifest file.
Raises:
KeywordException: If one or more image sync operations fail.
ValueError: If no registry can be resolved for an image.
"""
docker_config = ConfigurationManager.get_docker_config()
local_registry = docker_config.get_registry("local_registry")
default_registry_name = docker_config.get_default_source_registry_name()
with open(manifest_path, "r") as f:
manifest = yaml.safe_load(f)
manifest_registry_name = docker_config.get_registry_for_manifest(manifest_path)
if "images" not in manifest:
raise ValueError(f"Manifest at {manifest_path} is missing required 'images' key")
failures = []
for image in manifest["images"]:
name = image["name"]
tag = image["tag"]
# Resolve source registry in order of precedence:
# 1) per-image override ("source_registry" in manifest)
# 2) per-manifest default (manifest_registry_map in config)
# 3) global fallback (default_source_registry in config)
source_registry_name = image.get("source_registry") or manifest_registry_name or default_registry_name
if not source_registry_name:
raise ValueError(f"Image '{name}:{tag}' has no 'source_registry' and no default_source_registry is set in config.")
try:
source_registry = docker_config.get_registry(source_registry_name)
source_image = f"{source_registry.get_registry_url()}/{name}:{tag}"
target_image = f"{local_registry.get_registry_url()}/{name}:{tag}"
get_logger().log_info(f"Pulling {source_image}")
self.docker_images_keywords.pull_image(source_image)
get_logger().log_info(f"Tagging {source_image} -> {target_image}")
self.docker_load_keywords.tag_docker_image_for_registry(
image_name=source_image,
tag_name=f"{name}:{tag}",
registry=local_registry,
)
get_logger().log_info(f"Pushing {target_image}")
self.docker_load_keywords.push_docker_image_to_registry(
tag_name=f"{name}:{tag}",
registry=local_registry,
)
except Exception as e:
error_msg = f"Failed to sync image {name}:{tag} from {source_registry_name}: {e}"
get_logger().log_error(error_msg)
failures.append(error_msg)
if failures:
raise KeywordException(f"Image sync failed for manifest '{manifest_path}':\n - " + "\n - ".join(failures))