Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions test/acceptance/releases_images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package acceptance

import (
"fmt"
"strings"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -40,4 +41,76 @@ var _ = Describe("Trusted Artifact Signer Releases", Ordered, func() {
Expect(mapped).To(HaveEach(1))
Expect(len(snapshotData.Images)).To(BeNumerically("==", len(mapped)))
})

It("operator and operator bundle have both the same git reference", func() {
operatorReference, err := support.GetImageLabel(snapshotData.Images[support.OperatorImageKey], "vcs-ref")
Expect(err).NotTo(HaveOccurred())
Expect(operatorReference).NotTo(BeEmpty())
operatorBundleReference, err := support.GetImageLabel(snapshotData.Images[support.OperatorBundleImageKey], "vcs-ref")
Expect(err).NotTo(HaveOccurred())
Expect(operatorBundleReference).NotTo(BeEmpty())
Expect(operatorReference).To(Equal(operatorBundleReference))
})

It("snapshot.json images have correct labels", func() {
var imageDataList []support.ImageData
imageLabelsErrors := make(map[string][]string)

// Collect all images and their labels
for imageName, imageDefinition := range snapshotData.Images {
labels, err := support.InspectImageForLabels(imageDefinition)
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to inspect labels for image %s (%s)", imageName, imageDefinition))

imageData := support.ImageData{
Image: imageDefinition,
Labels: labels,
}
imageDataList = append(imageDataList, imageData)
}

// Check that all images have required labels with correct values
requiredLabels := support.RequiredImageLabels()
for _, imageData := range imageDataList {
var currentImageErrors []string

for labelName, expectedValue := range requiredLabels {
if _, exists := imageData.Labels[labelName]; !exists {
currentImageErrors = append(currentImageErrors,
fmt.Sprintf(" %s: missing", labelName))
continue
}

if expectedValue != "" { // Specific value expected
if imageData.Labels[labelName] != expectedValue {
currentImageErrors = append(currentImageErrors,
fmt.Sprintf(" %s: %s, expected: %s",
labelName, imageData.Labels[labelName], expectedValue))
}
} else { // Label must not be empty
if imageData.Labels[labelName] == "" {
currentImageErrors = append(currentImageErrors,
fmt.Sprintf(" %s: missing", labelName))
}
}
}

if len(currentImageErrors) > 0 {
imageLabelsErrors[imageData.Image] = currentImageErrors
}
}

// Format errors in a human-readable way
if len(imageLabelsErrors) > 0 {
var errorReport strings.Builder
for image, errors := range imageLabelsErrors {
errorReport.WriteString(image)
errorReport.WriteString(":\n")
for _, error := range errors {
errorReport.WriteString(error)
errorReport.WriteString("\n")
}
}
Fail("Label validation errors found:\n" + errorReport.String())
}
})
})
21 changes: 18 additions & 3 deletions test/support/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"log"
"os"
"slices"
)

func GetEnv(key string) string {
Expand All @@ -26,7 +27,7 @@ func getEnv(key string, isSecret bool) string {
return envValue
}

func GetMapKeys(m map[string]string) []string {
func GetMapKeys[V any](m map[string]V) []string {
result := make([]string, 0, len(m))
for k := range m {
result = append(result, k)
Expand All @@ -42,6 +43,12 @@ func GetMapValues(m map[string]string) []string {
return result
}

func GetMapKeysSorted[V any](m map[string]V) []string {
keys := GetMapKeys(m)
slices.Sort(keys)
return keys
}

func SplitMap(original map[string]string, keysToKeep []string) (map[string]string, map[string]string) {
remaining := make(map[string]string)
moved := make(map[string]string)
Expand Down Expand Up @@ -73,10 +80,18 @@ func LogArray(message string, data []string) {
log.Print(result)
}

func LogMap(message string, data map[string]string) {
func LogMap[V any](message string, data map[string]V) {
result := message + "\n"
for key, value := range data {
result += fmt.Sprintf(" [%-41s] %s\n", key, value)
result += fmt.Sprintf(" [%-41v] %v\n", key, value)
}
log.Print(result)
}

func LogMapByProvidedKeys[V any](message string, data map[string]V, keysToLog []string) {
result := message + "\n"
for _, key := range keysToLog {
result += fmt.Sprintf(" [%-53v] %v\n", key, data[key])
}
log.Print(result)
}
103 changes: 85 additions & 18 deletions test/support/image_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,56 @@ import (
"github.com/docker/docker/client"
)

type ImageData struct {
Image string
Labels map[string]string
}

func PullImageIfNotPresentLocally(ctx context.Context, imageDefinition string) error {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return fmt.Errorf("could not create docker client: %w", err)
}
defer cli.Close()

// Inspect the image to check if it exists locally.
_, _, err = cli.ImageInspectWithRaw(ctx, imageDefinition)
if err == nil {
return nil // image is present already
}

if client.IsErrNotFound(err) {
log.Printf("Image '%s' not found locally, pulling...\n", imageDefinition)
pullResp, pullErr := cli.ImagePull(ctx, imageDefinition, image.PullOptions{
Platform: "linux/amd64",
})
if pullErr != nil {
return fmt.Errorf("failed to pull image: %w", pullErr)
}
defer pullResp.Close()
// ensure the pull operation completes.
if _, err := io.Copy(io.Discard, pullResp); err != nil {
return fmt.Errorf("failed to read pull response: %w", err)
}
return nil
}

// another type of error, return it.
return fmt.Errorf("failed to inspect image: %w", err)
}

func RunImage(imageDefinition string, commands []string) (string, error) {
ctx := context.TODO()
err := PullImageIfNotPresentLocally(ctx, imageDefinition)
if err != nil {
return "", err
}

log.Printf("Running image %s with commands %v\n", imageDefinition, commands)
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return "", fmt.Errorf("error while initializing docker client: %w", err)
}
reader, err := cli.ImagePull(ctx, imageDefinition, image.PullOptions{
Platform: "linux/amd64",
})
if err != nil {
return "", fmt.Errorf("cannot pull image %s: %w", imageDefinition, err)
}
_, _ = io.Copy(os.Stdout, reader)
_ = reader.Close()

resp, err := cli.ContainerCreate(ctx, &container.Config{
Image: imageDefinition,
Expand Down Expand Up @@ -69,21 +104,53 @@ func RunImage(imageDefinition string, commands []string) (string, error) {
return buf.String(), nil
}

func FileFromImage(ctx context.Context, imageName, filePath, outputPath string) error {
// Initialize the Docker client
func InspectImageForLabels(imageDefinition string) (map[string]string, error) {
ctx := context.TODO()
err := PullImageIfNotPresentLocally(ctx, imageDefinition)
if err != nil {
return nil, err
}

cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
panic(err)
return nil, fmt.Errorf("error while initializing docker client: %w", err)
}
defer cli.Close()

inspectData, _, err := cli.ImageInspectWithRaw(ctx, imageDefinition)
if err != nil {
return nil, fmt.Errorf("cannot inspect image %s: %w", imageDefinition, err)
}
if inspectData.Config == nil || len(inspectData.Config.Labels) == 0 {
log.Printf("Image [%s] does not have any labels\n", imageDefinition)
return make(map[string]string), nil
}

return inspectData.Config.Labels, nil
}

func GetImageLabel(imageDefinition, labelName string) (string, error) {
labels, err := InspectImageForLabels(imageDefinition)
if err != nil {
return "", err
}
if labelValue, ok := labels[labelName]; ok {
return labelValue, nil
}
return "", fmt.Errorf("label [%s] not found in image %s", labelName, imageDefinition)
}

reader, err := cli.ImagePull(ctx, imageName, image.PullOptions{
Platform: "linux/amd64",
})
func FileFromImage(ctx context.Context, imageName, filePath, outputPath string) error {
err := PullImageIfNotPresentLocally(ctx, imageName)
if err != nil {
return fmt.Errorf("cannot pull image %s: %w", imageName, err)
return err
}

// Initialize the Docker client
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
panic(err)
}
_, _ = io.Copy(io.Discard, reader)
_ = reader.Close()

// Create a container from the image
resp, err := cli.ContainerCreate(ctx, &container.Config{
Expand All @@ -102,7 +169,7 @@ func FileFromImage(ctx context.Context, imageName, filePath, outputPath string)
}()

// Use Docker's API to copy the file from the container's filesystem
reader, _, err = cli.CopyFromContainer(ctx, resp.ID, filePath)
reader, _, err := cli.CopyFromContainer(ctx, resp.ID, filePath)
if err != nil {
return fmt.Errorf("failed to copy file from container: %w", err)
}
Expand Down
11 changes: 11 additions & 0 deletions test/support/test_constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,14 @@ func GetOSArchMatrix() OSArchMatrix {
"windows": {"amd64"},
}
}

// If no value is provided, the label must exist, but can have any non-empty value.
func RequiredImageLabels() map[string]string {
return map[string]string{
"architecture": "x86_64",
"build-date": "",
"vcs-ref": "",
"vcs-type": "git",
"vendor": "Red Hat, Inc.",
}
}
Loading