From 5d2a68fc5eddcbf21ec8ffd5fb47df47f361988a Mon Sep 17 00:00:00 2001 From: Sascha Schwarze Date: Fri, 25 Mar 2022 23:18:53 +0100 Subject: [PATCH] Fix BuildKit and BuildAh image digest extraction --- .../buildah/buildstrategy_buildah_cr.yaml | 16 ++- .../buildkit/buildstrategy_buildkit_cr.yaml | 2 +- ...ildstrategy_source-to-image-redhat_cr.yaml | 16 ++- test/e2e/e2e_bundle_test.go | 9 +- test/e2e/e2e_image_mutate_test.go | 74 +---------- test/e2e/e2e_params_test.go | 2 +- test/e2e/e2e_test.go | 70 +++++++---- test/e2e/validators_test.go | 28 +---- test/utils/image.go | 115 ++++++++++++++++++ 9 files changed, 202 insertions(+), 130 deletions(-) create mode 100644 test/utils/image.go diff --git a/samples/buildstrategy/buildah/buildstrategy_buildah_cr.yaml b/samples/buildstrategy/buildah/buildstrategy_buildah_cr.yaml index e88161cb15..362c4421f6 100644 --- a/samples/buildstrategy/buildah/buildstrategy_buildah_cr.yaml +++ b/samples/buildstrategy/buildah/buildstrategy_buildah_cr.yaml @@ -149,10 +149,18 @@ spec: "${image}" \ "docker://${image}" - # Store the digest result - buildah images \ - --format='{{.Digest}}' \ - "${image}" | tr -d "\n" > '$(results.shp-image-digest.path)' + # Store the digest result. This is more complex than expected. BuildAh locally calculates a wrong digest. + # We therefore tag the image to a dummy name so that the layers are still present. Then we remove the local + # tag. Then we pull again. Then the local digest is correct. + # This should be validated again with a newer BuildAh version. + # https://github.com/containers/buildah/issues/3866 + buildah tag "${image}" dummy + buildah rmi "${image}" + buildah pull "${image}" + buildah inspect \ + --type=image \ + --format='{{.FromImageDigest}}' \ + "${image}" > '$(results.shp-image-digest.path)' # That's the separator between the shell script and its args - -- - --context diff --git a/samples/buildstrategy/buildkit/buildstrategy_buildkit_cr.yaml b/samples/buildstrategy/buildkit/buildstrategy_buildkit_cr.yaml index a75caecb65..b1ceb70ae3 100644 --- a/samples/buildstrategy/buildkit/buildstrategy_buildkit_cr.yaml +++ b/samples/buildstrategy/buildkit/buildstrategy_buildkit_cr.yaml @@ -178,7 +178,7 @@ spec: /tmp/run.sh # Store the image digest - sed -E 's/.*containerimage.digest":"([^"]*).*/\1/' < /tmp/image-metadata.json > '$(results.shp-image-digest.path)' + grep containerimage.digest /tmp/image-metadata.json | sed -E 's/.*containerimage.digest":\s*"([^"]*).*/\1/' | tr -d '\n' > '$(results.shp-image-digest.path)' # That's the separator between the shell script and its args - -- - --build-args diff --git a/samples/buildstrategy/source-to-image/buildstrategy_source-to-image-redhat_cr.yaml b/samples/buildstrategy/source-to-image/buildstrategy_source-to-image-redhat_cr.yaml index b8e990f061..0d3938b82f 100644 --- a/samples/buildstrategy/source-to-image/buildstrategy_source-to-image-redhat_cr.yaml +++ b/samples/buildstrategy/source-to-image/buildstrategy_source-to-image-redhat_cr.yaml @@ -111,10 +111,18 @@ spec: buildah push \ "docker://${image}" - # Store the digest result - buildah images \ - --format='{{.Digest}}' \ - "${image}" | tr -d "\n" > '$(results.shp-image-digest.path)' + # Store the digest result. This is more complex than expected. BuildAh locally calculates a wrong digest. + # We therefore tag the image to a dummy name so that the layers are still present. Then we remove the local + # tag. Then we pull again. Then the local digest is correct. + # This should be validated again with a newer BuildAh version. + # https://github.com/containers/buildah/issues/3866 + buildah tag "${image}" dummy + buildah rmi "${image}" + buildah pull "${image}" + buildah inspect \ + --type=image \ + --format='{{.FromImageDigest}}' \ + "${image}" > '$(results.shp-image-digest.path)' # That's the separator between the shell script and its args - -- - --image diff --git a/test/e2e/e2e_bundle_test.go b/test/e2e/e2e_bundle_test.go index 35a417ae6b..ac10e54320 100644 --- a/test/e2e/e2e_bundle_test.go +++ b/test/e2e/e2e_bundle_test.go @@ -76,8 +76,9 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun Create() Expect(err).ToNot(HaveOccurred()) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) validateBuildRunResultsFromBundleSource(buildRun) + testBuild.ValidateImageDigest(buildRun) }) It("should work with Buildpacks build strategy", func() { @@ -99,8 +100,9 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun Create() Expect(err).ToNot(HaveOccurred()) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) validateBuildRunResultsFromBundleSource(buildRun) + testBuild.ValidateImageDigest(buildRun) }) It("should work with Buildah build strategy", func() { @@ -130,8 +132,9 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun Create() Expect(err).ToNot(HaveOccurred()) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) validateBuildRunResultsFromBundleSource(buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) }) diff --git a/test/e2e/e2e_image_mutate_test.go b/test/e2e/e2e_image_mutate_test.go index 6772898f4e..04fd005725 100644 --- a/test/e2e/e2e_image_mutate_test.go +++ b/test/e2e/e2e_image_mutate_test.go @@ -5,17 +5,9 @@ package e2e_test import ( - "encoding/json" - "log" - "strings" - - "github.com/google/go-containerregistry/pkg/authn" - "github.com/google/go-containerregistry/pkg/name" containerreg "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/remote" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/types" buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" ) @@ -66,76 +58,22 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") appendRegistryInsecureParamValue(build, buildRun) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + + image := testBuild.GetImage(buildRun) Expect( - getImageAnnotation(getImage(build), "org.opencontainers.image.url"), + getImageAnnotation(image, "org.opencontainers.image.url"), ).To(Equal("https://my-company.com/images")) Expect( - getImageLabel(getImage(build), "maintainer"), + getImageLabel(image, "maintainer"), ).To(Equal("team@my-company.com")) }) }) }) -func getRegistryAuthentication( - build *buildv1alpha1.Build, - ref name.Reference, -) authn.Authenticator { - // In case no secret is mounted, use anonymous - if build.Spec.Output.Credentials == nil || build.Spec.Output.Credentials.Name == "" { - log.Printf("No access credentials provided, using anonymous mode") - return authn.Anonymous - } - - secret, err := testBuild.LookupSecret( - types.NamespacedName{ - Namespace: build.Namespace, - Name: build.Spec.Output.Credentials.Name, - }, - ) - Expect(err).ToNot(HaveOccurred(), "Error retrieving registry secret") - - type auth struct { - Auths map[string]authn.AuthConfig `json:"auths,omitempty"` - } - - var authConfig auth - - Expect(json.Unmarshal(secret.Data[".dockerconfigjson"], &authConfig)). - ToNot(HaveOccurred()) - - // Look-up the respective registry server inside the credentials - registryName := ref.Context().RegistryStr() - if registryName == name.DefaultRegistry { - registryName = authn.DefaultAuthKey - } - - return authn.FromConfig(authConfig.Auths[registryName]) -} - -func getImage(build *buildv1alpha1.Build) containerreg.Image { - // In the GitHub action, we are using a registry inside the cluster to - // push the image created by `buildRun`. The registry inside the cluster - // is not directly accessible from the local, so that we have mapped - // the cluster registry port to the local system - // by providing `test/kind/config.yaml` config to the kind - image := strings.Replace( - build.Spec.Output.Image, - "registry.registry.svc.cluster.local", - "localhost", 1, - ) - - ref, err := name.ParseReference(image) - Expect(err).To(BeNil()) - - img, err := remote.Image(ref, remote.WithAuth(getRegistryAuthentication(build, ref))) - Expect(err).To(BeNil()) - - return img -} - func getImageAnnotation(img containerreg.Image, annotation string) string { manifest, err := img.Manifest() Expect(err).To(BeNil()) diff --git a/test/e2e/e2e_params_test.go b/test/e2e/e2e_params_test.go index ef8ab27738..2100bf2a46 100644 --- a/test/e2e/e2e_params_test.go +++ b/test/e2e/e2e_params_test.go @@ -120,7 +120,7 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun Create() Expect(err).ToNot(HaveOccurred()) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) // we verify the image digest here which is mis-used by the strategy to store a calculated sum // 13 (env1) + 21 (env2 = 2${a-configmap:number1}) + 2 (env3 = ${a-secret:number2}) + 39 (args[0] = ${a-secret:number3}9) + 47 (args[1]) = 122 diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index b4986b6851..479faa323c 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -63,8 +63,9 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") appendRegistryInsecureParamValue(build, buildRun) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) validateBuildRunResultsFromGitSource(buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -86,7 +87,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") appendRegistryInsecureParamValue(build, buildRun) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -107,7 +109,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildpacks-v3-heroku_cr.yaml") Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -135,8 +138,9 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildpacks-v3-heroku_namespaced_cr.yaml") Expect(err).ToNot(HaveOccurred()) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) validateBuildRunResultsFromGitSource(buildRun) + testBuild.ValidateImageDigest(buildRun) }) AfterEach(func() { @@ -162,8 +166,9 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildpacks-v3_cr.yaml") Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) validateBuildRunResultsFromGitSource(buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -191,7 +196,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildpacks-v3_namespaced_cr.yaml") Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) }) AfterEach(func() { @@ -217,7 +223,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_buildpacks-v3_php_cr.yaml") Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -238,7 +245,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_buildpacks-v3_ruby_cr.yaml") Expect(err).ToNot(HaveOccurred()) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -259,7 +267,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_buildpacks-v3_golang_cr.yaml") Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -279,7 +288,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_buildpacks-v3_golang_cr.yaml") Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -301,7 +311,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_buildpacks-v3_golang_cr.yaml") Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) By("deleting the parent Build object") err = testBuild.DeleteBuild(build.Name) @@ -336,7 +347,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_buildpacks-v3_java_cr.yaml") Expect(err).ToNot(HaveOccurred()) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -357,8 +369,9 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_kaniko_cr.yaml") Expect(err).ToNot(HaveOccurred()) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) validateBuildRunResultsFromGitSource(buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -379,7 +392,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_kaniko_cr_advanced_dockerfile.yaml") Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -400,7 +414,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_kaniko_cr_custom_context+dockerfile.yaml") Expect(err).ToNot(HaveOccurred()) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -421,8 +436,9 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_kaniko-trivy-good_cr.yaml") Expect(err).ToNot(HaveOccurred()) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) validateBuildRunResultsFromGitSource(buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -464,9 +480,9 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildkit_cr.yaml") Expect(err).ToNot(HaveOccurred()) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) validateBuildRunResultsFromGitSource(buildRun) - validateImagePlatformsExist(build, []v1.Platform{ + testBuild.ValidateImagePlatformsExist(buildRun, []v1.Platform{ { Architecture: "amd64", OS: "linux", @@ -496,8 +512,9 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_source-to-image_cr.yaml") Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) validateBuildRunResultsFromGitSource(buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -526,7 +543,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildah_cr.yaml") Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -547,7 +565,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildah_cr.yaml") Expect(err).ToNot(HaveOccurred()) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -568,7 +587,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_kaniko_cr.yaml") Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -589,7 +609,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_kaniko_cr.yaml") Expect(err).ToNot(HaveOccurred()) - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) @@ -610,7 +631,8 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_source-to-image_cr.yaml") Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") - validateBuildRunToSucceed(testBuild, buildRun) + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) }) }) }) diff --git a/test/e2e/validators_test.go b/test/e2e/validators_test.go index a958fa91f8..3e1f3c8a3a 100644 --- a/test/e2e/validators_test.go +++ b/test/e2e/validators_test.go @@ -23,9 +23,6 @@ import ( "k8s.io/kubectl/pkg/scheme" "k8s.io/utils/pointer" - "github.com/google/go-containerregistry/pkg/name" - gcrv1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/shipwright-io/build/pkg/apis" buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" "github.com/shipwright-io/build/test/utils" @@ -112,7 +109,7 @@ func createContainerRegistrySecret(testBuild *utils.TestBuild) { } // validateBuildRunToSucceed creates the build run and watches its flow until it succeeds. -func validateBuildRunToSucceed(testBuild *utils.TestBuild, testBuildRun *buildv1alpha1.BuildRun) { +func validateBuildRunToSucceed(testBuild *utils.TestBuild, testBuildRun *buildv1alpha1.BuildRun) *buildv1alpha1.BuildRun { trueCondition := corev1.ConditionTrue falseCondition := corev1.ConditionFalse @@ -150,6 +147,8 @@ func validateBuildRunToSucceed(testBuild *utils.TestBuild, testBuildRun *buildv1 Expect(testBuildRun.Status.BuildSpec).ToNot(BeNil(), "BuildSpec is not available in the status") Logf("Test build '%s' is completed after %v !", testBuildRun.GetName(), testBuildRun.Status.CompletionTime.Time.Sub(testBuildRun.Status.StartTime.Time)) + + return testBuildRun } func validateBuildRunResultsFromGitSource(testBuildRun *buildv1alpha1.BuildRun) { @@ -371,24 +370,3 @@ func getTimeoutMultiplier() int64 { Expect(err).ToNot(HaveOccurred(), "Failed to parse EnvVarTimeoutMultiplier to integer") return intValue } - -func validateImagePlatformsExist(build *buildv1alpha1.Build, expectedPlatforms []gcrv1.Platform) { - // In the GitHub action, we are using a registry inside the cluster to - // push the image created by `buildRun`. The registry inside the cluster - // is not directly accessible from the local, so that we have mapped - // the cluster registry port to the local system - // by providing `test/kind/config.yaml` config to the kind - image := strings.Replace( - build.Spec.Output.Image, - "registry.registry.svc.cluster.local", - "localhost", 1, - ) - - ref, err := name.ParseReference(image) - Expect(err).ToNot(HaveOccurred()) - - for _, expectedPlatform := range expectedPlatforms { - _, err := remote.Image(ref, remote.WithAuth(getRegistryAuthentication(build, ref)), remote.WithPlatform(expectedPlatform)) - Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("Failed to validate %s/%s", expectedPlatform.OS, expectedPlatform.Architecture)) - } -} diff --git a/test/utils/image.go b/test/utils/image.go new file mode 100644 index 0000000000..68901c56b1 --- /dev/null +++ b/test/utils/image.go @@ -0,0 +1,115 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "encoding/json" + "fmt" + "log" + "strings" + + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + containerreg "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" + buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" + "k8s.io/apimachinery/pkg/types" + + . "github.com/onsi/gomega" +) + +func getImageURL(buildRun *buildv1alpha1.BuildRun) string { + image := "" + if buildRun.Spec.Output != nil { + image = buildRun.Spec.Output.Image + } else { + image = buildRun.Status.BuildSpec.Output.Image + } + + if buildRun.Status.Output != nil && buildRun.Status.Output.Digest != "" { + image = fmt.Sprintf("%s@%s", image, buildRun.Status.Output.Digest) + } + + // In the GitHub action, we are using a registry inside the cluster to + // push the image created by `buildRun`. The registry inside the cluster + // is not directly accessible from the local, so that we have mapped + // the cluster registry port to the local system + // by providing `test/kind/config.yaml` config to the kind + return strings.Replace(image, "registry.registry.svc.cluster.local", "localhost", 1) +} + +// GetImage loads the image manifest for the image produced by a BuildRun +func (t *TestBuild) GetImage(buildRun *buildv1alpha1.BuildRun) containerreg.Image { + ref, err := name.ParseReference(getImageURL(buildRun)) + Expect(err).ToNot(HaveOccurred()) + + img, err := remote.Image(ref, remote.WithAuth(t.getRegistryAuthentication(buildRun, ref))) + Expect(err).ToNot(HaveOccurred()) + + return img +} + +func (t *TestBuild) getRegistryAuthentication( + buildRun *buildv1alpha1.BuildRun, + ref name.Reference, +) authn.Authenticator { + secretName := "" + if buildRun.Spec.Output != nil && buildRun.Spec.Output.Credentials != nil && buildRun.Spec.Output.Credentials.Name != "" { + secretName = buildRun.Spec.Output.Credentials.Name + } else if buildRun.Status.BuildSpec.Output.Credentials != nil && buildRun.Status.BuildSpec.Output.Credentials.Name != "" { + secretName = buildRun.Status.BuildSpec.Output.Credentials.Name + } + + // In case no secret is mounted, use anonymous + if secretName == "" { + log.Println("No access credentials provided, using anonymous mode") + return authn.Anonymous + } + + secret, err := t.LookupSecret( + types.NamespacedName{ + Namespace: buildRun.Namespace, + Name: secretName, + }, + ) + Expect(err).ToNot(HaveOccurred(), "Error retrieving registry secret") + + type auth struct { + Auths map[string]authn.AuthConfig `json:"auths,omitempty"` + } + + var authConfig auth + + Expect(json.Unmarshal(secret.Data[".dockerconfigjson"], &authConfig)).ToNot(HaveOccurred(), "Error parsing secrets docker config") + + // Look-up the respective registry server inside the credentials + registryName := ref.Context().RegistryStr() + if registryName == name.DefaultRegistry { + registryName = authn.DefaultAuthKey + } + + return authn.FromConfig(authConfig.Auths[registryName]) +} + +// ValidateImagePlatformsExist that the image produced by a BuildRun exists for a set of platforms +func (t *TestBuild) ValidateImagePlatformsExist(buildRun *buildv1alpha1.BuildRun, expectedPlatforms []containerreg.Platform) { + ref, err := name.ParseReference(getImageURL(buildRun)) + Expect(err).ToNot(HaveOccurred()) + + for _, expectedPlatform := range expectedPlatforms { + _, err := remote.Image(ref, remote.WithAuth(t.getRegistryAuthentication(buildRun, ref)), remote.WithPlatform(expectedPlatform)) + Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("Failed to validate %s/%s", expectedPlatform.OS, expectedPlatform.Architecture)) + } +} + +// ValidateImageDigest ensures that an image digest is set in the BuildRun status and that this digest is pointing to an image +func (t *TestBuild) ValidateImageDigest(buildRun *buildv1alpha1.BuildRun) { + // Verify that the status contains a digest + Expect(buildRun.Status.Output).NotTo(BeNil(), ".status.output is nil") + Expect(buildRun.Status.Output.Digest).NotTo(Equal(""), ".status.output.digest is empty") + + // Verify that the digest is valid by retrieving the image manifest + t.GetImage(buildRun) +}