Skip to content
Open
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
5 changes: 3 additions & 2 deletions tests/integration/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ require (
github.com/operator-framework/operator-registry v1.50.0
github.com/sirupsen/logrus v1.9.4
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.32.0
sigs.k8s.io/yaml v1.4.0
)

require (
Expand Down Expand Up @@ -125,13 +127,12 @@ require (
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gotest.tools/v3 v3.5.1 // indirect
k8s.io/api v0.32.0 // indirect
k8s.io/apiextensions-apiserver v0.32.0 // indirect
k8s.io/apimachinery v0.32.0 // indirect
k8s.io/client-go v0.32.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
1 change: 1 addition & 0 deletions tests/integration/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE=
k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0=
k8s.io/apiextensions-apiserver v0.32.0/go.mod h1:86hblMvN5yxMvZrZFX2OhIHAuFIMJIZ19bTvzkP+Fmw=
k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg=
k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8=
Expand Down
116 changes: 116 additions & 0 deletions tests/integration/signature_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
package integration_test

import (
"encoding/json"
"os"
"path/filepath"
"strings"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"

"github.com/openshift/oc-mirror/tests/integration/pkg/ocmirror"
)

var _ = Describe("signatures", func() {
Expand Down Expand Up @@ -48,6 +56,9 @@ var _ = Describe("signatures", func() {
By("verifying signatures are present in the local registry")
expectSignaturesInRegistry(*testRegistry)

By("verifying the signature configmap was generated correctly")
expectSignatureConfigMapGenerated(workDir)

By("running delete with --delete-signatures")
result, err = runner.DeletePhaseOne(ctx, filepath.Join(iscDir, discSignatures), workDir, "", testRegistry.Endpoint(),
"--delete-signatures")
Expand Down Expand Up @@ -86,4 +97,109 @@ var _ = Describe("signatures", func() {
By("verifying signature tags are still present")
expectOnlySignatureTagsRemain(*testRegistry)
})

It("fails gracefully when it fails to retrieve the release signature", func() {
// Use a plain, unseeded working directory so no cached signature is found,
// forcing oc-mirror to fetch it from OCP_SIGNATURE_URL.
unseededWorkDir, err := os.MkdirTemp("", "oc-mirror-test-*")
Expect(err).NotTo(HaveOccurred())
defer cleanupWorkDir(unseededWorkDir)

By("running mirrorToDisk with an unreachable signature server")
failingSigRunner := ocmirror.NewRunner(os.Getenv("OC_MIRROR_BINARY")).
WithEnv([]string{"OCP_SIGNATURE_URL=http://127.0.0.1:1/"})
result, err := failingSigRunner.MirrorToDisk(ctx, filepath.Join(iscDir, iscSignatures), unseededWorkDir)

By("verifying oc-mirror failed gracefully instead of panicking")
expectOcMirrorExitCode(result, err, 2, "collection error", "http request")
expectNoTarArchive(unseededWorkDir)
})

Describe("release signature configmap", func() {
It("should not generate a signature configmap when --ignore-release-signature is used", func() {
// Use a plain, unseeded working directory to guarantee no signature is ever
// cached, regardless of --ignore-release-signature.
unseededWorkDir, err := os.MkdirTemp("", "oc-mirror-test-*")
Expect(err).NotTo(HaveOccurred())
defer cleanupWorkDir(unseededWorkDir)

By("running mirrorToDisk with --ignore-release-signature")
result, err := runner.MirrorToDisk(ctx, filepath.Join(iscDir, iscSignatures), unseededWorkDir, "--ignore-release-signature")
expectOcMirrorCommandSuccess(result, err)

By("running diskToMirror with --ignore-release-signature")
result, err = runner.DiskToMirror(ctx, filepath.Join(iscDir, iscSignatures), unseededWorkDir, testRegistry.Endpoint(),
"--ignore-release-signature", "--dest-tls-verify=false")
expectOcMirrorCommandSuccess(result, err)

By("verifying no signature configmap was generated since no signature was ever cached")
expectNoSignatureConfigMap(unseededWorkDir)
})
})
})

// expectSignatureConfigMapGenerated verifies that the release signature configmap
// (both JSON and YAML) was generated in working-dir/cluster-resources with the
// expected static name, and that every binaryData entry in the configmap is backed
// by a cached signature file in working-dir/signatures with matching content.
// Note: the configmap only contains signatures for images that were actually
// mirrored as OCP releases, so not every file on disk is necessarily present in
// the configmap.
func expectSignatureConfigMapGenerated(workDir string) {
crDir := filepath.Join(workDir, dirWorkingDir, dirClusterResources)

var jsonCM corev1.ConfigMap
jsonData, err := os.ReadFile(filepath.Join(crDir, "signature-configmap.json"))
Expect(err).NotTo(HaveOccurred(), "signature-configmap.json not found")
Expect(json.Unmarshal(jsonData, &jsonCM)).To(Succeed(), "failed to unmarshal signature-configmap.json")

var yamlCM corev1.ConfigMap
yamlData, err := os.ReadFile(filepath.Join(crDir, "signature-configmap.yaml"))
Expect(err).NotTo(HaveOccurred(), "signature-configmap.yaml not found")
Expect(yaml.Unmarshal(yamlData, &yamlCM)).To(Succeed(), "failed to unmarshal signature-configmap.yaml")

for _, cm := range []corev1.ConfigMap{jsonCM, yamlCM} {
Expect(cm.Name).To(Equal("mirrored-release-signatures"))
Expect(cm.BinaryData).NotTo(BeEmpty())
for key := range cm.BinaryData {
Expect(key).To(MatchRegexp(`^sha256-[0-9a-f]{64}-\d+$`), "unexpected binaryData key format: %s", key)
}
}
Expect(jsonCM.BinaryData).To(Equal(yamlCM.BinaryData), "json and yaml signature configmaps disagree on binaryData")

sigDir := filepath.Join(workDir, dirWorkingDir, "signatures")
entries, err := os.ReadDir(sigDir)
Expect(err).NotTo(HaveOccurred())
Expect(entries).NotTo(BeEmpty(), "no cached signature files found in %s", sigDir)

// Verify every configmap entry is backed by a real signature file on disk.
// Key format is "sha256-<hex_digest>-<number>"; extract the hex digest.
for key, val := range jsonCM.BinaryData {
parts := strings.SplitN(key, "-", 3)
Expect(parts).To(HaveLen(3), "unexpected binaryData key format: %s", key)
digest := parts[1]

found := false
for _, f := range entries {
if strings.Contains(f.Name(), digest) {
data, err := os.ReadFile(filepath.Join(sigDir, f.Name()))
Expect(err).NotTo(HaveOccurred())
Expect(val).To(Equal(data), "binaryData content mismatch for key %s and file %s", key, f.Name())
found = true
break
}
}
Expect(found).To(BeTrue(), "no signature file found for configmap key %s", key)
}
}

// expectNoSignatureConfigMap verifies that no release signature configmap was generated.
func expectNoSignatureConfigMap(workDir string) {
crDir := filepath.Join(workDir, dirWorkingDir, dirClusterResources)

_, errJSON := os.Stat(filepath.Join(crDir, "signature-configmap.json"))
Expect(os.IsNotExist(errJSON)).To(BeTrue(), "expected signature-configmap.json to not exist")

_, errYAML := os.Stat(filepath.Join(crDir, "signature-configmap.yaml"))
Expect(os.IsNotExist(errYAML)).To(BeTrue(), "expected signature-configmap.yaml to not exist")
}