From 36fd7a7a52b8c975d8976b18f2a028952ff84133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Suszy=C5=84ski?= Date: Tue, 18 Jun 2024 17:40:35 +0200 Subject: [PATCH] Fixing unit tests --- Makefile | 12 +- go.mod | 1 + go.sum | 2 + pkg/prowgen/prowgen_images_discovery.go | 108 +++-- pkg/prowgen/prowgen_tests.go | 113 ++--- pkg/prowgen/prowgen_tests_discovery_test.go | 101 ++-- pkg/prowgen/testdata/eventing/Makefile | 2 +- .../ci-operator/build-image/Dockerfile | 1 + .../knative-test-images/webhook/Dockerfile | 1 + .../ci-operator/source-image/Dockerfile | 1 + .../ci-operator/build-image/Dockerfile | 1 + .../scale-from-zero/Dockerfile | 1 + .../knative-test-images/webhook/Dockerfile | 1 + .../ci-operator/source-image/Dockerfile | 1 + .../distribution/reference/.gitattributes | 1 + .../distribution/reference/.gitignore | 2 + .../distribution/reference/.golangci.yml | 18 + .../distribution/reference/CODE-OF-CONDUCT.md | 5 + .../distribution/reference/CONTRIBUTING.md | 114 +++++ .../distribution/reference/GOVERNANCE.md | 144 ++++++ .../github.com/distribution/reference/LICENSE | 202 ++++++++ .../distribution/reference/MAINTAINERS | 26 ++ .../distribution/reference/Makefile | 25 + .../distribution/reference/README.md | 30 ++ .../distribution/reference/SECURITY.md | 7 + .../reference/distribution-logo.svg | 1 + .../distribution/reference/helpers.go | 42 ++ .../distribution/reference/normalize.go | 255 +++++++++++ .../distribution/reference/reference.go | 432 ++++++++++++++++++ .../distribution/reference/regexp.go | 163 +++++++ .../github.com/distribution/reference/sort.go | 75 +++ vendor/modules.txt | 3 + 32 files changed, 1746 insertions(+), 145 deletions(-) create mode 100644 vendor/github.com/distribution/reference/.gitattributes create mode 100644 vendor/github.com/distribution/reference/.gitignore create mode 100644 vendor/github.com/distribution/reference/.golangci.yml create mode 100644 vendor/github.com/distribution/reference/CODE-OF-CONDUCT.md create mode 100644 vendor/github.com/distribution/reference/CONTRIBUTING.md create mode 100644 vendor/github.com/distribution/reference/GOVERNANCE.md create mode 100644 vendor/github.com/distribution/reference/LICENSE create mode 100644 vendor/github.com/distribution/reference/MAINTAINERS create mode 100644 vendor/github.com/distribution/reference/Makefile create mode 100644 vendor/github.com/distribution/reference/README.md create mode 100644 vendor/github.com/distribution/reference/SECURITY.md create mode 100644 vendor/github.com/distribution/reference/distribution-logo.svg create mode 100644 vendor/github.com/distribution/reference/helpers.go create mode 100644 vendor/github.com/distribution/reference/normalize.go create mode 100644 vendor/github.com/distribution/reference/reference.go create mode 100644 vendor/github.com/distribution/reference/regexp.go create mode 100644 vendor/github.com/distribution/reference/sort.go diff --git a/Makefile b/Makefile index b9d58741..7679fe6a 100644 --- a/Makefile +++ b/Makefile @@ -38,8 +38,14 @@ generate-action: go run github.com/openshift-knative/hack/cmd/update-konflux-gen-action .PHONY: generate-action +test: unit-tests +.PHONY: test + unit-tests: - go test ./pkg/... + go run gotest.tools/gotestsum@latest \ + --format testname -- \ + -count=1 -race -timeout=10m \ + ./pkg/... rm -rf openshift/project/testoutput rm -rf openshift/project/.github @@ -47,7 +53,7 @@ unit-tests: mkdir -p openshift go run ./cmd/update-konflux-gen-action --input ".github/workflows/release-generate-ci-template.yaml" --config "config/" --output "openshift/release-generate-ci.yaml" # If the following fails, please run 'make generate-action' - diff -r "openshift/release-generate-ci.yaml" ".github/workflows/release-generate-ci.yaml" + diff -ur "openshift/release-generate-ci.yaml" ".github/workflows/release-generate-ci.yaml" go run ./cmd/generate/ --generators dockerfile \ --project-file pkg/project/testdata/project.yaml \ @@ -57,7 +63,7 @@ unit-tests: --images-from "hack" \ --images-from-url-format "https://raw.githubusercontent.com/openshift-knative/%s/%s/pkg/project/testdata/additional-images.yaml" \ --output "openshift/project/testoutput/openshift" - diff -r "pkg/project/testoutput" "openshift/project/testoutput" + diff -ur "pkg/project/testoutput" "openshift/project/testoutput" .PHONY: unit-tests diff --git a/go.mod b/go.mod index 1e49c977..2a0fcf9c 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ replace k8s.io/client-go => k8s.io/client-go v0.27.2 require ( github.com/asottile/dockerfile v3.1.0+incompatible github.com/coreos/go-semver v0.3.0 + github.com/distribution/reference v0.6.0 github.com/ghodss/yaml v1.0.0 github.com/google/go-cmp v0.6.0 github.com/openshift/ci-tools v0.0.0-20240219083709-f792a49ba107 diff --git a/go.sum b/go.sum index 802ed3e2..e55e2db4 100644 --- a/go.sum +++ b/go.sum @@ -144,6 +144,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= diff --git a/pkg/prowgen/prowgen_images_discovery.go b/pkg/prowgen/prowgen_images_discovery.go index ed0014c7..c91425e4 100644 --- a/pkg/prowgen/prowgen_images_discovery.go +++ b/pkg/prowgen/prowgen_images_discovery.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/asottile/dockerfile" + "github.com/distribution/reference" cioperatorapi "github.com/openshift/ci-tools/pkg/api" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/utils/strings/slices" @@ -22,12 +23,12 @@ const ( ) var ( - ciRegistryRegex = regexp.MustCompile(`registry\.(|svc\.)ci\.openshift\.org/\S+`) + ciRegistryRegex = regexp.MustCompile(`registry\.(|svc\.)ci\.openshift\.org`) ubiMinimal8Regex = regexp.MustCompile(`registry\.access\.redhat\.com/ubi8-minimal:latest$`) ubiMinimal9Regex = regexp.MustCompile(`registry\.access\.redhat\.com/ubi9-minimal:latest$`) - imageOverrides = map[*regexp.Regexp]orgRepoTag{ + imageOverrides = map[*regexp.Regexp]ociReference{ ubiMinimal8Regex: {Org: "ocp", Repo: "ubi-minimal", Tag: "8"}, ubiMinimal9Regex: {Org: "ocp", Repo: "ubi-minimal", Tag: "9"}, } @@ -44,13 +45,15 @@ var ( } ) -type orgRepoTag struct { - Org string - Repo string - Tag string +type ociReference struct { + Domain string + Org string + Repo string + Tag string + Digest string } -func (ort orgRepoTag) String() string { +func (ort ociReference) String() string { return ort.Org + "_" + ort.Repo + "_" + ort.Tag } @@ -191,32 +194,37 @@ func discoverInputImages(dockerfile string) (map[string]cioperatorapi.ImageStrea if imagePath == srcImage { inputImages[srcImage] = cioperatorapi.ImageBuildInputs{As: []string{srcImage}} } else { - orgRepoTag, err := orgRepoTagFromPullString(imagePath) + ref, err := parseOciReference(imagePath) if err != nil { return nil, nil, fmt.Errorf("failed to parse string %s as pullspec: %w", imagePath, err) } for k, override := range imageOverrides { if k.FindString(imagePath) != "" { - orgRepoTag = override + ref = override break } } - inputs := inputImages[orgRepoTag.String()] - inputs.As = sets.NewString(inputs.As...).Insert(imagePath).List() //different registries can resolve to the same orgRepoTag + inputs := inputImages[ref.String()] + inputs.As = sets.NewString(inputs.As...).Insert(imagePath).List() //different registries can resolve to the same ociReference - if orgRepoTag.Org == "_" { + if ref.Org == "" && ref.Domain == "" { // consider image as a base image alias inputImages[imagePath] = inputs - } else { - requiredBaseImages[orgRepoTag.String()] = cioperatorapi.ImageStreamTagReference{ - Namespace: orgRepoTag.Org, - Name: orgRepoTag.Repo, - Tag: orgRepoTag.Tag, - } - inputImages[orgRepoTag.String()] = inputs + continue + } + if !ciRegistryRegex.MatchString(ref.Domain) { + // don't include images from other registries then registry.ci.openshift.org + continue } + + requiredBaseImages[ref.String()] = cioperatorapi.ImageStreamTagReference{ + Namespace: ref.Org, + Name: ref.Repo, + Tag: ref.Tag, + } + inputImages[ref.String()] = inputs } } @@ -230,19 +238,28 @@ func getPullStringsFromDockerfile(filename string) ([]string, error) { } images := make([]string, 0, 1) + aliases := make(map[string]string) for _, cmd := range cmds { if cmd.Cmd == "FROM" { - if len(cmd.Value) != 1 { - return nil, fmt.Errorf("expected one value for FROM "+ - "command, got %d: %q", len(cmd.Value), cmd.Value) + if len(cmd.Value) == 1 { + images = append(images, cmd.Value[0]) + continue + } + if len(cmd.Value) == 3 && strings.ToLower(cmd.Value[1]) == "as" { + aliases[cmd.Value[2]] = cmd.Value[0] + images = append(images, cmd.Value[0]) + continue } - images = append(images, cmd.Value[0]) - continue + return nil, fmt.Errorf("invalid FROM stanza: %q", cmd.Value) } if cmd.Cmd == "COPY" || cmd.Cmd == "ADD" { for _, fl := range cmd.Flags { if strings.HasPrefix(fl, "--from=") { - images = append(images, strings.TrimPrefix(fl, "--from=")) + imageOrAlias := strings.TrimPrefix(fl, "--from=") + if _, ok := aliases[imageOrAlias]; !ok { + // not an alias, consider it as an image + images = append(images, imageOrAlias) + } } } } @@ -274,28 +291,31 @@ func isSourceOrBuildDockerfile(dockerfile string) bool { strings.Contains(dockerfile, "build-image") } -func orgRepoTagFromPullString(pullString string) (orgRepoTag, error) { - res := orgRepoTag{Tag: "latest"} - slashSplit := strings.Split(pullString, "/") - switch n := len(slashSplit); n { - case 1: - res.Org = "_" - res.Repo = slashSplit[0] - case 2: - res.Org = slashSplit[0] - res.Repo = slashSplit[1] - case 3: - res.Org = slashSplit[1] - res.Repo = slashSplit[2] - default: - return res, fmt.Errorf("pull string %q couldn't be parsed, expected to get between one and three elements after slashsplitting, got %d", pullString, n) +func parseOciReference(pullString string) (ociReference, error) { + ref, err := reference.Parse(pullString) + if err != nil { + return ociReference{}, fmt.Errorf("failed to parse %q as a reference: %w", pullString, err) } - if repoTag := strings.Split(res.Repo, ":"); len(repoTag) == 2 { - res.Repo = repoTag[0] - res.Tag = repoTag[1] + res := ociReference{Tag: "latest"} + if named, ok := ref.(reference.Named); ok { + res.Domain = reference.Domain(named) + path := strings.SplitN(reference.Path(named), "/", 2) + if len(path) == 1 { + res.Repo = path[0] + } else { + res.Org = path[0] + res.Repo = path[1] + } + if tagged, ok := ref.(reference.Tagged); ok { + res.Tag = tagged.Tag() + } + if digested, ok := ref.(reference.Digested); ok { + res.Digest = digested.Digest().String() + } + return res, nil } - return res, nil + return res, fmt.Errorf("pull string %q couldn't be parsed as OCI image", pullString) } func ToRegexp(s []string) []*regexp.Regexp { diff --git a/pkg/prowgen/prowgen_tests.go b/pkg/prowgen/prowgen_tests.go index bd042334..c0b6520a 100644 --- a/pkg/prowgen/prowgen_tests.go +++ b/pkg/prowgen/prowgen_tests.go @@ -100,70 +100,76 @@ func DiscoverTests(r Repository, openShift OpenShift, sourceImageName string, sk AllowBestEffortPostSteps: pointer.Bool(true), AllowSkipOnSuccess: pointer.Bool(true), Environment: env, - Test: []cioperatorapi.TestStep{{ - LiteralTestStep: &cioperatorapi.LiteralTestStep{ - As: "test", - From: sourceImageName, - Commands: test.EffectiveCommand(), - Resources: cioperatorapi.ResourceRequirements{ - Requests: cioperatorapi.ResourceList{ - "cpu": "100m", + Test: []cioperatorapi.TestStep{ + { + LiteralTestStep: &cioperatorapi.LiteralTestStep{ + As: "test", + From: sourceImageName, + Commands: test.EffectiveCommand(), + Resources: cioperatorapi.ResourceRequirements{ + Requests: cioperatorapi.ResourceList{ + "cpu": "100m", + }, }, + Environment: test.EnvironmentAsStepParams(), + Timeout: testTimeout, + Dependencies: dependenciesFromImages(cfg.Images, test.SkipImages), + Cli: "latest", }, - Environment: test.EnvironmentAsStepParams(), - Timeout: testTimeout, - Dependencies: dependenciesFromImages(cfg.Images, test.SkipImages), - Cli: "latest", }, - }}, - Post: []cioperatorapi.TestStep{{ - LiteralTestStep: &cioperatorapi.LiteralTestStep{ - As: "knative-must-gather", - From: sourceImageName, - Commands: `oc adm must-gather --image=quay.io/openshift-knative/must-gather --dest-dir "${ARTIFACT_DIR}/gather-knative"`, - Resources: cioperatorapi.ResourceRequirements{ - Requests: cioperatorapi.ResourceList{ - "cpu": "100m", + }, + Post: []cioperatorapi.TestStep{ + { + LiteralTestStep: &cioperatorapi.LiteralTestStep{ + As: "knative-must-gather", + From: sourceImageName, + Commands: `oc adm must-gather --image=quay.io/openshift-knative/must-gather --dest-dir "${ARTIFACT_DIR}/gather-knative"`, + Resources: cioperatorapi.ResourceRequirements{ + Requests: cioperatorapi.ResourceList{ + "cpu": "100m", + }, }, + Timeout: &prowapi.Duration{Duration: 20 * time.Minute}, + BestEffort: pointer.Bool(true), + OptionalOnSuccess: pointer.Bool(true), + Cli: "latest", }, - Timeout: &prowapi.Duration{Duration: 20 * time.Minute}, - BestEffort: pointer.Bool(true), - OptionalOnSuccess: pointer.Bool(true), - Cli: "latest", }, - }, { - LiteralTestStep: &cioperatorapi.LiteralTestStep{ - As: "openshift-must-gather", - From: sourceImageName, - Commands: `oc adm must-gather --dest-dir "${ARTIFACT_DIR}/gather-openshift"`, - Resources: cioperatorapi.ResourceRequirements{ - Requests: cioperatorapi.ResourceList{ - "cpu": "100m", + { + LiteralTestStep: &cioperatorapi.LiteralTestStep{ + As: "openshift-must-gather", + From: sourceImageName, + Commands: `oc adm must-gather --dest-dir "${ARTIFACT_DIR}/gather-openshift"`, + Resources: cioperatorapi.ResourceRequirements{ + Requests: cioperatorapi.ResourceList{ + "cpu": "100m", + }, }, + Timeout: &prowapi.Duration{Duration: 20 * time.Minute}, + BestEffort: pointer.Bool(true), + OptionalOnSuccess: pointer.Bool(true), + Cli: "latest", }, - Timeout: &prowapi.Duration{Duration: 20 * time.Minute}, - BestEffort: pointer.Bool(true), - OptionalOnSuccess: pointer.Bool(true), - Cli: "latest", }, - }, { - LiteralTestStep: &cioperatorapi.LiteralTestStep{ - As: "openshift-gather-extra", - From: sourceImageName, - Commands: `curl -skSL https://raw.githubusercontent.com/openshift/release/master/ci-operator/step-registry/gather/extra/gather-extra-commands.sh | /bin/bash -s`, - GracePeriod: &prowapi.Duration{Duration: 60 * time.Second}, - Resources: cioperatorapi.ResourceRequirements{ - Requests: cioperatorapi.ResourceList{ - "cpu": "300m", - "memory": "300Mi", + { + LiteralTestStep: &cioperatorapi.LiteralTestStep{ + As: "openshift-gather-extra", + From: sourceImageName, + Commands: `curl -skSL https://raw.githubusercontent.com/openshift/release/master/ci-operator/step-registry/gather/extra/gather-extra-commands.sh | /bin/bash -s`, + GracePeriod: &prowapi.Duration{Duration: 60 * time.Second}, + Resources: cioperatorapi.ResourceRequirements{ + Requests: cioperatorapi.ResourceList{ + "cpu": "300m", + "memory": "300Mi", + }, }, + Timeout: &prowapi.Duration{Duration: 20 * time.Minute}, + BestEffort: pointer.Bool(true), + OptionalOnSuccess: pointer.Bool(true), + Cli: "latest", }, - Timeout: &prowapi.Duration{Duration: 20 * time.Minute}, - BestEffort: pointer.Bool(true), - OptionalOnSuccess: pointer.Bool(true), - Cli: "latest", }, - }}, + }, Workflow: workflow, }, } @@ -261,6 +267,9 @@ func (t *Test) EffectiveCommand() string { } func (t *Test) EnvironmentAsStepParams() []cioperatorapi.StepParameter { + if len(t.Environment) == 0 { + return nil + } params := make([]cioperatorapi.StepParameter, 0, len(t.Environment)) for k, v := range t.Environment { params = append(params, cioperatorapi.StepParameter{ diff --git a/pkg/prowgen/prowgen_tests_discovery_test.go b/pkg/prowgen/prowgen_tests_discovery_test.go index 5bf63ec5..185459d3 100644 --- a/pkg/prowgen/prowgen_tests_discovery_test.go +++ b/pkg/prowgen/prowgen_tests_discovery_test.go @@ -1,9 +1,8 @@ package prowgen import ( - "fmt" "math/rand" - "strings" + "sort" "testing" "time" @@ -99,7 +98,7 @@ func TestDiscoverTestsServing(t *testing.T) { expectedTests := []cioperatorapi.TestStepConfiguration{ { - As: "perf-tests-aws-412", + As: "perf-tests", MultiStageTestConfiguration: &cioperatorapi.MultiStageTestConfiguration{ ClusterProfile: serverlessClusterProfile, Test: []cioperatorapi.TestStep{ @@ -107,7 +106,7 @@ func TestDiscoverTestsServing(t *testing.T) { LiteralTestStep: &cioperatorapi.LiteralTestStep{ As: "test", From: servingSourceImage, - Commands: formatCommand("make perf-tests"), + Commands: "make perf-tests", Resources: cioperatorapi.ResourceRequirements{ Requests: cioperatorapi.ResourceList{ "cpu": "100m", @@ -123,7 +122,7 @@ func TestDiscoverTestsServing(t *testing.T) { }, }, { - As: "test-e2e-aws-412", + As: "test-e2e", Optional: true, MultiStageTestConfiguration: &cioperatorapi.MultiStageTestConfiguration{ ClusterProfile: serverlessClusterProfile, @@ -132,7 +131,7 @@ func TestDiscoverTestsServing(t *testing.T) { LiteralTestStep: &cioperatorapi.LiteralTestStep{ As: "test", From: servingSourceImage, - Commands: formatCommand("make test-e2e"), + Commands: "make test-e2e", Resources: cioperatorapi.ResourceRequirements{ Requests: cioperatorapi.ResourceList{ "cpu": "100m", @@ -148,7 +147,7 @@ func TestDiscoverTestsServing(t *testing.T) { }, }, { - As: "test-e2e-aws-412-c", + As: "test-e2e-c", Cron: cron, MultiStageTestConfiguration: &cioperatorapi.MultiStageTestConfiguration{ ClusterProfile: serverlessClusterProfile, @@ -157,7 +156,7 @@ func TestDiscoverTestsServing(t *testing.T) { LiteralTestStep: &cioperatorapi.LiteralTestStep{ As: "test", From: servingSourceImage, - Commands: formatCommand("make test-e2e"), + Commands: "make test-e2e", Resources: cioperatorapi.ResourceRequirements{ Requests: cioperatorapi.ResourceList{ "cpu": "100m", @@ -173,7 +172,7 @@ func TestDiscoverTestsServing(t *testing.T) { }, }, { - As: "test-e2e-tls-aws-412", + As: "test-e2e-tls", MultiStageTestConfiguration: &cioperatorapi.MultiStageTestConfiguration{ ClusterProfile: serverlessClusterProfile, Test: []cioperatorapi.TestStep{ @@ -181,7 +180,7 @@ func TestDiscoverTestsServing(t *testing.T) { LiteralTestStep: &cioperatorapi.LiteralTestStep{ As: "test", From: servingSourceImage, - Commands: formatCommand("make test-e2e-tls"), + Commands: "make test-e2e-tls", Resources: cioperatorapi.ResourceRequirements{ Requests: cioperatorapi.ResourceList{ "cpu": "100m", @@ -197,7 +196,7 @@ func TestDiscoverTestsServing(t *testing.T) { }, }, { - As: "test-e2e-tls-aws-412-c", + As: "test-e2e-tls-c", Cron: cron, MultiStageTestConfiguration: &cioperatorapi.MultiStageTestConfiguration{ ClusterProfile: serverlessClusterProfile, @@ -206,7 +205,7 @@ func TestDiscoverTestsServing(t *testing.T) { LiteralTestStep: &cioperatorapi.LiteralTestStep{ As: "test", From: servingSourceImage, - Commands: formatCommand("make test-e2e-tls"), + Commands: "make test-e2e-tls", Resources: cioperatorapi.ResourceRequirements{ Requests: cioperatorapi.ResourceList{ "cpu": "100m", @@ -222,7 +221,7 @@ func TestDiscoverTestsServing(t *testing.T) { }, }, { - As: "ui-e2e-aws-412", + As: "ui-e2e", RunIfChanged: "test/ui", MultiStageTestConfiguration: &cioperatorapi.MultiStageTestConfiguration{ ClusterProfile: serverlessClusterProfile, @@ -231,7 +230,7 @@ func TestDiscoverTestsServing(t *testing.T) { LiteralTestStep: &cioperatorapi.LiteralTestStep{ As: "test", From: servingSourceImage, - Commands: formatCommand("make ui-e2e"), + Commands: "make ui-e2e", Resources: cioperatorapi.ResourceRequirements{ Requests: cioperatorapi.ResourceList{ "cpu": "100m", @@ -277,8 +276,7 @@ func TestDiscoverTestsServing(t *testing.T) { t.Fatal(err) } - if !equality.Semantic.DeepEqual(expectedTests, cfg.Tests) { - diff := cmp.Diff(expectedTests, cfg.Tests) + if ok, diff := equals(expectedTests, cfg.Tests); !ok { t.Errorf("Unexpected tests (-want, +got): \n%s", diff) } } @@ -329,7 +327,7 @@ func TestDiscoverTestsServingClusterClaim(t *testing.T) { expectedTests := []cioperatorapi.TestStepConfiguration{ { - As: fmt.Sprintf("perf-tests-aws-%s", strings.ReplaceAll(clusterPoolVersion, ".", "")), + As: "perf-tests", ClusterClaim: &cioperatorapi.ClusterClaim{ Product: cioperatorapi.ReleaseProductOCP, Version: clusterPoolVersion, @@ -344,7 +342,7 @@ func TestDiscoverTestsServingClusterClaim(t *testing.T) { LiteralTestStep: &cioperatorapi.LiteralTestStep{ As: "test", From: servingSourceImage, - Commands: formatCommand("make perf-tests"), + Commands: "make perf-tests", Resources: cioperatorapi.ResourceRequirements{ Requests: cioperatorapi.ResourceList{ "cpu": "100m", @@ -381,8 +379,7 @@ func TestDiscoverTestsServingClusterClaim(t *testing.T) { t.Fatal(err) } - if !equality.Semantic.DeepEqual(expectedTests, cfg.Tests) { - diff := cmp.Diff(expectedTests, cfg.Tests) + if ok, diff := equals(expectedTests, cfg.Tests); !ok { t.Errorf("Unexpected tests (-want, +got): \n%s", diff) } } @@ -436,7 +433,7 @@ func TestDiscoverTestsEventing(t *testing.T) { expectedTests := []cioperatorapi.TestStepConfiguration{ { - As: "test-conformance-aws-412", + As: "test-conformance", MultiStageTestConfiguration: &cioperatorapi.MultiStageTestConfiguration{ ClusterProfile: serverlessClusterProfile, Test: []cioperatorapi.TestStep{ @@ -444,7 +441,7 @@ func TestDiscoverTestsEventing(t *testing.T) { LiteralTestStep: &cioperatorapi.LiteralTestStep{ As: "test", From: eventingSourceImage, - Commands: formatCommand("make test-conformance"), + Commands: "make test-conformance", Resources: cioperatorapi.ResourceRequirements{ Requests: cioperatorapi.ResourceList{ "cpu": "100m", @@ -460,8 +457,8 @@ func TestDiscoverTestsEventing(t *testing.T) { }, }, { - As: "test-conformance-aws-412-c", - Cron: pointer.String("23 1 * * 2,6"), + As: "test-conformance-c", + Cron: pointer.String("43 1 * * 2,6"), MultiStageTestConfiguration: &cioperatorapi.MultiStageTestConfiguration{ ClusterProfile: serverlessClusterProfile, Test: []cioperatorapi.TestStep{ @@ -469,7 +466,7 @@ func TestDiscoverTestsEventing(t *testing.T) { LiteralTestStep: &cioperatorapi.LiteralTestStep{ As: "test", From: eventingSourceImage, - Commands: formatCommand("make test-conformance"), + Commands: "make test-conformance", Resources: cioperatorapi.ResourceRequirements{ Requests: cioperatorapi.ResourceList{ "cpu": "100m", @@ -485,7 +482,7 @@ func TestDiscoverTestsEventing(t *testing.T) { }, }, { - As: "test-conformance-long-lo-510e96a-aws-412", + As: "test-conformance-long-long-long-360d252", MultiStageTestConfiguration: &cioperatorapi.MultiStageTestConfiguration{ ClusterProfile: serverlessClusterProfile, Test: []cioperatorapi.TestStep{ @@ -493,7 +490,7 @@ func TestDiscoverTestsEventing(t *testing.T) { LiteralTestStep: &cioperatorapi.LiteralTestStep{ As: "test", From: eventingSourceImage, - Commands: formatCommand("make test-conformance-long-long-long-command"), + Commands: "make test-conformance-long-long-long-loooooooooong-command", Resources: cioperatorapi.ResourceRequirements{ Requests: cioperatorapi.ResourceList{ "cpu": "100m", @@ -509,8 +506,8 @@ func TestDiscoverTestsEventing(t *testing.T) { }, }, { - As: "test-conformance-long-lo-510e96a-aws-412-c", - Cron: pointer.String("43 1 * * 2,6"), + As: "test-conformance-long-long-long-360d252-c", + Cron: pointer.String("16 5 * * 2,6"), MultiStageTestConfiguration: &cioperatorapi.MultiStageTestConfiguration{ ClusterProfile: serverlessClusterProfile, Test: []cioperatorapi.TestStep{ @@ -518,7 +515,7 @@ func TestDiscoverTestsEventing(t *testing.T) { LiteralTestStep: &cioperatorapi.LiteralTestStep{ As: "test", From: eventingSourceImage, - Commands: formatCommand("make test-conformance-long-long-long-command"), + Commands: "make test-conformance-long-long-long-loooooooooong-command", Resources: cioperatorapi.ResourceRequirements{ Requests: cioperatorapi.ResourceList{ "cpu": "100m", @@ -534,7 +531,7 @@ func TestDiscoverTestsEventing(t *testing.T) { }, }, { - As: "test-e2e-aws-412", + As: "test-e2e", MultiStageTestConfiguration: &cioperatorapi.MultiStageTestConfiguration{ ClusterProfile: serverlessClusterProfile, Test: []cioperatorapi.TestStep{ @@ -542,7 +539,7 @@ func TestDiscoverTestsEventing(t *testing.T) { LiteralTestStep: &cioperatorapi.LiteralTestStep{ As: "test", From: eventingSourceImage, - Commands: formatCommand("make test-e2e"), + Commands: "make test-e2e", Resources: cioperatorapi.ResourceRequirements{ Requests: cioperatorapi.ResourceList{ "cpu": "100m", @@ -558,7 +555,7 @@ func TestDiscoverTestsEventing(t *testing.T) { }, }, { - As: "test-e2e-aws-412-c", + As: "test-e2e-c", Cron: pointer.String("4 1 * * 2,6"), MultiStageTestConfiguration: &cioperatorapi.MultiStageTestConfiguration{ ClusterProfile: serverlessClusterProfile, @@ -567,7 +564,7 @@ func TestDiscoverTestsEventing(t *testing.T) { LiteralTestStep: &cioperatorapi.LiteralTestStep{ As: "test", From: eventingSourceImage, - Commands: formatCommand("make test-e2e"), + Commands: "make test-e2e", Resources: cioperatorapi.ResourceRequirements{ Requests: cioperatorapi.ResourceList{ "cpu": "100m", @@ -583,7 +580,7 @@ func TestDiscoverTestsEventing(t *testing.T) { }, }, { - As: "test-reconciler-aws-412", + As: "test-reconciler", MultiStageTestConfiguration: &cioperatorapi.MultiStageTestConfiguration{ ClusterProfile: serverlessClusterProfile, Test: []cioperatorapi.TestStep{ @@ -591,7 +588,7 @@ func TestDiscoverTestsEventing(t *testing.T) { LiteralTestStep: &cioperatorapi.LiteralTestStep{ As: "test", From: eventingSourceImage, - Commands: formatCommand("make test-reconciler"), + Commands: "make test-reconciler", Resources: cioperatorapi.ResourceRequirements{ Requests: cioperatorapi.ResourceList{ "cpu": "100m", @@ -607,8 +604,8 @@ func TestDiscoverTestsEventing(t *testing.T) { }, }, { - As: "test-reconciler-aws-412-c", - Cron: pointer.String("16 5 * * 2,6"), + As: "test-reconciler-c", + Cron: pointer.String("23 1 * * 2,6"), MultiStageTestConfiguration: &cioperatorapi.MultiStageTestConfiguration{ ClusterProfile: serverlessClusterProfile, Test: []cioperatorapi.TestStep{ @@ -616,7 +613,7 @@ func TestDiscoverTestsEventing(t *testing.T) { LiteralTestStep: &cioperatorapi.LiteralTestStep{ As: "test", From: eventingSourceImage, - Commands: formatCommand("make test-reconciler"), + Commands: "make test-reconciler", Resources: cioperatorapi.ResourceRequirements{ Requests: cioperatorapi.ResourceList{ "cpu": "100m", @@ -662,12 +659,30 @@ func TestDiscoverTestsEventing(t *testing.T) { t.Fatal(err) } - if !equality.Semantic.DeepEqual(expectedTests, cfg.Tests) { - diff := cmp.Diff(expectedTests, cfg.Tests) + if ok, diff := equals(expectedTests, cfg.Tests); !ok { t.Errorf("Unexpected tests (-want, +got): \n%s", diff) } } +func equals(a, b []cioperatorapi.TestStepConfiguration) (bool, string) { + sa := sortByName(a) + sb := sortByName(b) + + if !equality.Semantic.DeepEqual(sa, sb) { + return false, cmp.Diff(sa, sb) + } + return true, "" +} + +func sortByName(tests []cioperatorapi.TestStepConfiguration) []cioperatorapi.TestStepConfiguration { + sorted := make([]cioperatorapi.TestStepConfiguration, len(tests)) + copy(sorted, tests) + sort.Slice(sorted, func(i, j int) bool { + return sorted[i].As < sorted[j].As + }) + return sorted +} + func mustGatherSteps(sourceImage string, optionalOnSuccess bool) []cioperatorapi.TestStep { return []cioperatorapi.TestStep{ { @@ -722,7 +737,3 @@ func mustGatherSteps(sourceImage string, optionalOnSuccess bool) []cioperatorapi }, } } - -func formatCommand(cmd string) string { - return fmt.Sprintf("SKIP_MESH_AUTH_POLICY_GENERATION=true %s", cmd) -} diff --git a/pkg/prowgen/testdata/eventing/Makefile b/pkg/prowgen/testdata/eventing/Makefile index bd2fd1e0..f3e30665 100644 --- a/pkg/prowgen/testdata/eventing/Makefile +++ b/pkg/prowgen/testdata/eventing/Makefile @@ -8,5 +8,5 @@ test-conformance: test-e2e: echo "Hello" -test-conformance-long-long-long-command: +test-conformance-long-long-long-loooooooooong-command: echo "Hello" diff --git a/pkg/prowgen/testdata/eventing/openshift/ci-operator/build-image/Dockerfile b/pkg/prowgen/testdata/eventing/openshift/ci-operator/build-image/Dockerfile index e69de29b..14634a96 100644 --- a/pkg/prowgen/testdata/eventing/openshift/ci-operator/build-image/Dockerfile +++ b/pkg/prowgen/testdata/eventing/openshift/ci-operator/build-image/Dockerfile @@ -0,0 +1 @@ +FROM registry.access.redhat.com/ubi8/ubi-minimal diff --git a/pkg/prowgen/testdata/eventing/openshift/ci-operator/knative-test-images/webhook/Dockerfile b/pkg/prowgen/testdata/eventing/openshift/ci-operator/knative-test-images/webhook/Dockerfile index e69de29b..d694a1fe 100644 --- a/pkg/prowgen/testdata/eventing/openshift/ci-operator/knative-test-images/webhook/Dockerfile +++ b/pkg/prowgen/testdata/eventing/openshift/ci-operator/knative-test-images/webhook/Dockerfile @@ -0,0 +1 @@ +FROM base diff --git a/pkg/prowgen/testdata/eventing/openshift/ci-operator/source-image/Dockerfile b/pkg/prowgen/testdata/eventing/openshift/ci-operator/source-image/Dockerfile index e69de29b..c6c622fb 100644 --- a/pkg/prowgen/testdata/eventing/openshift/ci-operator/source-image/Dockerfile +++ b/pkg/prowgen/testdata/eventing/openshift/ci-operator/source-image/Dockerfile @@ -0,0 +1 @@ +FROM src diff --git a/pkg/prowgen/testdata/serving/openshift/ci-operator/build-image/Dockerfile b/pkg/prowgen/testdata/serving/openshift/ci-operator/build-image/Dockerfile index e69de29b..14634a96 100644 --- a/pkg/prowgen/testdata/serving/openshift/ci-operator/build-image/Dockerfile +++ b/pkg/prowgen/testdata/serving/openshift/ci-operator/build-image/Dockerfile @@ -0,0 +1 @@ +FROM registry.access.redhat.com/ubi8/ubi-minimal diff --git a/pkg/prowgen/testdata/serving/openshift/ci-operator/knative-perf-images/scale-from-zero/Dockerfile b/pkg/prowgen/testdata/serving/openshift/ci-operator/knative-perf-images/scale-from-zero/Dockerfile index e69de29b..d694a1fe 100644 --- a/pkg/prowgen/testdata/serving/openshift/ci-operator/knative-perf-images/scale-from-zero/Dockerfile +++ b/pkg/prowgen/testdata/serving/openshift/ci-operator/knative-perf-images/scale-from-zero/Dockerfile @@ -0,0 +1 @@ +FROM base diff --git a/pkg/prowgen/testdata/serving/openshift/ci-operator/knative-test-images/webhook/Dockerfile b/pkg/prowgen/testdata/serving/openshift/ci-operator/knative-test-images/webhook/Dockerfile index e69de29b..d694a1fe 100644 --- a/pkg/prowgen/testdata/serving/openshift/ci-operator/knative-test-images/webhook/Dockerfile +++ b/pkg/prowgen/testdata/serving/openshift/ci-operator/knative-test-images/webhook/Dockerfile @@ -0,0 +1 @@ +FROM base diff --git a/pkg/prowgen/testdata/serving/openshift/ci-operator/source-image/Dockerfile b/pkg/prowgen/testdata/serving/openshift/ci-operator/source-image/Dockerfile index e69de29b..c6c622fb 100644 --- a/pkg/prowgen/testdata/serving/openshift/ci-operator/source-image/Dockerfile +++ b/pkg/prowgen/testdata/serving/openshift/ci-operator/source-image/Dockerfile @@ -0,0 +1 @@ +FROM src diff --git a/vendor/github.com/distribution/reference/.gitattributes b/vendor/github.com/distribution/reference/.gitattributes new file mode 100644 index 00000000..d207b180 --- /dev/null +++ b/vendor/github.com/distribution/reference/.gitattributes @@ -0,0 +1 @@ +*.go text eol=lf diff --git a/vendor/github.com/distribution/reference/.gitignore b/vendor/github.com/distribution/reference/.gitignore new file mode 100644 index 00000000..dc07e6b0 --- /dev/null +++ b/vendor/github.com/distribution/reference/.gitignore @@ -0,0 +1,2 @@ +# Cover profiles +*.out diff --git a/vendor/github.com/distribution/reference/.golangci.yml b/vendor/github.com/distribution/reference/.golangci.yml new file mode 100644 index 00000000..793f0bb7 --- /dev/null +++ b/vendor/github.com/distribution/reference/.golangci.yml @@ -0,0 +1,18 @@ +linters: + enable: + - bodyclose + - dupword # Checks for duplicate words in the source code + - gofmt + - goimports + - ineffassign + - misspell + - revive + - staticcheck + - unconvert + - unused + - vet + disable: + - errcheck + +run: + deadline: 2m diff --git a/vendor/github.com/distribution/reference/CODE-OF-CONDUCT.md b/vendor/github.com/distribution/reference/CODE-OF-CONDUCT.md new file mode 100644 index 00000000..48f6704c --- /dev/null +++ b/vendor/github.com/distribution/reference/CODE-OF-CONDUCT.md @@ -0,0 +1,5 @@ +# Code of Conduct + +We follow the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). + +Please contact the [CNCF Code of Conduct Committee](mailto:conduct@cncf.io) in order to report violations of the Code of Conduct. diff --git a/vendor/github.com/distribution/reference/CONTRIBUTING.md b/vendor/github.com/distribution/reference/CONTRIBUTING.md new file mode 100644 index 00000000..ab219466 --- /dev/null +++ b/vendor/github.com/distribution/reference/CONTRIBUTING.md @@ -0,0 +1,114 @@ +# Contributing to the reference library + +## Community help + +If you need help, please ask in the [#distribution](https://cloud-native.slack.com/archives/C01GVR8SY4R) channel on CNCF community slack. +[Click here for an invite to the CNCF community slack](https://slack.cncf.io/) + +## Reporting security issues + +The maintainers take security seriously. If you discover a security +issue, please bring it to their attention right away! + +Please **DO NOT** file a public issue, instead send your report privately to +[cncf-distribution-security@lists.cncf.io](mailto:cncf-distribution-security@lists.cncf.io). + +## Reporting an issue properly + +By following these simple rules you will get better and faster feedback on your issue. + + - search the bugtracker for an already reported issue + +### If you found an issue that describes your problem: + + - please read other user comments first, and confirm this is the same issue: a given error condition might be indicative of different problems - you may also find a workaround in the comments + - please refrain from adding "same thing here" or "+1" comments + - you don't need to comment on an issue to get notified of updates: just hit the "subscribe" button + - comment if you have some new, technical and relevant information to add to the case + - __DO NOT__ comment on closed issues or merged PRs. If you think you have a related problem, open up a new issue and reference the PR or issue. + +### If you have not found an existing issue that describes your problem: + + 1. create a new issue, with a succinct title that describes your issue: + - bad title: "It doesn't work with my docker" + - good title: "Private registry push fail: 400 error with E_INVALID_DIGEST" + 2. copy the output of (or similar for other container tools): + - `docker version` + - `docker info` + - `docker exec registry --version` + 3. copy the command line you used to launch your Registry + 4. restart your docker daemon in debug mode (add `-D` to the daemon launch arguments) + 5. reproduce your problem and get your docker daemon logs showing the error + 6. if relevant, copy your registry logs that show the error + 7. provide any relevant detail about your specific Registry configuration (e.g., storage backend used) + 8. indicate if you are using an enterprise proxy, Nginx, or anything else between you and your Registry + +## Contributing Code + +Contributions should be made via pull requests. Pull requests will be reviewed +by one or more maintainers or reviewers and merged when acceptable. + +You should follow the basic GitHub workflow: + + 1. Use your own [fork](https://help.github.com/en/articles/about-forks) + 2. Create your [change](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes) + 3. Test your code + 4. [Commit](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#commit-messages) your work, always [sign your commits](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#commit-messages) + 5. Push your change to your fork and create a [Pull Request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork) + +Refer to [containerd's contribution guide](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes) +for tips on creating a successful contribution. + +## Sign your work + +The sign-off is a simple line at the end of the explanation for the patch. Your +signature certifies that you wrote the patch or otherwise have the right to pass +it on as an open-source patch. The rules are pretty simple: if you can certify +the below (from [developercertificate.org](http://developercertificate.org/)): + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +Then you just add a line to every git commit message: + + Signed-off-by: Joe Smith + +Use your real name (sorry, no pseudonyms or anonymous contributions.) + +If you set your `user.name` and `user.email` git configs, you can sign your +commit automatically with `git commit -s`. diff --git a/vendor/github.com/distribution/reference/GOVERNANCE.md b/vendor/github.com/distribution/reference/GOVERNANCE.md new file mode 100644 index 00000000..200045b0 --- /dev/null +++ b/vendor/github.com/distribution/reference/GOVERNANCE.md @@ -0,0 +1,144 @@ +# distribution/reference Project Governance + +Distribution [Code of Conduct](./CODE-OF-CONDUCT.md) can be found here. + +For specific guidance on practical contribution steps please +see our [CONTRIBUTING.md](./CONTRIBUTING.md) guide. + +## Maintainership + +There are different types of maintainers, with different responsibilities, but +all maintainers have 3 things in common: + +1) They share responsibility in the project's success. +2) They have made a long-term, recurring time investment to improve the project. +3) They spend that time doing whatever needs to be done, not necessarily what +is the most interesting or fun. + +Maintainers are often under-appreciated, because their work is harder to appreciate. +It's easy to appreciate a really cool and technically advanced feature. It's harder +to appreciate the absence of bugs, the slow but steady improvement in stability, +or the reliability of a release process. But those things distinguish a good +project from a great one. + +## Reviewers + +A reviewer is a core role within the project. +They share in reviewing issues and pull requests and their LGTM counts towards the +required LGTM count to merge a code change into the project. + +Reviewers are part of the organization but do not have write access. +Becoming a reviewer is a core aspect in the journey to becoming a maintainer. + +## Adding maintainers + +Maintainers are first and foremost contributors that have shown they are +committed to the long term success of a project. Contributors wanting to become +maintainers are expected to be deeply involved in contributing code, pull +request review, and triage of issues in the project for more than three months. + +Just contributing does not make you a maintainer, it is about building trust +with the current maintainers of the project and being a person that they can +depend on and trust to make decisions in the best interest of the project. + +Periodically, the existing maintainers curate a list of contributors that have +shown regular activity on the project over the prior months. From this list, +maintainer candidates are selected and proposed in a pull request or a +maintainers communication channel. + +After a candidate has been announced to the maintainers, the existing +maintainers are given five business days to discuss the candidate, raise +objections and cast their vote. Votes may take place on the communication +channel or via pull request comment. Candidates must be approved by at least 66% +of the current maintainers by adding their vote on the mailing list. The +reviewer role has the same process but only requires 33% of current maintainers. +Only maintainers of the repository that the candidate is proposed for are +allowed to vote. + +If a candidate is approved, a maintainer will contact the candidate to invite +the candidate to open a pull request that adds the contributor to the +MAINTAINERS file. The voting process may take place inside a pull request if a +maintainer has already discussed the candidacy with the candidate and a +maintainer is willing to be a sponsor by opening the pull request. The candidate +becomes a maintainer once the pull request is merged. + +## Stepping down policy + +Life priorities, interests, and passions can change. If you're a maintainer but +feel you must remove yourself from the list, inform other maintainers that you +intend to step down, and if possible, help find someone to pick up your work. +At the very least, ensure your work can be continued where you left off. + +After you've informed other maintainers, create a pull request to remove +yourself from the MAINTAINERS file. + +## Removal of inactive maintainers + +Similar to the procedure for adding new maintainers, existing maintainers can +be removed from the list if they do not show significant activity on the +project. Periodically, the maintainers review the list of maintainers and their +activity over the last three months. + +If a maintainer has shown insufficient activity over this period, a neutral +person will contact the maintainer to ask if they want to continue being +a maintainer. If the maintainer decides to step down as a maintainer, they +open a pull request to be removed from the MAINTAINERS file. + +If the maintainer wants to remain a maintainer, but is unable to perform the +required duties they can be removed with a vote of at least 66% of the current +maintainers. In this case, maintainers should first propose the change to +maintainers via the maintainers communication channel, then open a pull request +for voting. The voting period is five business days. The voting pull request +should not come as a surpise to any maintainer and any discussion related to +performance must not be discussed on the pull request. + +## How are decisions made? + +Docker distribution is an open-source project with an open design philosophy. +This means that the repository is the source of truth for EVERY aspect of the +project, including its philosophy, design, road map, and APIs. *If it's part of +the project, it's in the repo. If it's in the repo, it's part of the project.* + +As a result, all decisions can be expressed as changes to the repository. An +implementation change is a change to the source code. An API change is a change +to the API specification. A philosophy change is a change to the philosophy +manifesto, and so on. + +All decisions affecting distribution, big and small, follow the same 3 steps: + +* Step 1: Open a pull request. Anyone can do this. + +* Step 2: Discuss the pull request. Anyone can do this. + +* Step 3: Merge or refuse the pull request. Who does this depends on the nature +of the pull request and which areas of the project it affects. + +## Helping contributors with the DCO + +The [DCO or `Sign your work`](./CONTRIBUTING.md#sign-your-work) +requirement is not intended as a roadblock or speed bump. + +Some contributors are not as familiar with `git`, or have used a web +based editor, and thus asking them to `git commit --amend -s` is not the best +way forward. + +In this case, maintainers can update the commits based on clause (c) of the DCO. +The most trivial way for a contributor to allow the maintainer to do this, is to +add a DCO signature in a pull requests's comment, or a maintainer can simply +note that the change is sufficiently trivial that it does not substantially +change the existing contribution - i.e., a spelling change. + +When you add someone's DCO, please also add your own to keep a log. + +## I'm a maintainer. Should I make pull requests too? + +Yes. Nobody should ever push to master directly. All changes should be +made through a pull request. + +## Conflict Resolution + +If you have a technical dispute that you feel has reached an impasse with a +subset of the community, any contributor may open an issue, specifically +calling for a resolution vote of the current core maintainers to resolve the +dispute. The same voting quorums required (2/3) for adding and removing +maintainers will apply to conflict resolution. diff --git a/vendor/github.com/distribution/reference/LICENSE b/vendor/github.com/distribution/reference/LICENSE new file mode 100644 index 00000000..e06d2081 --- /dev/null +++ b/vendor/github.com/distribution/reference/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/vendor/github.com/distribution/reference/MAINTAINERS b/vendor/github.com/distribution/reference/MAINTAINERS new file mode 100644 index 00000000..9e0a60c8 --- /dev/null +++ b/vendor/github.com/distribution/reference/MAINTAINERS @@ -0,0 +1,26 @@ +# Distribution project maintainers & reviewers +# +# See GOVERNANCE.md for maintainer versus reviewer roles +# +# MAINTAINERS (cncf-distribution-maintainers@lists.cncf.io) +# GitHub ID, Name, Email address +"chrispat","Chris Patterson","chrispat@github.com" +"clarkbw","Bryan Clark","clarkbw@github.com" +"corhere","Cory Snider","csnider@mirantis.com" +"deleteriousEffect","Hayley Swimelar","hswimelar@gitlab.com" +"heww","He Weiwei","hweiwei@vmware.com" +"joaodrp","João Pereira","jpereira@gitlab.com" +"justincormack","Justin Cormack","justin.cormack@docker.com" +"squizzi","Kyle Squizzato","ksquizzato@mirantis.com" +"milosgajdos","Milos Gajdos","milosthegajdos@gmail.com" +"sargun","Sargun Dhillon","sargun@sargun.me" +"wy65701436","Wang Yan","wangyan@vmware.com" +"stevelasker","Steve Lasker","steve.lasker@microsoft.com" +# +# REVIEWERS +# GitHub ID, Name, Email address +"dmcgowan","Derek McGowan","derek@mcgstyle.net" +"stevvooe","Stephen Day","stevvooe@gmail.com" +"thajeztah","Sebastiaan van Stijn","github@gone.nl" +"DavidSpek", "David van der Spek", "vanderspek.david@gmail.com" +"Jamstah", "James Hewitt", "james.hewitt@gmail.com" diff --git a/vendor/github.com/distribution/reference/Makefile b/vendor/github.com/distribution/reference/Makefile new file mode 100644 index 00000000..c78576b7 --- /dev/null +++ b/vendor/github.com/distribution/reference/Makefile @@ -0,0 +1,25 @@ +# Project packages. +PACKAGES=$(shell go list ./...) + +# Flags passed to `go test` +BUILDFLAGS ?= +TESTFLAGS ?= + +.PHONY: all build test coverage +.DEFAULT: all + +all: build + +build: ## no binaries to build, so just check compilation suceeds + go build ${BUILDFLAGS} ./... + +test: ## run tests + go test ${TESTFLAGS} ./... + +coverage: ## generate coverprofiles from the unit tests + rm -f coverage.txt + go test ${TESTFLAGS} -cover -coverprofile=cover.out ./... + +.PHONY: help +help: + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_\/%-]+:.*?##/ { printf " \033[36m%-27s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) diff --git a/vendor/github.com/distribution/reference/README.md b/vendor/github.com/distribution/reference/README.md new file mode 100644 index 00000000..172a02e0 --- /dev/null +++ b/vendor/github.com/distribution/reference/README.md @@ -0,0 +1,30 @@ +# Distribution reference + +Go library to handle references to container images. + + + +[![Build Status](https://github.com/distribution/reference/actions/workflows/test.yml/badge.svg?branch=main&event=push)](https://github.com/distribution/reference/actions?query=workflow%3ACI) +[![GoDoc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/distribution/reference) +[![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE) +[![codecov](https://codecov.io/gh/distribution/reference/branch/main/graph/badge.svg)](https://codecov.io/gh/distribution/reference) +[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Freference.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Freference?ref=badge_shield) + +This repository contains a library for handling references to container images held in container registries. Please see [godoc](https://pkg.go.dev/github.com/distribution/reference) for details. + +## Contribution + +Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute +issues, fixes, and patches to this project. + +## Communication + +For async communication and long running discussions please use issues and pull requests on the github repo. +This will be the best place to discuss design and implementation. + +For sync communication we have a #distribution channel in the [CNCF Slack](https://slack.cncf.io/) +that everyone is welcome to join and chat about development. + +## Licenses + +The distribution codebase is released under the [Apache 2.0 license](LICENSE). diff --git a/vendor/github.com/distribution/reference/SECURITY.md b/vendor/github.com/distribution/reference/SECURITY.md new file mode 100644 index 00000000..aaf983c0 --- /dev/null +++ b/vendor/github.com/distribution/reference/SECURITY.md @@ -0,0 +1,7 @@ +# Security Policy + +## Reporting a Vulnerability + +The maintainers take security seriously. If you discover a security issue, please bring it to their attention right away! + +Please DO NOT file a public issue, instead send your report privately to cncf-distribution-security@lists.cncf.io. diff --git a/vendor/github.com/distribution/reference/distribution-logo.svg b/vendor/github.com/distribution/reference/distribution-logo.svg new file mode 100644 index 00000000..cc9f4073 --- /dev/null +++ b/vendor/github.com/distribution/reference/distribution-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vendor/github.com/distribution/reference/helpers.go b/vendor/github.com/distribution/reference/helpers.go new file mode 100644 index 00000000..d10c7ef8 --- /dev/null +++ b/vendor/github.com/distribution/reference/helpers.go @@ -0,0 +1,42 @@ +package reference + +import "path" + +// IsNameOnly returns true if reference only contains a repo name. +func IsNameOnly(ref Named) bool { + if _, ok := ref.(NamedTagged); ok { + return false + } + if _, ok := ref.(Canonical); ok { + return false + } + return true +} + +// FamiliarName returns the familiar name string +// for the given named, familiarizing if needed. +func FamiliarName(ref Named) string { + if nn, ok := ref.(normalizedNamed); ok { + return nn.Familiar().Name() + } + return ref.Name() +} + +// FamiliarString returns the familiar string representation +// for the given reference, familiarizing if needed. +func FamiliarString(ref Reference) string { + if nn, ok := ref.(normalizedNamed); ok { + return nn.Familiar().String() + } + return ref.String() +} + +// FamiliarMatch reports whether ref matches the specified pattern. +// See [path.Match] for supported patterns. +func FamiliarMatch(pattern string, ref Reference) (bool, error) { + matched, err := path.Match(pattern, FamiliarString(ref)) + if namedRef, isNamed := ref.(Named); isNamed && !matched { + matched, _ = path.Match(pattern, FamiliarName(namedRef)) + } + return matched, err +} diff --git a/vendor/github.com/distribution/reference/normalize.go b/vendor/github.com/distribution/reference/normalize.go new file mode 100644 index 00000000..f4128314 --- /dev/null +++ b/vendor/github.com/distribution/reference/normalize.go @@ -0,0 +1,255 @@ +package reference + +import ( + "fmt" + "strings" + + "github.com/opencontainers/go-digest" +) + +const ( + // legacyDefaultDomain is the legacy domain for Docker Hub (which was + // originally named "the Docker Index"). This domain is still used for + // authentication and image search, which were part of the "v1" Docker + // registry specification. + // + // This domain will continue to be supported, but there are plans to consolidate + // legacy domains to new "canonical" domains. Once those domains are decided + // on, we must update the normalization functions, but preserve compatibility + // with existing installs, clients, and user configuration. + legacyDefaultDomain = "index.docker.io" + + // defaultDomain is the default domain used for images on Docker Hub. + // It is used to normalize "familiar" names to canonical names, for example, + // to convert "ubuntu" to "docker.io/library/ubuntu:latest". + // + // Note that actual domain of Docker Hub's registry is registry-1.docker.io. + // This domain will continue to be supported, but there are plans to consolidate + // legacy domains to new "canonical" domains. Once those domains are decided + // on, we must update the normalization functions, but preserve compatibility + // with existing installs, clients, and user configuration. + defaultDomain = "docker.io" + + // officialRepoPrefix is the namespace used for official images on Docker Hub. + // It is used to normalize "familiar" names to canonical names, for example, + // to convert "ubuntu" to "docker.io/library/ubuntu:latest". + officialRepoPrefix = "library/" + + // defaultTag is the default tag if no tag is provided. + defaultTag = "latest" +) + +// normalizedNamed represents a name which has been +// normalized and has a familiar form. A familiar name +// is what is used in Docker UI. An example normalized +// name is "docker.io/library/ubuntu" and corresponding +// familiar name of "ubuntu". +type normalizedNamed interface { + Named + Familiar() Named +} + +// ParseNormalizedNamed parses a string into a named reference +// transforming a familiar name from Docker UI to a fully +// qualified reference. If the value may be an identifier +// use ParseAnyReference. +func ParseNormalizedNamed(s string) (Named, error) { + if ok := anchoredIdentifierRegexp.MatchString(s); ok { + return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s) + } + domain, remainder := splitDockerDomain(s) + var remote string + if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 { + remote = remainder[:tagSep] + } else { + remote = remainder + } + if strings.ToLower(remote) != remote { + return nil, fmt.Errorf("invalid reference format: repository name (%s) must be lowercase", remote) + } + + ref, err := Parse(domain + "/" + remainder) + if err != nil { + return nil, err + } + named, isNamed := ref.(Named) + if !isNamed { + return nil, fmt.Errorf("reference %s has no name", ref.String()) + } + return named, nil +} + +// namedTaggedDigested is a reference that has both a tag and a digest. +type namedTaggedDigested interface { + NamedTagged + Digested +} + +// ParseDockerRef normalizes the image reference following the docker convention, +// which allows for references to contain both a tag and a digest. It returns a +// reference that is either tagged or digested. For references containing both +// a tag and a digest, it returns a digested reference. For example, the following +// reference: +// +// docker.io/library/busybox:latest@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa +// +// Is returned as a digested reference (with the ":latest" tag removed): +// +// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa +// +// References that are already "tagged" or "digested" are returned unmodified: +// +// // Already a digested reference +// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa +// +// // Already a named reference +// docker.io/library/busybox:latest +func ParseDockerRef(ref string) (Named, error) { + named, err := ParseNormalizedNamed(ref) + if err != nil { + return nil, err + } + if canonical, ok := named.(namedTaggedDigested); ok { + // The reference is both tagged and digested; only return digested. + newNamed, err := WithName(canonical.Name()) + if err != nil { + return nil, err + } + return WithDigest(newNamed, canonical.Digest()) + } + return TagNameOnly(named), nil +} + +// splitDockerDomain splits a repository name to domain and remote-name. +// If no valid domain is found, the default domain is used. Repository name +// needs to be already validated before. +func splitDockerDomain(name string) (domain, remoteName string) { + maybeDomain, maybeRemoteName, ok := strings.Cut(name, "/") + if !ok { + // Fast-path for single element ("familiar" names), such as "ubuntu" + // or "ubuntu:latest". Familiar names must be handled separately, to + // prevent them from being handled as "hostname:port". + // + // Canonicalize them as "docker.io/library/name[:tag]" + + // FIXME(thaJeztah): account for bare "localhost" or "example.com" names, which SHOULD be considered a domain. + return defaultDomain, officialRepoPrefix + name + } + + switch { + case maybeDomain == localhost: + // localhost is a reserved namespace and always considered a domain. + domain, remoteName = maybeDomain, maybeRemoteName + case maybeDomain == legacyDefaultDomain: + // canonicalize the Docker Hub and legacy "Docker Index" domains. + domain, remoteName = defaultDomain, maybeRemoteName + case strings.ContainsAny(maybeDomain, ".:"): + // Likely a domain or IP-address: + // + // - contains a "." (e.g., "example.com" or "127.0.0.1") + // - contains a ":" (e.g., "example:5000", "::1", or "[::1]:5000") + domain, remoteName = maybeDomain, maybeRemoteName + case strings.ToLower(maybeDomain) != maybeDomain: + // Uppercase namespaces are not allowed, so if the first element + // is not lowercase, we assume it to be a domain-name. + domain, remoteName = maybeDomain, maybeRemoteName + default: + // None of the above: it's not a domain, so use the default, and + // use the name input the remote-name. + domain, remoteName = defaultDomain, name + } + + if domain == defaultDomain && !strings.ContainsRune(remoteName, '/') { + // Canonicalize "familiar" names, but only on Docker Hub, not + // on other domains: + // + // "docker.io/ubuntu[:tag]" => "docker.io/library/ubuntu[:tag]" + remoteName = officialRepoPrefix + remoteName + } + + return domain, remoteName +} + +// familiarizeName returns a shortened version of the name familiar +// to the Docker UI. Familiar names have the default domain +// "docker.io" and "library/" repository prefix removed. +// For example, "docker.io/library/redis" will have the familiar +// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp". +// Returns a familiarized named only reference. +func familiarizeName(named namedRepository) repository { + repo := repository{ + domain: named.Domain(), + path: named.Path(), + } + + if repo.domain == defaultDomain { + repo.domain = "" + // Handle official repositories which have the pattern "library/" + if strings.HasPrefix(repo.path, officialRepoPrefix) { + // TODO(thaJeztah): this check may be too strict, as it assumes the + // "library/" namespace does not have nested namespaces. While this + // is true (currently), technically it would be possible for Docker + // Hub to use those (e.g. "library/distros/ubuntu:latest"). + // See https://github.com/distribution/distribution/pull/3769#issuecomment-1302031785. + if remainder := strings.TrimPrefix(repo.path, officialRepoPrefix); !strings.ContainsRune(remainder, '/') { + repo.path = remainder + } + } + } + return repo +} + +func (r reference) Familiar() Named { + return reference{ + namedRepository: familiarizeName(r.namedRepository), + tag: r.tag, + digest: r.digest, + } +} + +func (r repository) Familiar() Named { + return familiarizeName(r) +} + +func (t taggedReference) Familiar() Named { + return taggedReference{ + namedRepository: familiarizeName(t.namedRepository), + tag: t.tag, + } +} + +func (c canonicalReference) Familiar() Named { + return canonicalReference{ + namedRepository: familiarizeName(c.namedRepository), + digest: c.digest, + } +} + +// TagNameOnly adds the default tag "latest" to a reference if it only has +// a repo name. +func TagNameOnly(ref Named) Named { + if IsNameOnly(ref) { + namedTagged, err := WithTag(ref, defaultTag) + if err != nil { + // Default tag must be valid, to create a NamedTagged + // type with non-validated input the WithTag function + // should be used instead + panic(err) + } + return namedTagged + } + return ref +} + +// ParseAnyReference parses a reference string as a possible identifier, +// full digest, or familiar name. +func ParseAnyReference(ref string) (Reference, error) { + if ok := anchoredIdentifierRegexp.MatchString(ref); ok { + return digestReference("sha256:" + ref), nil + } + if dgst, err := digest.Parse(ref); err == nil { + return digestReference(dgst), nil + } + + return ParseNormalizedNamed(ref) +} diff --git a/vendor/github.com/distribution/reference/reference.go b/vendor/github.com/distribution/reference/reference.go new file mode 100644 index 00000000..900398bd --- /dev/null +++ b/vendor/github.com/distribution/reference/reference.go @@ -0,0 +1,432 @@ +// Package reference provides a general type to represent any way of referencing images within the registry. +// Its main purpose is to abstract tags and digests (content-addressable hash). +// +// Grammar +// +// reference := name [ ":" tag ] [ "@" digest ] +// name := [domain '/'] remote-name +// domain := host [':' port-number] +// host := domain-name | IPv4address | \[ IPv6address \] ; rfc3986 appendix-A +// domain-name := domain-component ['.' domain-component]* +// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/ +// port-number := /[0-9]+/ +// path-component := alpha-numeric [separator alpha-numeric]* +// path (or "remote-name") := path-component ['/' path-component]* +// alpha-numeric := /[a-z0-9]+/ +// separator := /[_.]|__|[-]*/ +// +// tag := /[\w][\w.-]{0,127}/ +// +// digest := digest-algorithm ":" digest-hex +// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]* +// digest-algorithm-separator := /[+.-_]/ +// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/ +// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value +// +// identifier := /[a-f0-9]{64}/ +package reference + +import ( + "errors" + "fmt" + "strings" + + "github.com/opencontainers/go-digest" +) + +const ( + // RepositoryNameTotalLengthMax is the maximum total number of characters in a repository name. + RepositoryNameTotalLengthMax = 255 + + // NameTotalLengthMax is the maximum total number of characters in a repository name. + // + // Deprecated: use [RepositoryNameTotalLengthMax] instead. + NameTotalLengthMax = RepositoryNameTotalLengthMax +) + +var ( + // ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference. + ErrReferenceInvalidFormat = errors.New("invalid reference format") + + // ErrTagInvalidFormat represents an error while trying to parse a string as a tag. + ErrTagInvalidFormat = errors.New("invalid tag format") + + // ErrDigestInvalidFormat represents an error while trying to parse a string as a tag. + ErrDigestInvalidFormat = errors.New("invalid digest format") + + // ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters. + ErrNameContainsUppercase = errors.New("repository name must be lowercase") + + // ErrNameEmpty is returned for empty, invalid repository names. + ErrNameEmpty = errors.New("repository name must have at least one component") + + // ErrNameTooLong is returned when a repository name is longer than RepositoryNameTotalLengthMax. + ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", RepositoryNameTotalLengthMax) + + // ErrNameNotCanonical is returned when a name is not canonical. + ErrNameNotCanonical = errors.New("repository name must be canonical") +) + +// Reference is an opaque object reference identifier that may include +// modifiers such as a hostname, name, tag, and digest. +type Reference interface { + // String returns the full reference + String() string +} + +// Field provides a wrapper type for resolving correct reference types when +// working with encoding. +type Field struct { + reference Reference +} + +// AsField wraps a reference in a Field for encoding. +func AsField(reference Reference) Field { + return Field{reference} +} + +// Reference unwraps the reference type from the field to +// return the Reference object. This object should be +// of the appropriate type to further check for different +// reference types. +func (f Field) Reference() Reference { + return f.reference +} + +// MarshalText serializes the field to byte text which +// is the string of the reference. +func (f Field) MarshalText() (p []byte, err error) { + return []byte(f.reference.String()), nil +} + +// UnmarshalText parses text bytes by invoking the +// reference parser to ensure the appropriately +// typed reference object is wrapped by field. +func (f *Field) UnmarshalText(p []byte) error { + r, err := Parse(string(p)) + if err != nil { + return err + } + + f.reference = r + return nil +} + +// Named is an object with a full name +type Named interface { + Reference + Name() string +} + +// Tagged is an object which has a tag +type Tagged interface { + Reference + Tag() string +} + +// NamedTagged is an object including a name and tag. +type NamedTagged interface { + Named + Tag() string +} + +// Digested is an object which has a digest +// in which it can be referenced by +type Digested interface { + Reference + Digest() digest.Digest +} + +// Canonical reference is an object with a fully unique +// name including a name with domain and digest +type Canonical interface { + Named + Digest() digest.Digest +} + +// namedRepository is a reference to a repository with a name. +// A namedRepository has both domain and path components. +type namedRepository interface { + Named + Domain() string + Path() string +} + +// Domain returns the domain part of the [Named] reference. +func Domain(named Named) string { + if r, ok := named.(namedRepository); ok { + return r.Domain() + } + domain, _ := splitDomain(named.Name()) + return domain +} + +// Path returns the name without the domain part of the [Named] reference. +func Path(named Named) (name string) { + if r, ok := named.(namedRepository); ok { + return r.Path() + } + _, path := splitDomain(named.Name()) + return path +} + +// splitDomain splits a named reference into a hostname and path string. +// If no valid hostname is found, the hostname is empty and the full value +// is returned as name +func splitDomain(name string) (string, string) { + match := anchoredNameRegexp.FindStringSubmatch(name) + if len(match) != 3 { + return "", name + } + return match[1], match[2] +} + +// Parse parses s and returns a syntactically valid Reference. +// If an error was encountered it is returned, along with a nil Reference. +func Parse(s string) (Reference, error) { + matches := ReferenceRegexp.FindStringSubmatch(s) + if matches == nil { + if s == "" { + return nil, ErrNameEmpty + } + if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil { + return nil, ErrNameContainsUppercase + } + return nil, ErrReferenceInvalidFormat + } + + var repo repository + + nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1]) + if len(nameMatch) == 3 { + repo.domain = nameMatch[1] + repo.path = nameMatch[2] + } else { + repo.domain = "" + repo.path = matches[1] + } + + if len(repo.path) > RepositoryNameTotalLengthMax { + return nil, ErrNameTooLong + } + + ref := reference{ + namedRepository: repo, + tag: matches[2], + } + if matches[3] != "" { + var err error + ref.digest, err = digest.Parse(matches[3]) + if err != nil { + return nil, err + } + } + + r := getBestReferenceType(ref) + if r == nil { + return nil, ErrNameEmpty + } + + return r, nil +} + +// ParseNamed parses s and returns a syntactically valid reference implementing +// the Named interface. The reference must have a name and be in the canonical +// form, otherwise an error is returned. +// If an error was encountered it is returned, along with a nil Reference. +func ParseNamed(s string) (Named, error) { + named, err := ParseNormalizedNamed(s) + if err != nil { + return nil, err + } + if named.String() != s { + return nil, ErrNameNotCanonical + } + return named, nil +} + +// WithName returns a named object representing the given string. If the input +// is invalid ErrReferenceInvalidFormat will be returned. +func WithName(name string) (Named, error) { + match := anchoredNameRegexp.FindStringSubmatch(name) + if match == nil || len(match) != 3 { + return nil, ErrReferenceInvalidFormat + } + + if len(match[2]) > RepositoryNameTotalLengthMax { + return nil, ErrNameTooLong + } + + return repository{ + domain: match[1], + path: match[2], + }, nil +} + +// WithTag combines the name from "name" and the tag from "tag" to form a +// reference incorporating both the name and the tag. +func WithTag(name Named, tag string) (NamedTagged, error) { + if !anchoredTagRegexp.MatchString(tag) { + return nil, ErrTagInvalidFormat + } + var repo repository + if r, ok := name.(namedRepository); ok { + repo.domain = r.Domain() + repo.path = r.Path() + } else { + repo.path = name.Name() + } + if canonical, ok := name.(Canonical); ok { + return reference{ + namedRepository: repo, + tag: tag, + digest: canonical.Digest(), + }, nil + } + return taggedReference{ + namedRepository: repo, + tag: tag, + }, nil +} + +// WithDigest combines the name from "name" and the digest from "digest" to form +// a reference incorporating both the name and the digest. +func WithDigest(name Named, digest digest.Digest) (Canonical, error) { + if !anchoredDigestRegexp.MatchString(digest.String()) { + return nil, ErrDigestInvalidFormat + } + var repo repository + if r, ok := name.(namedRepository); ok { + repo.domain = r.Domain() + repo.path = r.Path() + } else { + repo.path = name.Name() + } + if tagged, ok := name.(Tagged); ok { + return reference{ + namedRepository: repo, + tag: tagged.Tag(), + digest: digest, + }, nil + } + return canonicalReference{ + namedRepository: repo, + digest: digest, + }, nil +} + +// TrimNamed removes any tag or digest from the named reference. +func TrimNamed(ref Named) Named { + repo := repository{} + if r, ok := ref.(namedRepository); ok { + repo.domain, repo.path = r.Domain(), r.Path() + } else { + repo.domain, repo.path = splitDomain(ref.Name()) + } + return repo +} + +func getBestReferenceType(ref reference) Reference { + if ref.Name() == "" { + // Allow digest only references + if ref.digest != "" { + return digestReference(ref.digest) + } + return nil + } + if ref.tag == "" { + if ref.digest != "" { + return canonicalReference{ + namedRepository: ref.namedRepository, + digest: ref.digest, + } + } + return ref.namedRepository + } + if ref.digest == "" { + return taggedReference{ + namedRepository: ref.namedRepository, + tag: ref.tag, + } + } + + return ref +} + +type reference struct { + namedRepository + tag string + digest digest.Digest +} + +func (r reference) String() string { + return r.Name() + ":" + r.tag + "@" + r.digest.String() +} + +func (r reference) Tag() string { + return r.tag +} + +func (r reference) Digest() digest.Digest { + return r.digest +} + +type repository struct { + domain string + path string +} + +func (r repository) String() string { + return r.Name() +} + +func (r repository) Name() string { + if r.domain == "" { + return r.path + } + return r.domain + "/" + r.path +} + +func (r repository) Domain() string { + return r.domain +} + +func (r repository) Path() string { + return r.path +} + +type digestReference digest.Digest + +func (d digestReference) String() string { + return digest.Digest(d).String() +} + +func (d digestReference) Digest() digest.Digest { + return digest.Digest(d) +} + +type taggedReference struct { + namedRepository + tag string +} + +func (t taggedReference) String() string { + return t.Name() + ":" + t.tag +} + +func (t taggedReference) Tag() string { + return t.tag +} + +type canonicalReference struct { + namedRepository + digest digest.Digest +} + +func (c canonicalReference) String() string { + return c.Name() + "@" + c.digest.String() +} + +func (c canonicalReference) Digest() digest.Digest { + return c.digest +} diff --git a/vendor/github.com/distribution/reference/regexp.go b/vendor/github.com/distribution/reference/regexp.go new file mode 100644 index 00000000..65bc49d7 --- /dev/null +++ b/vendor/github.com/distribution/reference/regexp.go @@ -0,0 +1,163 @@ +package reference + +import ( + "regexp" + "strings" +) + +// DigestRegexp matches well-formed digests, including algorithm (e.g. "sha256:"). +var DigestRegexp = regexp.MustCompile(digestPat) + +// DomainRegexp matches hostname or IP-addresses, optionally including a port +// number. It defines the structure of potential domain components that may be +// part of image names. This is purposely a subset of what is allowed by DNS to +// ensure backwards compatibility with Docker image names. It may be a subset of +// DNS domain name, an IPv4 address in decimal format, or an IPv6 address between +// square brackets (excluding zone identifiers as defined by [RFC 6874] or special +// addresses such as IPv4-Mapped). +// +// [RFC 6874]: https://www.rfc-editor.org/rfc/rfc6874. +var DomainRegexp = regexp.MustCompile(domainAndPort) + +// IdentifierRegexp is the format for string identifier used as a +// content addressable identifier using sha256. These identifiers +// are like digests without the algorithm, since sha256 is used. +var IdentifierRegexp = regexp.MustCompile(identifier) + +// NameRegexp is the format for the name component of references, including +// an optional domain and port, but without tag or digest suffix. +var NameRegexp = regexp.MustCompile(namePat) + +// ReferenceRegexp is the full supported format of a reference. The regexp +// is anchored and has capturing groups for name, tag, and digest +// components. +var ReferenceRegexp = regexp.MustCompile(referencePat) + +// TagRegexp matches valid tag names. From [docker/docker:graph/tags.go]. +// +// [docker/docker:graph/tags.go]: https://github.com/moby/moby/blob/v1.6.0/graph/tags.go#L26-L28 +var TagRegexp = regexp.MustCompile(tag) + +const ( + // alphanumeric defines the alphanumeric atom, typically a + // component of names. This only allows lower case characters and digits. + alphanumeric = `[a-z0-9]+` + + // separator defines the separators allowed to be embedded in name + // components. This allows one period, one or two underscore and multiple + // dashes. Repeated dashes and underscores are intentionally treated + // differently. In order to support valid hostnames as name components, + // supporting repeated dash was added. Additionally double underscore is + // now allowed as a separator to loosen the restriction for previously + // supported names. + separator = `(?:[._]|__|[-]+)` + + // localhost is treated as a special value for domain-name. Any other + // domain-name without a "." or a ":port" are considered a path component. + localhost = `localhost` + + // domainNameComponent restricts the registry domain component of a + // repository name to start with a component as defined by DomainRegexp. + domainNameComponent = `(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])` + + // optionalPort matches an optional port-number including the port separator + // (e.g. ":80"). + optionalPort = `(?::[0-9]+)?` + + // tag matches valid tag names. From docker/docker:graph/tags.go. + tag = `[\w][\w.-]{0,127}` + + // digestPat matches well-formed digests, including algorithm (e.g. "sha256:"). + // + // TODO(thaJeztah): this should follow the same rules as https://pkg.go.dev/github.com/opencontainers/go-digest@v1.0.0#DigestRegexp + // so that go-digest defines the canonical format. Note that the go-digest is + // more relaxed: + // - it allows multiple algorithms (e.g. "sha256+b64:") to allow + // future expansion of supported algorithms. + // - it allows the "" value to use urlsafe base64 encoding as defined + // in [rfc4648, section 5]. + // + // [rfc4648, section 5]: https://www.rfc-editor.org/rfc/rfc4648#section-5. + digestPat = `[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}` + + // identifier is the format for a content addressable identifier using sha256. + // These identifiers are like digests without the algorithm, since sha256 is used. + identifier = `([a-f0-9]{64})` + + // ipv6address are enclosed between square brackets and may be represented + // in many ways, see rfc5952. Only IPv6 in compressed or uncompressed format + // are allowed, IPv6 zone identifiers (rfc6874) or Special addresses such as + // IPv4-Mapped are deliberately excluded. + ipv6address = `\[(?:[a-fA-F0-9:]+)\]` +) + +var ( + // domainName defines the structure of potential domain components + // that may be part of image names. This is purposely a subset of what is + // allowed by DNS to ensure backwards compatibility with Docker image + // names. This includes IPv4 addresses on decimal format. + domainName = domainNameComponent + anyTimes(`\.`+domainNameComponent) + + // host defines the structure of potential domains based on the URI + // Host subcomponent on rfc3986. It may be a subset of DNS domain name, + // or an IPv4 address in decimal format, or an IPv6 address between square + // brackets (excluding zone identifiers as defined by rfc6874 or special + // addresses such as IPv4-Mapped). + host = `(?:` + domainName + `|` + ipv6address + `)` + + // allowed by the URI Host subcomponent on rfc3986 to ensure backwards + // compatibility with Docker image names. + domainAndPort = host + optionalPort + + // anchoredTagRegexp matches valid tag names, anchored at the start and + // end of the matched string. + anchoredTagRegexp = regexp.MustCompile(anchored(tag)) + + // anchoredDigestRegexp matches valid digests, anchored at the start and + // end of the matched string. + anchoredDigestRegexp = regexp.MustCompile(anchored(digestPat)) + + // pathComponent restricts path-components to start with an alphanumeric + // character, with following parts able to be separated by a separator + // (one period, one or two underscore and multiple dashes). + pathComponent = alphanumeric + anyTimes(separator+alphanumeric) + + // remoteName matches the remote-name of a repository. It consists of one + // or more forward slash (/) delimited path-components: + // + // pathComponent[[/pathComponent] ...] // e.g., "library/ubuntu" + remoteName = pathComponent + anyTimes(`/`+pathComponent) + namePat = optional(domainAndPort+`/`) + remoteName + + // anchoredNameRegexp is used to parse a name value, capturing the + // domain and trailing components. + anchoredNameRegexp = regexp.MustCompile(anchored(optional(capture(domainAndPort), `/`), capture(remoteName))) + + referencePat = anchored(capture(namePat), optional(`:`, capture(tag)), optional(`@`, capture(digestPat))) + + // anchoredIdentifierRegexp is used to check or match an + // identifier value, anchored at start and end of string. + anchoredIdentifierRegexp = regexp.MustCompile(anchored(identifier)) +) + +// optional wraps the expression in a non-capturing group and makes the +// production optional. +func optional(res ...string) string { + return `(?:` + strings.Join(res, "") + `)?` +} + +// anyTimes wraps the expression in a non-capturing group that can occur +// any number of times. +func anyTimes(res ...string) string { + return `(?:` + strings.Join(res, "") + `)*` +} + +// capture wraps the expression in a capturing group. +func capture(res ...string) string { + return `(` + strings.Join(res, "") + `)` +} + +// anchored anchors the regular expression by adding start and end delimiters. +func anchored(res ...string) string { + return `^` + strings.Join(res, "") + `$` +} diff --git a/vendor/github.com/distribution/reference/sort.go b/vendor/github.com/distribution/reference/sort.go new file mode 100644 index 00000000..416c37b0 --- /dev/null +++ b/vendor/github.com/distribution/reference/sort.go @@ -0,0 +1,75 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package reference + +import ( + "sort" +) + +// Sort sorts string references preferring higher information references. +// +// The precedence is as follows: +// +// 1. [Named] + [Tagged] + [Digested] (e.g., "docker.io/library/busybox:latest@sha256:") +// 2. [Named] + [Tagged] (e.g., "docker.io/library/busybox:latest") +// 3. [Named] + [Digested] (e.g., "docker.io/library/busybo@sha256:") +// 4. [Named] (e.g., "docker.io/library/busybox") +// 5. [Digested] (e.g., "docker.io@sha256:") +// 6. Parse error +func Sort(references []string) []string { + var prefs []Reference + var bad []string + + for _, ref := range references { + pref, err := ParseAnyReference(ref) + if err != nil { + bad = append(bad, ref) + } else { + prefs = append(prefs, pref) + } + } + sort.Slice(prefs, func(a, b int) bool { + ar := refRank(prefs[a]) + br := refRank(prefs[b]) + if ar == br { + return prefs[a].String() < prefs[b].String() + } + return ar < br + }) + sort.Strings(bad) + var refs []string + for _, pref := range prefs { + refs = append(refs, pref.String()) + } + return append(refs, bad...) +} + +func refRank(ref Reference) uint8 { + if _, ok := ref.(Named); ok { + if _, ok = ref.(Tagged); ok { + if _, ok = ref.(Digested); ok { + return 1 + } + return 2 + } + if _, ok = ref.(Digested); ok { + return 3 + } + return 4 + } + return 5 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 018b7d08..f5a86b1c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -134,6 +134,9 @@ github.com/denormal/go-gitignore # github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 ## explicit; go 1.12 github.com/dgrijalva/jwt-go/v4 +# github.com/distribution/reference v0.6.0 +## explicit; go 1.20 +github.com/distribution/reference # github.com/emicklei/go-restful/v3 v3.10.2 ## explicit; go 1.13 github.com/emicklei/go-restful/v3