From 5583a73d79f0cd4cce0522a682e4e92ff46687b6 Mon Sep 17 00:00:00 2001 From: arewm Date: Fri, 13 Dec 2024 15:55:10 -0500 Subject: [PATCH 1/3] Enable the fetching of only the OCI descriptor Some OCI artifacts need to have a specific media type (i.e. they should only be an OCI image index or an OCI image manifest). In order to determine that with a single remote call, we can just get the descriptor and then check the mediaType of that object. Signed-off-by: arewm --- .../modules/ROOT/pages/ec_oci_descriptor.adoc | 19 + docs/modules/ROOT/pages/rego_builtins.adoc | 2 + docs/modules/ROOT/partials/rego_nav.adoc | 1 + internal/rego/oci/__snapshots__/oci_test.snap | 1074 +++++++++++++++++ internal/rego/oci/oci.go | 61 + internal/rego/oci/oci_test.go | 347 ++++++ 6 files changed, 1504 insertions(+) create mode 100644 docs/modules/ROOT/pages/ec_oci_descriptor.adoc diff --git a/docs/modules/ROOT/pages/ec_oci_descriptor.adoc b/docs/modules/ROOT/pages/ec_oci_descriptor.adoc new file mode 100644 index 000000000..3b7d32652 --- /dev/null +++ b/docs/modules/ROOT/pages/ec_oci_descriptor.adoc @@ -0,0 +1,19 @@ += ec.oci.descriptor + +Fetch a raw Image from an OCI registry. + +== Usage + + object = ec.oci.descriptor(ref: string) + +== Parameters + +* `ref` (`string`): OCI descriptor reference + +== Return + +`object` (`object`): the OCI descriptor object + +The object contains the following attributes: + +* `mediaType` (`string`) diff --git a/docs/modules/ROOT/pages/rego_builtins.adoc b/docs/modules/ROOT/pages/rego_builtins.adoc index 4067b78fe..427803b96 100644 --- a/docs/modules/ROOT/pages/rego_builtins.adoc +++ b/docs/modules/ROOT/pages/rego_builtins.adoc @@ -10,6 +10,8 @@ information. |=== |xref:ec_oci_blob.adoc[ec.oci.blob] |Fetch a blob from an OCI registry. +|xref:ec_oci_descriptor.adoc[ec.oci.descriptor] +|Fetch a raw Image from an OCI registry. |xref:ec_oci_image_files.adoc[ec.oci.image_files] |Fetch structured files (YAML or JSON) from within an image. |xref:ec_oci_image_manifest.adoc[ec.oci.image_manifest] diff --git a/docs/modules/ROOT/partials/rego_nav.adoc b/docs/modules/ROOT/partials/rego_nav.adoc index 70f0cf88e..a60e223a5 100644 --- a/docs/modules/ROOT/partials/rego_nav.adoc +++ b/docs/modules/ROOT/partials/rego_nav.adoc @@ -1,5 +1,6 @@ * xref:rego_builtins.adoc[Rego Reference] ** xref:ec_oci_blob.adoc[ec.oci.blob] +** xref:ec_oci_descriptor.adoc[ec.oci.descriptor] ** xref:ec_oci_image_files.adoc[ec.oci.image_files] ** xref:ec_oci_image_manifest.adoc[ec.oci.image_manifest] ** xref:ec_purl_is_valid.adoc[ec.purl.is_valid] diff --git a/internal/rego/oci/__snapshots__/oci_test.snap b/internal/rego/oci/__snapshots__/oci_test.snap index a94b1e97c..721f4e406 100755 --- a/internal/rego/oci/__snapshots__/oci_test.snap +++ b/internal/rego/oci/__snapshots__/oci_test.snap @@ -867,3 +867,1077 @@ ] } --- + +[TestOCIDescriptorManifest/complete_image_manifest - 1] +{ + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "annotations" + }, + { + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "manifest.annotation.1" + }, + { + "type": "string", + "value": "config.annotation.value.1" + } + ], + [ + { + "type": "string", + "value": "manifest.annotation.2" + }, + { + "type": "string", + "value": "config.annotation.value.2" + } + ] + ] + } + ], + [ + { + "type": "string", + "value": "config" + }, + { + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "annotations" + }, + { + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "config.annotation.1" + }, + { + "type": "string", + "value": "config.annotation.value.1" + } + ], + [ + { + "type": "string", + "value": "config.annotation.2" + }, + { + "type": "string", + "value": "config.annotation.value.2" + } + ] + ] + } + ], + [ + { + "type": "string", + "value": "artifactType" + }, + { + "type": "string", + "value": "artifact-type" + } + ], + [ + { + "type": "string", + "value": "data" + }, + { + "type": "string", + "value": "{\"data\": \"config\"}" + } + ], + [ + { + "type": "string", + "value": "digest" + }, + { + "type": "string", + "value": "sha256:4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb" + } + ], + [ + { + "type": "string", + "value": "mediaType" + }, + { + "type": "string", + "value": "application/vnd.oci.image.config.v1+json" + } + ], + [ + { + "type": "string", + "value": "platform" + }, + { + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "architecture" + }, + { + "type": "string", + "value": "arch" + } + ], + [ + { + "type": "string", + "value": "features" + }, + { + "type": "array", + "value": [ + { + "type": "string", + "value": "feature-1" + }, + { + "type": "string", + "value": "feature-2" + } + ] + } + ], + [ + { + "type": "string", + "value": "os" + }, + { + "type": "string", + "value": "os" + } + ], + [ + { + "type": "string", + "value": "os.features" + }, + { + "type": "array", + "value": [ + { + "type": "string", + "value": "os-feature-1" + }, + { + "type": "string", + "value": "os-feature-2" + } + ] + } + ], + [ + { + "type": "string", + "value": "os.version" + }, + { + "type": "string", + "value": "os-version" + } + ], + [ + { + "type": "string", + "value": "variant" + }, + { + "type": "string", + "value": "variant" + } + ] + ] + } + ], + [ + { + "type": "string", + "value": "size" + }, + { + "type": "number", + "value": 123 + } + ], + [ + { + "type": "string", + "value": "urls" + }, + { + "type": "array", + "value": [ + { + "type": "string", + "value": "https://config-1.local/spam" + }, + { + "type": "string", + "value": "https://config-2.local/spam" + } + ] + } + ] + ] + } + ], + [ + { + "type": "string", + "value": "layers" + }, + { + "type": "array", + "value": [ + { + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "annotations" + }, + { + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "layer.annotation.1" + }, + { + "type": "string", + "value": "layer.annotation.value.1" + } + ], + [ + { + "type": "string", + "value": "layer.annotation.2" + }, + { + "type": "string", + "value": "layer.annotation.value.2" + } + ] + ] + } + ], + [ + { + "type": "string", + "value": "artifactType" + }, + { + "type": "string", + "value": "artifact-type" + } + ], + [ + { + "type": "string", + "value": "data" + }, + { + "type": "string", + "value": "{\"data\": \"layer\"}" + } + ], + [ + { + "type": "string", + "value": "digest" + }, + { + "type": "string", + "value": "sha256:325392e8dd2826a53a9a35b7a7f8d71683cd27ebc2c73fee85dab673bc909b67" + } + ], + [ + { + "type": "string", + "value": "mediaType" + }, + { + "type": "string", + "value": "application/vnd.oci.image.layer.v1.tar+gzip" + } + ], + [ + { + "type": "string", + "value": "platform" + }, + { + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "architecture" + }, + { + "type": "string", + "value": "arch" + } + ], + [ + { + "type": "string", + "value": "features" + }, + { + "type": "array", + "value": [ + { + "type": "string", + "value": "feature-1" + }, + { + "type": "string", + "value": "feature-2" + } + ] + } + ], + [ + { + "type": "string", + "value": "os" + }, + { + "type": "string", + "value": "os" + } + ], + [ + { + "type": "string", + "value": "os.features" + }, + { + "type": "array", + "value": [ + { + "type": "string", + "value": "os-feature-1" + }, + { + "type": "string", + "value": "os-feature-2" + } + ] + } + ], + [ + { + "type": "string", + "value": "os.version" + }, + { + "type": "string", + "value": "os-version" + } + ], + [ + { + "type": "string", + "value": "variant" + }, + { + "type": "string", + "value": "variant" + } + ] + ] + } + ], + [ + { + "type": "string", + "value": "size" + }, + { + "type": "number", + "value": 9999 + } + ], + [ + { + "type": "string", + "value": "urls" + }, + { + "type": "array", + "value": [ + { + "type": "string", + "value": "https://layer-1.local/spam" + }, + { + "type": "string", + "value": "https://layer-2.local/spam" + } + ] + } + ] + ] + } + ] + } + ], + [ + { + "type": "string", + "value": "mediaType" + }, + { + "type": "string", + "value": "application/vnd.oci.image.manifest.v1+json" + } + ], + [ + { + "type": "string", + "value": "schemaVersion" + }, + { + "type": "number", + "value": 2 + } + ], + [ + { + "type": "string", + "value": "subject" + }, + { + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "annotations" + }, + { + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "subject.annotation.1" + }, + { + "type": "string", + "value": "subject.annotation.value.1" + } + ], + [ + { + "type": "string", + "value": "subject.annotation.2" + }, + { + "type": "string", + "value": "subject.annotation.value.2" + } + ] + ] + } + ], + [ + { + "type": "string", + "value": "artifactType" + }, + { + "type": "string", + "value": "artifact-type" + } + ], + [ + { + "type": "string", + "value": "data" + }, + { + "type": "string", + "value": "{\"data\": \"subject\"}" + } + ], + [ + { + "type": "string", + "value": "digest" + }, + { + "type": "string", + "value": "sha256:d9298a10d1b0735837dc4bd85dac641b0f3cef27a47e5d53a54f2f3f5b2fcffa" + } + ], + [ + { + "type": "string", + "value": "mediaType" + }, + { + "type": "string", + "value": "application/vnd.oci.image.manifest.v1+json" + } + ], + [ + { + "type": "string", + "value": "platform" + }, + { + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "architecture" + }, + { + "type": "string", + "value": "arch" + } + ], + [ + { + "type": "string", + "value": "features" + }, + { + "type": "array", + "value": [ + { + "type": "string", + "value": "feature-1" + }, + { + "type": "string", + "value": "feature-2" + } + ] + } + ], + [ + { + "type": "string", + "value": "os" + }, + { + "type": "string", + "value": "os" + } + ], + [ + { + "type": "string", + "value": "os.features" + }, + { + "type": "array", + "value": [ + { + "type": "string", + "value": "os-feature-1" + }, + { + "type": "string", + "value": "os-feature-2" + } + ] + } + ], + [ + { + "type": "string", + "value": "os.version" + }, + { + "type": "string", + "value": "os-version" + } + ], + [ + { + "type": "string", + "value": "variant" + }, + { + "type": "string", + "value": "variant" + } + ] + ] + } + ], + [ + { + "type": "string", + "value": "size" + }, + { + "type": "number", + "value": 8888 + } + ], + [ + { + "type": "string", + "value": "urls" + }, + { + "type": "array", + "value": [ + { + "type": "string", + "value": "https://subject-1.local/spam" + }, + { + "type": "string", + "value": "https://subject-2.local/spam" + } + ] + } + ] + ] + } + ] + ] +} +--- + +[TestOCIDescriptorManifest/minimal_image_manifest - 1] +{ + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "annotations" + }, + { + "type": "object", + "value": [] + } + ], + [ + { + "type": "string", + "value": "config" + }, + { + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "annotations" + }, + { + "type": "object", + "value": [] + } + ], + [ + { + "type": "string", + "value": "artifactType" + }, + { + "type": "string", + "value": "" + } + ], + [ + { + "type": "string", + "value": "data" + }, + { + "type": "string", + "value": "" + } + ], + [ + { + "type": "string", + "value": "digest" + }, + { + "type": "string", + "value": "sha256:4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb" + } + ], + [ + { + "type": "string", + "value": "mediaType" + }, + { + "type": "string", + "value": "application/vnd.oci.image.config.v1+json" + } + ], + [ + { + "type": "string", + "value": "size" + }, + { + "type": "number", + "value": 123 + } + ], + [ + { + "type": "string", + "value": "urls" + }, + { + "type": "array", + "value": [] + } + ] + ] + } + ], + [ + { + "type": "string", + "value": "layers" + }, + { + "type": "array", + "value": [ + { + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "annotations" + }, + { + "type": "object", + "value": [] + } + ], + [ + { + "type": "string", + "value": "artifactType" + }, + { + "type": "string", + "value": "" + } + ], + [ + { + "type": "string", + "value": "data" + }, + { + "type": "string", + "value": "" + } + ], + [ + { + "type": "string", + "value": "digest" + }, + { + "type": "string", + "value": "sha256:325392e8dd2826a53a9a35b7a7f8d71683cd27ebc2c73fee85dab673bc909b67" + } + ], + [ + { + "type": "string", + "value": "mediaType" + }, + { + "type": "string", + "value": "application/vnd.oci.image.layer.v1.tar+gzip" + } + ], + [ + { + "type": "string", + "value": "size" + }, + { + "type": "number", + "value": 9999 + } + ], + [ + { + "type": "string", + "value": "urls" + }, + { + "type": "array", + "value": [] + } + ] + ] + } + ] + } + ], + [ + { + "type": "string", + "value": "mediaType" + }, + { + "type": "string", + "value": "application/vnd.oci.image.manifest.v1+json" + } + ], + [ + { + "type": "string", + "value": "schemaVersion" + }, + { + "type": "number", + "value": 2 + } + ] + ] +} +--- + +[TestOCIDescriptorManifest/minimal_image_index - 1] +{ + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "annotations" + }, + { + "type": "object", + "value": [] + } + ], + [ + { + "type": "string", + "value": "config" + }, + { + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "annotations" + }, + { + "type": "object", + "value": [] + } + ], + [ + { + "type": "string", + "value": "artifactType" + }, + { + "type": "string", + "value": "" + } + ], + [ + { + "type": "string", + "value": "data" + }, + { + "type": "string", + "value": "" + } + ], + [ + { + "type": "string", + "value": "digest" + }, + { + "type": "string", + "value": "sha256:4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb" + } + ], + [ + { + "type": "string", + "value": "mediaType" + }, + { + "type": "string", + "value": "application/vnd.oci.image.config.v1+json" + } + ], + [ + { + "type": "string", + "value": "size" + }, + { + "type": "number", + "value": 123 + } + ], + [ + { + "type": "string", + "value": "urls" + }, + { + "type": "array", + "value": [] + } + ] + ] + } + ], + [ + { + "type": "string", + "value": "layers" + }, + { + "type": "array", + "value": [ + { + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "annotations" + }, + { + "type": "object", + "value": [] + } + ], + [ + { + "type": "string", + "value": "artifactType" + }, + { + "type": "string", + "value": "" + } + ], + [ + { + "type": "string", + "value": "data" + }, + { + "type": "string", + "value": "" + } + ], + [ + { + "type": "string", + "value": "digest" + }, + { + "type": "string", + "value": "sha256:325392e8dd2826a53a9a35b7a7f8d71683cd27ebc2c73fee85dab673bc909b67" + } + ], + [ + { + "type": "string", + "value": "mediaType" + }, + { + "type": "string", + "value": "application/vnd.oci.image.layer.v1.tar+gzip" + } + ], + [ + { + "type": "string", + "value": "size" + }, + { + "type": "number", + "value": 9999 + } + ], + [ + { + "type": "string", + "value": "urls" + }, + { + "type": "array", + "value": [] + } + ] + ] + } + ] + } + ], + [ + { + "type": "string", + "value": "mediaType" + }, + { + "type": "string", + "value": "application/vnd.oci.image.manifest.v1+json" + } + ], + [ + { + "type": "string", + "value": "schemaVersion" + }, + { + "type": "number", + "value": 2 + } + ] + ] +} +--- diff --git a/internal/rego/oci/oci.go b/internal/rego/oci/oci.go index f66142799..0839b90ec 100644 --- a/internal/rego/oci/oci.go +++ b/internal/rego/oci/oci.go @@ -41,6 +41,7 @@ import ( const ( ociBlobName = "ec.oci.blob" + ociDescriptorName = "ec.oci.descriptor" ociImageManifestName = "ec.oci.image_manifest" ociImageFilesName = "ec.oci.image_files" ) @@ -73,6 +74,43 @@ func registerOCIBlob() { }) } +func registerOCIDescriptor() { + manifest := types.NewObject( + []*types.StaticProperty{ + // Specifying the properties like this ensure the compiler catches typos when + // evaluating rego functions. + {Key: "mediaType", Value: types.S}, + }, + nil, + ) + + decl := rego.Function{ + Name: ociDescriptorName, + Decl: types.NewFunction( + types.Args( + types.Named("ref", types.S).Description("OCI descriptor reference"), + ), + types.Named("object", manifest).Description("the OCI descriptor object"), + ), + // As per the documentation, enable memoization to ensure function evaluation is + // deterministic. But also mark it as non-deterministic because it does rely on external + // entities, i.e. OCI registry. https://www.openpolicyagent.org/docs/latest/extensions/ + Memoize: true, + Nondeterministic: true, + } + + rego.RegisterBuiltin1(&decl, ociDescriptor) + // Due to https://github.com/open-policy-agent/opa/issues/6449, we cannot set a description for + // the custom function through the call above. As a workaround we re-register the function with + // a declaration that does include the description. + ast.RegisterBuiltin(&ast.Builtin{ + Name: decl.Name, + Description: "Fetch a raw Image from an OCI registry.", + Decl: decl.Decl, + Nondeterministic: decl.Nondeterministic, + }) +} + func registerOCIImageManifest() { platform := types.NewObject( []*types.StaticProperty{ @@ -225,6 +263,28 @@ func ociBlob(bctx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { return ast.StringTerm(blob.String()), nil } +func ociDescriptor(bctx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { + log := log.WithField("rego", ociDescriptor) + uri, ok := a.Value.(ast.String) + if !ok { + return nil, nil + } + + ref, err := name.NewDigest(string(uri)) + if err != nil { + log.Errorf("new digest: %s", err) + return nil, nil + } + + descriptor, err := oci.NewClient(bctx.Context).Head(ref) + if err != nil { + log.Errorf("fetch image: %s", err) + return nil, nil + } + + return newDescriptorTerm(*descriptor), nil +} + func ociImageManifest(bctx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { log := log.WithField("rego", ociImageManifestName) uri, ok := a.Value.(ast.String) @@ -378,6 +438,7 @@ func newAnnotationsTerm(annotations map[string]string) *ast.Term { func init() { registerOCIBlob() + registerOCIDescriptor() registerOCIImageFiles() registerOCIImageManifest() } diff --git a/internal/rego/oci/oci_test.go b/internal/rego/oci/oci_test.go index dbf303369..d1737093b 100644 --- a/internal/rego/oci/oci_test.go +++ b/internal/rego/oci/oci_test.go @@ -110,6 +110,352 @@ func TestOCIBlob(t *testing.T) { } } +func TestOCIDescriptorManifest(t *testing.T) { + cases := []struct { + name string + ref *ast.Term + manifest *v1.Manifest + imageErr error + manifestErr error + wantErr bool + }{ + { + name: "complete image manifest", + ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), + manifest: &v1.Manifest{ + SchemaVersion: 2, + MediaType: types.OCIManifestSchema1, + Config: v1.Descriptor{ + MediaType: types.OCIConfigJSON, + Size: 123, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb", + }, + Data: []byte(`{"data": "config"}`), + URLs: []string{"https://config-1.local/spam", "https://config-2.local/spam"}, + Annotations: map[string]string{ + "config.annotation.1": "config.annotation.value.1", + "config.annotation.2": "config.annotation.value.2", + }, + Platform: &v1.Platform{ + Architecture: "arch", + OS: "os", + OSVersion: "os-version", + OSFeatures: []string{"os-feature-1", "os-feature-2"}, + Variant: "variant", + Features: []string{"feature-1", "feature-2"}, + }, + ArtifactType: "artifact-type", + }, + Layers: []v1.Descriptor{ + { + MediaType: types.OCILayer, + Size: 9999, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "325392e8dd2826a53a9a35b7a7f8d71683cd27ebc2c73fee85dab673bc909b67", + }, + Data: []byte(`{"data": "layer"}`), + URLs: []string{"https://layer-1.local/spam", "https://layer-2.local/spam"}, + Annotations: map[string]string{ + "layer.annotation.1": "layer.annotation.value.1", + "layer.annotation.2": "layer.annotation.value.2", + }, + Platform: &v1.Platform{ + Architecture: "arch", + OS: "os", + OSVersion: "os-version", + OSFeatures: []string{"os-feature-1", "os-feature-2"}, + Variant: "variant", + Features: []string{"feature-1", "feature-2"}, + }, + ArtifactType: "artifact-type", + }, + }, + Annotations: map[string]string{ + "manifest.annotation.1": "config.annotation.value.1", + "manifest.annotation.2": "config.annotation.value.2", + }, + Subject: &v1.Descriptor{ + MediaType: types.OCIManifestSchema1, + Size: 8888, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "d9298a10d1b0735837dc4bd85dac641b0f3cef27a47e5d53a54f2f3f5b2fcffa", + }, + Data: []byte(`{"data": "subject"}`), + URLs: []string{"https://subject-1.local/spam", "https://subject-2.local/spam"}, + Annotations: map[string]string{ + "subject.annotation.1": "subject.annotation.value.1", + "subject.annotation.2": "subject.annotation.value.2", + }, + Platform: &v1.Platform{ + Architecture: "arch", + OS: "os", + OSVersion: "os-version", + OSFeatures: []string{"os-feature-1", "os-feature-2"}, + Variant: "variant", + Features: []string{"feature-1", "feature-2"}, + }, + ArtifactType: "artifact-type", + }, + }, + }, + { + name: "minimal image manifest", + ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), + manifest: &v1.Manifest{ + SchemaVersion: 2, + MediaType: types.OCIManifestSchema1, + Config: v1.Descriptor{ + MediaType: types.OCIConfigJSON, + Size: 123, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb", + }, + }, + Layers: []v1.Descriptor{ + { + MediaType: types.OCILayer, + Size: 9999, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "325392e8dd2826a53a9a35b7a7f8d71683cd27ebc2c73fee85dab673bc909b67", + }, + }, + }, + }, + }, + { + name: "minimal image index", + ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), + manifest: &v1.Manifest{ + SchemaVersion: 2, + MediaType: types.OCIManifestSchema1, + Config: v1.Descriptor{ + MediaType: types.OCIConfigJSON, + Size: 123, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb", + }, + }, + Layers: []v1.Descriptor{ + { + MediaType: types.OCILayer, + Size: 9999, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "325392e8dd2826a53a9a35b7a7f8d71683cd27ebc2c73fee85dab673bc909b67", + }, + }, + }, + }, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + client := fake.FakeClient{} + if c.imageErr != nil { + client.On("Head", mock.Anything, mock.Anything).Return(nil, c.imageErr) + } else { + imageManifest := v1fake.FakeImage{} + imageManifest.ManifestReturns(c.manifest, c.manifestErr) + client.On("Head", mock.Anything, mock.Anything).Return(&imageManifest, nil) + } + ctx := oci.WithClient(context.Background(), &client) + bctx := rego.BuiltinContext{Context: ctx} + + got, err := ociDescriptor(bctx, c.ref) + require.NoError(t, err) + if c.wantErr { + require.Nil(t, got) + } else { + require.NotNil(t, got) + snaps.MatchJSON(t, got) + } + }) + } +} + +func TestOCIDescriptorErrors(t *testing.T) { + cases := []struct { + name string + ref *ast.Term + manifest *v1.Manifest + imageErr error + manifestErr error + wantErr bool + }{ + { + name: "missing digest", + ref: ast.StringTerm("registry.local/spam:latest"), + wantErr: true, + }, + { + name: "bad image ref", + ref: ast.StringTerm("......registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), + wantErr: true, + }, + { + name: "invalid ref type", + ref: ast.IntNumberTerm(42), + wantErr: true, + }, + { + name: "image error", + ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), + manifestErr: errors.New("kaboom!"), + wantErr: true, + }, + { + name: "nil manifest", + ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), + manifest: nil, + wantErr: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + client := fake.FakeClient{} + if c.imageErr != nil { + client.On("Head", mock.Anything, mock.Anything).Return(nil, c.imageErr) + } else { + imageManifest := v1fake.FakeImage{} + imageManifest.ManifestReturns(c.manifest, c.manifestErr) + client.On("Head", mock.Anything, mock.Anything).Return(&imageManifest, nil) + } + ctx := oci.WithClient(context.Background(), &client) + bctx := rego.BuiltinContext{Context: ctx} + + got, err := ociDescriptor(bctx, c.ref) + require.NoError(t, err) + if c.wantErr { + require.Nil(t, got) + } else { + require.NotNil(t, got) + snaps.MatchJSON(t, got) + } + }) + } +} + +func TestOCIDescriptorIndex(t *testing.T) { + cases := []struct { + name string + ref *ast.Term + manifest *v1.IndexManifest + imageErr error + manifestErr error + wantErr bool + }{ + { + name: "complete image index", + ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), + manifest: &v1.IndexManifest{ + SchemaVersion: 2, + MediaType: types.OCIImageIndex, + Manifests: []v1.Descriptor{ + { + MediaType: types.OCIManifestSchema1, + Size: 123, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb", + }, + Annotations: map[string]string{ + "config.annotation.1": "config.annotation.value.1", + "config.annotation.2": "config.annotation.value.2", + }, + Platform: &v1.Platform{ + Architecture: "arch", + OS: "os", + OSVersion: "os-version", + OSFeatures: []string{"os-feature-1", "os-feature-2"}, + Variant: "variant", + Features: []string{"feature-1", "feature-2"}, + }, + ArtifactType: "artifact-type", + }, + }, + Annotations: map[string]string{ + "manifest.annotation.1": "config.annotation.value.1", + "manifest.annotation.2": "config.annotation.value.2", + }, + Subject: &v1.Descriptor{ + MediaType: types.OCIManifestSchema1, + Size: 8888, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "d9298a10d1b0735837dc4bd85dac641b0f3cef27a47e5d53a54f2f3f5b2fcffa", + }, + Data: []byte(`{"data": "subject"}`), + URLs: []string{"https://subject-1.local/spam", "https://subject-2.local/spam"}, + Annotations: map[string]string{ + "subject.annotation.1": "subject.annotation.value.1", + "subject.annotation.2": "subject.annotation.value.2", + }, + Platform: &v1.Platform{ + Architecture: "arch", + OS: "os", + OSVersion: "os-version", + OSFeatures: []string{"os-feature-1", "os-feature-2"}, + Variant: "variant", + Features: []string{"feature-1", "feature-2"}, + }, + ArtifactType: "artifact-type", + }, + }, + }, + { + name: "minimal image index", + ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), + manifest: &v1.IndexManifest{ + SchemaVersion: 2, + MediaType: types.OCIImageIndex, + Manifests: []v1.Descriptor{ + { + MediaType: types.OCILayer, + Size: 9999, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "325392e8dd2826a53a9a35b7a7f8d71683cd27ebc2c73fee85dab673bc909b67", + }, + }, + }, + }, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + client := fake.FakeClient{} + if c.imageErr != nil { + client.On("Head", mock.Anything, mock.Anything).Return(nil, c.imageErr) + } else { + imageIndex := v1fake.FakeImageIndex{} + imageIndex.IndexManifestReturns(c.manifest, c.manifestErr) + client.On("Head", mock.Anything, mock.Anything).Return(&imageIndex, nil) + } + ctx := oci.WithClient(context.Background(), &client) + bctx := rego.BuiltinContext{Context: ctx} + + got, err := ociDescriptor(bctx, c.ref) + require.NoError(t, err) + if c.wantErr { + require.Nil(t, got) + } else { + require.NotNil(t, got) + snaps.MatchJSON(t, got) + } + }) + } +} + func TestOCIImageManifest(t *testing.T) { cases := []struct { name string @@ -360,6 +706,7 @@ func TestOCIImageFiles(t *testing.T) { func TestFunctionsRegistered(t *testing.T) { names := []string{ ociBlobName, + ociDescriptorName, ociImageFilesName, ociImageManifestName, } From 012e6a57a1791fc93a9d039dbbb6ecbc1c5ce8f1 Mon Sep 17 00:00:00 2001 From: Zoran Regvart Date: Mon, 16 Dec 2024 15:52:11 +0100 Subject: [PATCH 2/3] Something? --- internal/rego/oci/__snapshots__/oci_test.snap | 1133 +++-------------- internal/rego/oci/oci_test.go | 326 +---- 2 files changed, 254 insertions(+), 1205 deletions(-) diff --git a/internal/rego/oci/__snapshots__/oci_test.snap b/internal/rego/oci/__snapshots__/oci_test.snap index 721f4e406..59994275e 100755 --- a/internal/rego/oci/__snapshots__/oci_test.snap +++ b/internal/rego/oci/__snapshots__/oci_test.snap @@ -883,745 +883,21 @@ [ { "type": "string", - "value": "manifest.annotation.1" - }, - { - "type": "string", - "value": "config.annotation.value.1" - } - ], - [ - { - "type": "string", - "value": "manifest.annotation.2" - }, - { - "type": "string", - "value": "config.annotation.value.2" - } - ] - ] - } - ], - [ - { - "type": "string", - "value": "config" - }, - { - "type": "object", - "value": [ - [ - { - "type": "string", - "value": "annotations" - }, - { - "type": "object", - "value": [ - [ - { - "type": "string", - "value": "config.annotation.1" - }, - { - "type": "string", - "value": "config.annotation.value.1" - } - ], - [ - { - "type": "string", - "value": "config.annotation.2" - }, - { - "type": "string", - "value": "config.annotation.value.2" - } - ] - ] - } - ], - [ - { - "type": "string", - "value": "artifactType" - }, - { - "type": "string", - "value": "artifact-type" - } - ], - [ - { - "type": "string", - "value": "data" - }, - { - "type": "string", - "value": "{\"data\": \"config\"}" - } - ], - [ - { - "type": "string", - "value": "digest" - }, - { - "type": "string", - "value": "sha256:4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb" - } - ], - [ - { - "type": "string", - "value": "mediaType" - }, - { - "type": "string", - "value": "application/vnd.oci.image.config.v1+json" - } - ], - [ - { - "type": "string", - "value": "platform" - }, - { - "type": "object", - "value": [ - [ - { - "type": "string", - "value": "architecture" - }, - { - "type": "string", - "value": "arch" - } - ], - [ - { - "type": "string", - "value": "features" - }, - { - "type": "array", - "value": [ - { - "type": "string", - "value": "feature-1" - }, - { - "type": "string", - "value": "feature-2" - } - ] - } - ], - [ - { - "type": "string", - "value": "os" - }, - { - "type": "string", - "value": "os" - } - ], - [ - { - "type": "string", - "value": "os.features" - }, - { - "type": "array", - "value": [ - { - "type": "string", - "value": "os-feature-1" - }, - { - "type": "string", - "value": "os-feature-2" - } - ] - } - ], - [ - { - "type": "string", - "value": "os.version" - }, - { - "type": "string", - "value": "os-version" - } - ], - [ - { - "type": "string", - "value": "variant" - }, - { - "type": "string", - "value": "variant" - } - ] - ] - } - ], - [ - { - "type": "string", - "value": "size" - }, - { - "type": "number", - "value": 123 - } - ], - [ - { - "type": "string", - "value": "urls" - }, - { - "type": "array", - "value": [ - { - "type": "string", - "value": "https://config-1.local/spam" - }, - { - "type": "string", - "value": "https://config-2.local/spam" - } - ] - } - ] - ] - } - ], - [ - { - "type": "string", - "value": "layers" - }, - { - "type": "array", - "value": [ - { - "type": "object", - "value": [ - [ - { - "type": "string", - "value": "annotations" - }, - { - "type": "object", - "value": [ - [ - { - "type": "string", - "value": "layer.annotation.1" - }, - { - "type": "string", - "value": "layer.annotation.value.1" - } - ], - [ - { - "type": "string", - "value": "layer.annotation.2" - }, - { - "type": "string", - "value": "layer.annotation.value.2" - } - ] - ] - } - ], - [ - { - "type": "string", - "value": "artifactType" - }, - { - "type": "string", - "value": "artifact-type" - } - ], - [ - { - "type": "string", - "value": "data" - }, - { - "type": "string", - "value": "{\"data\": \"layer\"}" - } - ], - [ - { - "type": "string", - "value": "digest" - }, - { - "type": "string", - "value": "sha256:325392e8dd2826a53a9a35b7a7f8d71683cd27ebc2c73fee85dab673bc909b67" - } - ], - [ - { - "type": "string", - "value": "mediaType" - }, - { - "type": "string", - "value": "application/vnd.oci.image.layer.v1.tar+gzip" - } - ], - [ - { - "type": "string", - "value": "platform" - }, - { - "type": "object", - "value": [ - [ - { - "type": "string", - "value": "architecture" - }, - { - "type": "string", - "value": "arch" - } - ], - [ - { - "type": "string", - "value": "features" - }, - { - "type": "array", - "value": [ - { - "type": "string", - "value": "feature-1" - }, - { - "type": "string", - "value": "feature-2" - } - ] - } - ], - [ - { - "type": "string", - "value": "os" - }, - { - "type": "string", - "value": "os" - } - ], - [ - { - "type": "string", - "value": "os.features" - }, - { - "type": "array", - "value": [ - { - "type": "string", - "value": "os-feature-1" - }, - { - "type": "string", - "value": "os-feature-2" - } - ] - } - ], - [ - { - "type": "string", - "value": "os.version" - }, - { - "type": "string", - "value": "os-version" - } - ], - [ - { - "type": "string", - "value": "variant" - }, - { - "type": "string", - "value": "variant" - } - ] - ] - } - ], - [ - { - "type": "string", - "value": "size" - }, - { - "type": "number", - "value": 9999 - } - ], - [ - { - "type": "string", - "value": "urls" - }, - { - "type": "array", - "value": [ - { - "type": "string", - "value": "https://layer-1.local/spam" - }, - { - "type": "string", - "value": "https://layer-2.local/spam" - } - ] - } - ] - ] - } - ] - } - ], - [ - { - "type": "string", - "value": "mediaType" - }, - { - "type": "string", - "value": "application/vnd.oci.image.manifest.v1+json" - } - ], - [ - { - "type": "string", - "value": "schemaVersion" - }, - { - "type": "number", - "value": 2 - } - ], - [ - { - "type": "string", - "value": "subject" - }, - { - "type": "object", - "value": [ - [ - { - "type": "string", - "value": "annotations" - }, - { - "type": "object", - "value": [ - [ - { - "type": "string", - "value": "subject.annotation.1" - }, - { - "type": "string", - "value": "subject.annotation.value.1" - } - ], - [ - { - "type": "string", - "value": "subject.annotation.2" - }, - { - "type": "string", - "value": "subject.annotation.value.2" - } - ] - ] - } - ], - [ - { - "type": "string", - "value": "artifactType" - }, - { - "type": "string", - "value": "artifact-type" - } - ], - [ - { - "type": "string", - "value": "data" - }, - { - "type": "string", - "value": "{\"data\": \"subject\"}" - } - ], - [ - { - "type": "string", - "value": "digest" - }, - { - "type": "string", - "value": "sha256:d9298a10d1b0735837dc4bd85dac641b0f3cef27a47e5d53a54f2f3f5b2fcffa" - } - ], - [ - { - "type": "string", - "value": "mediaType" - }, - { - "type": "string", - "value": "application/vnd.oci.image.manifest.v1+json" - } - ], - [ - { - "type": "string", - "value": "platform" - }, - { - "type": "object", - "value": [ - [ - { - "type": "string", - "value": "architecture" - }, - { - "type": "string", - "value": "arch" - } - ], - [ - { - "type": "string", - "value": "features" - }, - { - "type": "array", - "value": [ - { - "type": "string", - "value": "feature-1" - }, - { - "type": "string", - "value": "feature-2" - } - ] - } - ], - [ - { - "type": "string", - "value": "os" - }, - { - "type": "string", - "value": "os" - } - ], - [ - { - "type": "string", - "value": "os.features" - }, - { - "type": "array", - "value": [ - { - "type": "string", - "value": "os-feature-1" - }, - { - "type": "string", - "value": "os-feature-2" - } - ] - } - ], - [ - { - "type": "string", - "value": "os.version" - }, - { - "type": "string", - "value": "os-version" - } - ], - [ - { - "type": "string", - "value": "variant" - }, - { - "type": "string", - "value": "variant" - } - ] - ] - } - ], - [ - { - "type": "string", - "value": "size" - }, - { - "type": "number", - "value": 8888 - } - ], - [ - { - "type": "string", - "value": "urls" - }, - { - "type": "array", - "value": [ - { - "type": "string", - "value": "https://subject-1.local/spam" - }, - { - "type": "string", - "value": "https://subject-2.local/spam" - } - ] - } - ] - ] - } - ] - ] -} ---- - -[TestOCIDescriptorManifest/minimal_image_manifest - 1] -{ - "type": "object", - "value": [ - [ - { - "type": "string", - "value": "annotations" - }, - { - "type": "object", - "value": [] - } - ], - [ - { - "type": "string", - "value": "config" - }, - { - "type": "object", - "value": [ - [ - { - "type": "string", - "value": "annotations" - }, - { - "type": "object", - "value": [] - } - ], - [ - { - "type": "string", - "value": "artifactType" - }, - { - "type": "string", - "value": "" - } - ], - [ - { - "type": "string", - "value": "data" - }, - { - "type": "string", - "value": "" - } - ], - [ - { - "type": "string", - "value": "digest" - }, - { - "type": "string", - "value": "sha256:4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb" - } - ], - [ - { - "type": "string", - "value": "mediaType" + "value": "config.annotation.1" }, { "type": "string", - "value": "application/vnd.oci.image.config.v1+json" + "value": "config.annotation.value.1" } ], [ { "type": "string", - "value": "size" + "value": "config.annotation.2" }, - { - "type": "number", - "value": 123 - } - ], - [ { "type": "string", - "value": "urls" - }, - { - "type": "array", - "value": [] + "value": "config.annotation.value.2" } ] ] @@ -1630,131 +906,47 @@ [ { "type": "string", - "value": "layers" + "value": "artifactType" }, { - "type": "array", - "value": [ - { - "type": "object", - "value": [ - [ - { - "type": "string", - "value": "annotations" - }, - { - "type": "object", - "value": [] - } - ], - [ - { - "type": "string", - "value": "artifactType" - }, - { - "type": "string", - "value": "" - } - ], - [ - { - "type": "string", - "value": "data" - }, - { - "type": "string", - "value": "" - } - ], - [ - { - "type": "string", - "value": "digest" - }, - { - "type": "string", - "value": "sha256:325392e8dd2826a53a9a35b7a7f8d71683cd27ebc2c73fee85dab673bc909b67" - } - ], - [ - { - "type": "string", - "value": "mediaType" - }, - { - "type": "string", - "value": "application/vnd.oci.image.layer.v1.tar+gzip" - } - ], - [ - { - "type": "string", - "value": "size" - }, - { - "type": "number", - "value": 9999 - } - ], - [ - { - "type": "string", - "value": "urls" - }, - { - "type": "array", - "value": [] - } - ] - ] - } - ] + "type": "string", + "value": "artifact-type" } ], [ { "type": "string", - "value": "mediaType" + "value": "data" }, { "type": "string", - "value": "application/vnd.oci.image.manifest.v1+json" + "value": "{\"data\": \"config\"}" } ], [ { "type": "string", - "value": "schemaVersion" + "value": "digest" }, { - "type": "number", - "value": 2 + "type": "string", + "value": "sha256:4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb" } - ] - ] -} ---- - -[TestOCIDescriptorManifest/minimal_image_index - 1] -{ - "type": "object", - "value": [ + ], [ { "type": "string", - "value": "annotations" + "value": "mediaType" }, { - "type": "object", - "value": [] + "type": "string", + "value": "application/vnd.oci.image.config.v1+json" } ], [ { "type": "string", - "value": "config" + "value": "platform" }, { "type": "object", @@ -1762,71 +954,79 @@ [ { "type": "string", - "value": "annotations" + "value": "architecture" }, { - "type": "object", - "value": [] + "type": "string", + "value": "arch" } ], [ { "type": "string", - "value": "artifactType" + "value": "features" }, { - "type": "string", - "value": "" + "type": "array", + "value": [ + { + "type": "string", + "value": "feature-1" + }, + { + "type": "string", + "value": "feature-2" + } + ] } ], [ { "type": "string", - "value": "data" + "value": "os" }, { "type": "string", - "value": "" + "value": "os" } ], [ { "type": "string", - "value": "digest" + "value": "os.features" }, { - "type": "string", - "value": "sha256:4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb" + "type": "array", + "value": [ + { + "type": "string", + "value": "os-feature-1" + }, + { + "type": "string", + "value": "os-feature-2" + } + ] } ], [ { "type": "string", - "value": "mediaType" + "value": "os.version" }, { "type": "string", - "value": "application/vnd.oci.image.config.v1+json" + "value": "os-version" } ], [ { "type": "string", - "value": "size" + "value": "variant" }, - { - "type": "number", - "value": 123 - } - ], - [ { "type": "string", - "value": "urls" - }, - { - "type": "array", - "value": [] + "value": "variant" } ] ] @@ -1835,88 +1035,79 @@ [ { "type": "string", - "value": "layers" + "value": "size" + }, + { + "type": "number", + "value": 123 + } + ], + [ + { + "type": "string", + "value": "urls" }, { "type": "array", "value": [ { - "type": "object", - "value": [ - [ - { - "type": "string", - "value": "annotations" - }, - { - "type": "object", - "value": [] - } - ], - [ - { - "type": "string", - "value": "artifactType" - }, - { - "type": "string", - "value": "" - } - ], - [ - { - "type": "string", - "value": "data" - }, - { - "type": "string", - "value": "" - } - ], - [ - { - "type": "string", - "value": "digest" - }, - { - "type": "string", - "value": "sha256:325392e8dd2826a53a9a35b7a7f8d71683cd27ebc2c73fee85dab673bc909b67" - } - ], - [ - { - "type": "string", - "value": "mediaType" - }, - { - "type": "string", - "value": "application/vnd.oci.image.layer.v1.tar+gzip" - } - ], - [ - { - "type": "string", - "value": "size" - }, - { - "type": "number", - "value": 9999 - } - ], - [ - { - "type": "string", - "value": "urls" - }, - { - "type": "array", - "value": [] - } - ] - ] + "type": "string", + "value": "https://config-1.local/spam" + }, + { + "type": "string", + "value": "https://config-2.local/spam" } ] } + ] + ] +} +--- + +[TestOCIDescriptorManifest/minimal_image_manifest - 1] +{ + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "annotations" + }, + { + "type": "object", + "value": [] + } + ], + [ + { + "type": "string", + "value": "artifactType" + }, + { + "type": "string", + "value": "" + } + ], + [ + { + "type": "string", + "value": "data" + }, + { + "type": "string", + "value": "" + } + ], + [ + { + "type": "string", + "value": "digest" + }, + { + "type": "string", + "value": "sha256:4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb" + } ], [ { @@ -1925,17 +1116,105 @@ }, { "type": "string", - "value": "application/vnd.oci.image.manifest.v1+json" + "value": "application/vnd.oci.image.config.v1+json" } ], [ { "type": "string", - "value": "schemaVersion" + "value": "size" }, { "type": "number", - "value": 2 + "value": 123 + } + ], + [ + { + "type": "string", + "value": "urls" + }, + { + "type": "array", + "value": [] + } + ] + ] +} +--- + +[TestOCIDescriptorManifest/minimal_image_index - 1] +{ + "type": "object", + "value": [ + [ + { + "type": "string", + "value": "annotations" + }, + { + "type": "object", + "value": [] + } + ], + [ + { + "type": "string", + "value": "artifactType" + }, + { + "type": "string", + "value": "" + } + ], + [ + { + "type": "string", + "value": "data" + }, + { + "type": "string", + "value": "" + } + ], + [ + { + "type": "string", + "value": "digest" + }, + { + "type": "string", + "value": "sha256:4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb" + } + ], + [ + { + "type": "string", + "value": "mediaType" + }, + { + "type": "string", + "value": "application/vnd.oci.image.config.v1+json" + } + ], + [ + { + "type": "string", + "value": "size" + }, + { + "type": "number", + "value": 123 + } + ], + [ + { + "type": "string", + "value": "urls" + }, + { + "type": "array", + "value": [] } ] ] diff --git a/internal/rego/oci/oci_test.go b/internal/rego/oci/oci_test.go index d1737093b..e178768f4 100644 --- a/internal/rego/oci/oci_test.go +++ b/internal/rego/oci/oci_test.go @@ -112,145 +112,59 @@ func TestOCIBlob(t *testing.T) { func TestOCIDescriptorManifest(t *testing.T) { cases := []struct { - name string - ref *ast.Term - manifest *v1.Manifest - imageErr error - manifestErr error - wantErr bool + name string + ref *ast.Term + descriptor *v1.Descriptor + err error }{ { name: "complete image manifest", ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), - manifest: &v1.Manifest{ - SchemaVersion: 2, - MediaType: types.OCIManifestSchema1, - Config: v1.Descriptor{ - MediaType: types.OCIConfigJSON, - Size: 123, - Digest: v1.Hash{ - Algorithm: "sha256", - Hex: "4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb", - }, - Data: []byte(`{"data": "config"}`), - URLs: []string{"https://config-1.local/spam", "https://config-2.local/spam"}, - Annotations: map[string]string{ - "config.annotation.1": "config.annotation.value.1", - "config.annotation.2": "config.annotation.value.2", - }, - Platform: &v1.Platform{ - Architecture: "arch", - OS: "os", - OSVersion: "os-version", - OSFeatures: []string{"os-feature-1", "os-feature-2"}, - Variant: "variant", - Features: []string{"feature-1", "feature-2"}, - }, - ArtifactType: "artifact-type", - }, - Layers: []v1.Descriptor{ - { - MediaType: types.OCILayer, - Size: 9999, - Digest: v1.Hash{ - Algorithm: "sha256", - Hex: "325392e8dd2826a53a9a35b7a7f8d71683cd27ebc2c73fee85dab673bc909b67", - }, - Data: []byte(`{"data": "layer"}`), - URLs: []string{"https://layer-1.local/spam", "https://layer-2.local/spam"}, - Annotations: map[string]string{ - "layer.annotation.1": "layer.annotation.value.1", - "layer.annotation.2": "layer.annotation.value.2", - }, - Platform: &v1.Platform{ - Architecture: "arch", - OS: "os", - OSVersion: "os-version", - OSFeatures: []string{"os-feature-1", "os-feature-2"}, - Variant: "variant", - Features: []string{"feature-1", "feature-2"}, - }, - ArtifactType: "artifact-type", - }, + descriptor: &v1.Descriptor{ + MediaType: types.OCIConfigJSON, + Size: 123, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb", }, + Data: []byte(`{"data": "config"}`), + URLs: []string{"https://config-1.local/spam", "https://config-2.local/spam"}, Annotations: map[string]string{ - "manifest.annotation.1": "config.annotation.value.1", - "manifest.annotation.2": "config.annotation.value.2", + "config.annotation.1": "config.annotation.value.1", + "config.annotation.2": "config.annotation.value.2", }, - Subject: &v1.Descriptor{ - MediaType: types.OCIManifestSchema1, - Size: 8888, - Digest: v1.Hash{ - Algorithm: "sha256", - Hex: "d9298a10d1b0735837dc4bd85dac641b0f3cef27a47e5d53a54f2f3f5b2fcffa", - }, - Data: []byte(`{"data": "subject"}`), - URLs: []string{"https://subject-1.local/spam", "https://subject-2.local/spam"}, - Annotations: map[string]string{ - "subject.annotation.1": "subject.annotation.value.1", - "subject.annotation.2": "subject.annotation.value.2", - }, - Platform: &v1.Platform{ - Architecture: "arch", - OS: "os", - OSVersion: "os-version", - OSFeatures: []string{"os-feature-1", "os-feature-2"}, - Variant: "variant", - Features: []string{"feature-1", "feature-2"}, - }, - ArtifactType: "artifact-type", + Platform: &v1.Platform{ + Architecture: "arch", + OS: "os", + OSVersion: "os-version", + OSFeatures: []string{"os-feature-1", "os-feature-2"}, + Variant: "variant", + Features: []string{"feature-1", "feature-2"}, }, + ArtifactType: "artifact-type", }, }, { name: "minimal image manifest", ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), - manifest: &v1.Manifest{ - SchemaVersion: 2, - MediaType: types.OCIManifestSchema1, - Config: v1.Descriptor{ - MediaType: types.OCIConfigJSON, - Size: 123, - Digest: v1.Hash{ - Algorithm: "sha256", - Hex: "4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb", - }, - }, - Layers: []v1.Descriptor{ - { - MediaType: types.OCILayer, - Size: 9999, - Digest: v1.Hash{ - Algorithm: "sha256", - Hex: "325392e8dd2826a53a9a35b7a7f8d71683cd27ebc2c73fee85dab673bc909b67", - }, - }, + descriptor: &v1.Descriptor{ + MediaType: types.OCIConfigJSON, + Size: 123, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb", }, }, }, { name: "minimal image index", ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), - manifest: &v1.Manifest{ - SchemaVersion: 2, - MediaType: types.OCIManifestSchema1, - Config: v1.Descriptor{ - MediaType: types.OCIConfigJSON, - Size: 123, - Digest: v1.Hash{ - Algorithm: "sha256", - Hex: "4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb", - }, - }, - Layers: []v1.Descriptor{ - { - MediaType: types.OCILayer, - Size: 9999, - Digest: v1.Hash{ - Algorithm: "sha256", - Hex: "325392e8dd2826a53a9a35b7a7f8d71683cd27ebc2c73fee85dab673bc909b67", - }, - }, + descriptor: &v1.Descriptor{ + MediaType: types.OCIConfigJSON, + Size: 123, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb", }, }, }, @@ -259,19 +173,17 @@ func TestOCIDescriptorManifest(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { client := fake.FakeClient{} - if c.imageErr != nil { - client.On("Head", mock.Anything, mock.Anything).Return(nil, c.imageErr) + if c.err != nil { + client.On("Head", mock.Anything).Return(nil, c.err) } else { - imageManifest := v1fake.FakeImage{} - imageManifest.ManifestReturns(c.manifest, c.manifestErr) - client.On("Head", mock.Anything, mock.Anything).Return(&imageManifest, nil) + client.On("Head", mock.Anything).Return(c.descriptor, nil) } ctx := oci.WithClient(context.Background(), &client) bctx := rego.BuiltinContext{Context: ctx} got, err := ociDescriptor(bctx, c.ref) require.NoError(t, err) - if c.wantErr { + if c.err != nil { require.Nil(t, got) } else { require.NotNil(t, got) @@ -283,175 +195,33 @@ func TestOCIDescriptorManifest(t *testing.T) { func TestOCIDescriptorErrors(t *testing.T) { cases := []struct { - name string - ref *ast.Term - manifest *v1.Manifest - imageErr error - manifestErr error - wantErr bool + name string + ref *ast.Term }{ { - name: "missing digest", - ref: ast.StringTerm("registry.local/spam:latest"), - wantErr: true, - }, - { - name: "bad image ref", - ref: ast.StringTerm("......registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), - wantErr: true, - }, - { - name: "invalid ref type", - ref: ast.IntNumberTerm(42), - wantErr: true, - }, - { - name: "image error", - ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), - manifestErr: errors.New("kaboom!"), - wantErr: true, - }, - { - name: "nil manifest", - ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), - manifest: nil, - wantErr: true, + name: "missing digest", + ref: ast.StringTerm("registry.local/spam:latest"), }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - client := fake.FakeClient{} - if c.imageErr != nil { - client.On("Head", mock.Anything, mock.Anything).Return(nil, c.imageErr) - } else { - imageManifest := v1fake.FakeImage{} - imageManifest.ManifestReturns(c.manifest, c.manifestErr) - client.On("Head", mock.Anything, mock.Anything).Return(&imageManifest, nil) - } - ctx := oci.WithClient(context.Background(), &client) - bctx := rego.BuiltinContext{Context: ctx} - - got, err := ociDescriptor(bctx, c.ref) - require.NoError(t, err) - if c.wantErr { - require.Nil(t, got) - } else { - require.NotNil(t, got) - snaps.MatchJSON(t, got) - } - }) - } -} - -func TestOCIDescriptorIndex(t *testing.T) { - cases := []struct { - name string - ref *ast.Term - manifest *v1.IndexManifest - imageErr error - manifestErr error - wantErr bool - }{ { - name: "complete image index", - ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), - manifest: &v1.IndexManifest{ - SchemaVersion: 2, - MediaType: types.OCIImageIndex, - Manifests: []v1.Descriptor{ - { - MediaType: types.OCIManifestSchema1, - Size: 123, - Digest: v1.Hash{ - Algorithm: "sha256", - Hex: "4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb", - }, - Annotations: map[string]string{ - "config.annotation.1": "config.annotation.value.1", - "config.annotation.2": "config.annotation.value.2", - }, - Platform: &v1.Platform{ - Architecture: "arch", - OS: "os", - OSVersion: "os-version", - OSFeatures: []string{"os-feature-1", "os-feature-2"}, - Variant: "variant", - Features: []string{"feature-1", "feature-2"}, - }, - ArtifactType: "artifact-type", - }, - }, - Annotations: map[string]string{ - "manifest.annotation.1": "config.annotation.value.1", - "manifest.annotation.2": "config.annotation.value.2", - }, - Subject: &v1.Descriptor{ - MediaType: types.OCIManifestSchema1, - Size: 8888, - Digest: v1.Hash{ - Algorithm: "sha256", - Hex: "d9298a10d1b0735837dc4bd85dac641b0f3cef27a47e5d53a54f2f3f5b2fcffa", - }, - Data: []byte(`{"data": "subject"}`), - URLs: []string{"https://subject-1.local/spam", "https://subject-2.local/spam"}, - Annotations: map[string]string{ - "subject.annotation.1": "subject.annotation.value.1", - "subject.annotation.2": "subject.annotation.value.2", - }, - Platform: &v1.Platform{ - Architecture: "arch", - OS: "os", - OSVersion: "os-version", - OSFeatures: []string{"os-feature-1", "os-feature-2"}, - Variant: "variant", - Features: []string{"feature-1", "feature-2"}, - }, - ArtifactType: "artifact-type", - }, - }, + name: "bad image ref", + ref: ast.StringTerm("......registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), }, { - name: "minimal image index", - ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), - manifest: &v1.IndexManifest{ - SchemaVersion: 2, - MediaType: types.OCIImageIndex, - Manifests: []v1.Descriptor{ - { - MediaType: types.OCILayer, - Size: 9999, - Digest: v1.Hash{ - Algorithm: "sha256", - Hex: "325392e8dd2826a53a9a35b7a7f8d71683cd27ebc2c73fee85dab673bc909b67", - }, - }, - }, - }, + name: "invalid ref type", + ref: ast.IntNumberTerm(42), }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { client := fake.FakeClient{} - if c.imageErr != nil { - client.On("Head", mock.Anything, mock.Anything).Return(nil, c.imageErr) - } else { - imageIndex := v1fake.FakeImageIndex{} - imageIndex.IndexManifestReturns(c.manifest, c.manifestErr) - client.On("Head", mock.Anything, mock.Anything).Return(&imageIndex, nil) - } + client.On("Head", mock.Anything, mock.Anything).Return(nil, errors.New("expected")) ctx := oci.WithClient(context.Background(), &client) bctx := rego.BuiltinContext{Context: ctx} got, err := ociDescriptor(bctx, c.ref) require.NoError(t, err) - if c.wantErr { - require.Nil(t, got) - } else { - require.NotNil(t, got) - snaps.MatchJSON(t, got) - } + require.Nil(t, got) }) } } From 34bdc25f0f25d50becaa816b7f82dacb3c94a94e Mon Sep 17 00:00:00 2001 From: arewm Date: Mon, 16 Dec 2024 10:17:13 -0500 Subject: [PATCH 3/3] associate appropriate mediaTypes Signed-off-by: arewm --- internal/rego/oci/__snapshots__/oci_test.snap | 6 +++--- internal/rego/oci/oci_test.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/rego/oci/__snapshots__/oci_test.snap b/internal/rego/oci/__snapshots__/oci_test.snap index 59994275e..91c7baa0e 100755 --- a/internal/rego/oci/__snapshots__/oci_test.snap +++ b/internal/rego/oci/__snapshots__/oci_test.snap @@ -940,7 +940,7 @@ }, { "type": "string", - "value": "application/vnd.oci.image.config.v1+json" + "value": "application/vnd.oci.image.manifest.v1+json" } ], [ @@ -1116,7 +1116,7 @@ }, { "type": "string", - "value": "application/vnd.oci.image.config.v1+json" + "value": "application/vnd.oci.image.manifest.v1+json" } ], [ @@ -1194,7 +1194,7 @@ }, { "type": "string", - "value": "application/vnd.oci.image.config.v1+json" + "value": "application/vnd.oci.image.index.v1+json" } ], [ diff --git a/internal/rego/oci/oci_test.go b/internal/rego/oci/oci_test.go index e178768f4..a4a561a69 100644 --- a/internal/rego/oci/oci_test.go +++ b/internal/rego/oci/oci_test.go @@ -121,7 +121,7 @@ func TestOCIDescriptorManifest(t *testing.T) { name: "complete image manifest", ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), descriptor: &v1.Descriptor{ - MediaType: types.OCIConfigJSON, + MediaType: types.OCIManifestSchema1, Size: 123, Digest: v1.Hash{ Algorithm: "sha256", @@ -148,7 +148,7 @@ func TestOCIDescriptorManifest(t *testing.T) { name: "minimal image manifest", ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), descriptor: &v1.Descriptor{ - MediaType: types.OCIConfigJSON, + MediaType: types.OCIManifestSchema1, Size: 123, Digest: v1.Hash{ Algorithm: "sha256", @@ -160,7 +160,7 @@ func TestOCIDescriptorManifest(t *testing.T) { name: "minimal image index", ref: ast.StringTerm("registry.local/spam:latest@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"), descriptor: &v1.Descriptor{ - MediaType: types.OCIConfigJSON, + MediaType: types.OCIImageIndex, Size: 123, Digest: v1.Hash{ Algorithm: "sha256",