
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>
123 lines
5.1 KiB
Python
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))
|