mirror of
https://github.com/open-cluster-management-io/ocm.git
synced 2026-02-14 10:00:11 +00:00
🌱 Change image tag validation to fail only for tag format mismatches (#1385)
Modified e2e test suite to validate image tags and fail tests when tag-based images don't match the expected tag, while skipping validation for digest-based images (SHA format). Changes: - Added validateImageFormat() helper to check image format (tag vs digest) - Images using digest format (@sha256:...) skip validation - Images using tag format (:tag) are validated against expected tag - Tests fail with Expect() if tag validation fails - Validation applies to test image variables and ClusterManager specs - Only validates ClusterManager CR specs, not deployments - Removed validateKlusterletImageSpecs() to avoid validation before resource creation Bug fix: - Fixed CI failure where image validation ran before Klusterlet was created - The validation now only checks test inputs (which are used to create Klusterlet) - This ensures Klusterlet has correct images by design without redundant validation This fixes the BeforeSuite error: "image validation failed: [failed to get Klusterlet: klusterlets.operator.open-cluster-management.io "e2e-universal-klusterlet" not found]" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: zhujian <jiazhu@redhat.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -179,7 +179,7 @@ var _ = BeforeSuite(func() {
|
||||
}).Should(Succeed())
|
||||
|
||||
By("Validate image configuration")
|
||||
validateImageConfiguration()
|
||||
Expect(validateImageConfiguration()).ToNot(HaveOccurred())
|
||||
|
||||
By("Create a universal Klusterlet/managedcluster")
|
||||
framework.CreateAndApproveKlusterlet(
|
||||
@@ -228,36 +228,71 @@ var _ = AfterSuite(func() {
|
||||
})
|
||||
|
||||
// validateImageConfiguration validates that all image configurations use the expected tag
|
||||
func validateImageConfiguration() {
|
||||
func validateImageConfiguration() error {
|
||||
if expectedImageTag == "" {
|
||||
By("Skipping image validation: no expected image tag specified")
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
By(fmt.Sprintf("Validating image configuration uses expected tag: %s", expectedImageTag))
|
||||
|
||||
var validationErrors []error
|
||||
|
||||
// Validate test image variables
|
||||
validateTestImageVariables()
|
||||
if err := validateTestImageVariables(); err != nil {
|
||||
validationErrors = append(validationErrors, err)
|
||||
}
|
||||
|
||||
// Validate ClusterManager spec after it's deployed
|
||||
Eventually(validateClusterManagerImageSpecs).Should(Succeed())
|
||||
if err := validateClusterManagerImageSpecs(); err != nil {
|
||||
validationErrors = append(validationErrors, err)
|
||||
}
|
||||
|
||||
// Validate actual deployment containers
|
||||
validateDeploymentContainers()
|
||||
// Note: Klusterlet validation is not needed because the Klusterlet is created
|
||||
// using the test image variables that are already validated above
|
||||
|
||||
if len(validationErrors) > 0 {
|
||||
return fmt.Errorf("image validation failed: %v", validationErrors)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateTestImageVariables() {
|
||||
if registrationImage != "" && !strings.HasSuffix(registrationImage, ":"+expectedImageTag) {
|
||||
Fail(fmt.Sprintf("registrationImage does not end with expected tag: %s (actual: %s)", expectedImageTag, registrationImage))
|
||||
func validateTestImageVariables() error {
|
||||
if registrationImage != "" {
|
||||
if err := validateImageFormat(registrationImage, "registrationImage"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if workImage != "" && !strings.HasSuffix(workImage, ":"+expectedImageTag) {
|
||||
Fail(fmt.Sprintf("workImage does not end with expected tag: %s (actual: %s)", expectedImageTag, workImage))
|
||||
if workImage != "" {
|
||||
if err := validateImageFormat(workImage, "workImage"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if singletonImage != "" && !strings.HasSuffix(singletonImage, ":"+expectedImageTag) {
|
||||
Fail(fmt.Sprintf("singletonImage does not end with expected tag: %s (actual: %s)", expectedImageTag, singletonImage))
|
||||
if singletonImage != "" {
|
||||
if err := validateImageFormat(singletonImage, "singletonImage"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateImageFormat(image, imageName string) error {
|
||||
if strings.Contains(image, "@") {
|
||||
// Image uses digest format (e.g., registry/image@sha256:...), skip validation
|
||||
By(fmt.Sprintf("Skipping validation for %s: using digest format", imageName))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Image uses tag format, validate the tag
|
||||
if !strings.HasSuffix(image, ":"+expectedImageTag) {
|
||||
return fmt.Errorf("%s does not end with expected tag: %s (actual: %s)", imageName, expectedImageTag, image)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateClusterManagerImageSpecs() error {
|
||||
@@ -273,147 +308,28 @@ func validateClusterManagerImageSpecs() error {
|
||||
spec := clusterManager.Spec
|
||||
|
||||
if spec.RegistrationImagePullSpec != "" {
|
||||
if err := validateImageTagInSpec(spec.RegistrationImagePullSpec, "registrationImagePullSpec"); err != nil {
|
||||
if err := validateImageFormat(spec.RegistrationImagePullSpec, "ClusterManager.spec.registrationImagePullSpec"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if spec.WorkImagePullSpec != "" {
|
||||
if err := validateImageTagInSpec(spec.WorkImagePullSpec, "workImagePullSpec"); err != nil {
|
||||
if err := validateImageFormat(spec.WorkImagePullSpec, "ClusterManager.spec.workImagePullSpec"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if spec.PlacementImagePullSpec != "" {
|
||||
if err := validateImageTagInSpec(spec.PlacementImagePullSpec, "placementImagePullSpec"); err != nil {
|
||||
if err := validateImageFormat(spec.PlacementImagePullSpec, "ClusterManager.spec.placementImagePullSpec"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if spec.AddOnManagerImagePullSpec != "" {
|
||||
if err := validateImageTagInSpec(spec.AddOnManagerImagePullSpec, "addOnManagerImagePullSpec"); err != nil {
|
||||
if err := validateImageFormat(spec.AddOnManagerImagePullSpec, "ClusterManager.spec.addOnManagerImagePullSpec"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateImageTagInSpec(imageSpec, fieldName string) error {
|
||||
if !strings.HasSuffix(imageSpec, ":"+expectedImageTag) {
|
||||
return fmt.Errorf("ClusterManager.spec.%s does not end with expected tag: %s (actual: %s)",
|
||||
fieldName, expectedImageTag, imageSpec)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDeploymentContainers() {
|
||||
By("Validating deployment container images")
|
||||
|
||||
validateClusterManagerImageTags()
|
||||
validateKlusterletImageTags()
|
||||
}
|
||||
|
||||
func validateClusterManagerImageTags() {
|
||||
By("Checking cluster-manager deployment containers")
|
||||
ctx := context.TODO()
|
||||
|
||||
Eventually(func() error {
|
||||
deployment, err := hub.KubeClient.AppsV1().Deployments("open-cluster-management").Get(ctx, "cluster-manager", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get cluster-manager deployment: %v", err)
|
||||
}
|
||||
|
||||
for _, container := range deployment.Spec.Template.Spec.Containers {
|
||||
if err := validateImageTag(container.Image, container.Name, "cluster-manager"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Also check init containers if any
|
||||
for _, container := range deployment.Spec.Template.Spec.InitContainers {
|
||||
if err := validateImageTag(container.Image, container.Name, "cluster-manager"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}).Should(Succeed())
|
||||
}
|
||||
|
||||
func validateKlusterletImageTags() {
|
||||
By("Checking klusterlet deployment containers")
|
||||
ctx := context.TODO()
|
||||
|
||||
Eventually(func() error {
|
||||
deployment, err := hub.KubeClient.AppsV1().Deployments("open-cluster-management").Get(ctx, "klusterlet", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get klusterlet deployment: %v", err)
|
||||
}
|
||||
|
||||
for _, container := range deployment.Spec.Template.Spec.Containers {
|
||||
if err := validateImageTag(container.Image, container.Name, "klusterlet"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Also check init containers if any
|
||||
for _, container := range deployment.Spec.Template.Spec.InitContainers {
|
||||
if err := validateImageTag(container.Image, container.Name, "klusterlet"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}).Should(Succeed())
|
||||
}
|
||||
|
||||
func validateImageTag(image, containerName, deploymentName string) error {
|
||||
// Extract the tag from the image
|
||||
// Image format: registry/repo:tag or registry/repo@digest
|
||||
var actualTag string
|
||||
|
||||
switch {
|
||||
case strings.Contains(image, "@"):
|
||||
// Handle digest format (registry/repo@sha256:...)
|
||||
return fmt.Errorf("container %s in %s deployment uses digest format (%s), cannot validate tag",
|
||||
containerName, deploymentName, image)
|
||||
case strings.Contains(image, ":"):
|
||||
// Handle tag format (registry/repo:tag)
|
||||
parts := strings.Split(image, ":")
|
||||
actualTag = parts[len(parts)-1]
|
||||
default:
|
||||
// No tag specified, defaults to "latest"
|
||||
actualTag = "latest"
|
||||
}
|
||||
|
||||
// Only validate OCM-related images (skip system images like kube-rbac-proxy, etc.)
|
||||
if isOCMImage(image) {
|
||||
if actualTag != expectedImageTag {
|
||||
return fmt.Errorf("container %s in %s deployment has incorrect image tag: expected %s, got %s (image: %s)",
|
||||
containerName, deploymentName, expectedImageTag, actualTag, image)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isOCMImage(image string) bool {
|
||||
// Check if this is an OCM component image
|
||||
ocmPatterns := []string{
|
||||
"open-cluster-management",
|
||||
"registration",
|
||||
"work",
|
||||
"placement",
|
||||
"addon-manager",
|
||||
"klusterlet",
|
||||
}
|
||||
|
||||
for _, pattern := range ocmPatterns {
|
||||
if strings.Contains(image, pattern) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user