diff --git a/go.mod b/go.mod index 6364081e25..c69ccecb56 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( k8s.io/client-go v0.25.6 k8s.io/code-generator v0.25.6 k8s.io/kubectl v0.25.6 - k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 + k8s.io/utils v0.0.0-20230726121419-3b25d923346b knative.dev/pkg v0.0.0-20221123011842-b78020c16606 sigs.k8s.io/controller-runtime v0.13.1 sigs.k8s.io/yaml v1.3.0 diff --git a/go.sum b/go.sum index 6daaffe62d..d68407fdbe 100644 --- a/go.sum +++ b/go.sum @@ -837,8 +837,8 @@ k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+O k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/kubectl v0.25.6 h1:uGoNw4vF+O62NTwRqM40gMCEI5ujw2g0ocT6dDPt55c= k8s.io/kubectl v0.25.6/go.mod h1:sqtVRxDlZdaSusmLVysYo8S7h4mB/Fe8YWvt2KCle3k= -k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 h1:cTdVh7LYu82xeClmfzGtgyspNh6UxpwLWGi8R4sspNo= -k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= knative.dev/pkg v0.0.0-20221123011842-b78020c16606 h1:bHozIhJtQE9/SVQkVX2vqnoJGJt6zo7J0jnkLFX3Crw= knative.dev/pkg v0.0.0-20221123011842-b78020c16606/go.mod h1:DMTRDJ5WRxf/DrlOPzohzfhSuJggscLZ8EavOq9O/x8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/pkg/image/delete_test.go b/pkg/image/delete_test.go index 0ecb592fc7..74193d1923 100644 --- a/pkg/image/delete_test.go +++ b/pkg/image/delete_test.go @@ -17,7 +17,7 @@ import ( "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/shipwright-io/build/pkg/image" - "github.com/shipwright-io/build/test/utils" + utils "github.com/shipwright-io/build/test/utils/v1alpha1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/pkg/reconciler/build/build_test.go b/pkg/reconciler/build/build_test.go index f22136fcfe..c399ee49e7 100644 --- a/pkg/reconciler/build/build_test.go +++ b/pkg/reconciler/build/build_test.go @@ -24,7 +24,7 @@ import ( "github.com/shipwright-io/build/pkg/config" "github.com/shipwright-io/build/pkg/controller/fakes" buildController "github.com/shipwright-io/build/pkg/reconciler/build" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1alpha1_samples" ) var _ = Describe("Reconcile Build", func() { diff --git a/pkg/reconciler/buildrun/buildrun_test.go b/pkg/reconciler/buildrun/buildrun_test.go index 197dd7b039..7dd8cf45cd 100644 --- a/pkg/reconciler/buildrun/buildrun_test.go +++ b/pkg/reconciler/buildrun/buildrun_test.go @@ -34,7 +34,7 @@ import ( "github.com/shipwright-io/build/pkg/controller/fakes" buildrunctl "github.com/shipwright-io/build/pkg/reconciler/buildrun" "github.com/shipwright-io/build/pkg/reconciler/buildrun/resources" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1alpha1_samples" ) var _ = Describe("Reconcile BuildRun", func() { diff --git a/pkg/reconciler/buildrun/resources/build_test.go b/pkg/reconciler/buildrun/resources/build_test.go index 25cf9ffb33..c559f1447f 100644 --- a/pkg/reconciler/buildrun/resources/build_test.go +++ b/pkg/reconciler/buildrun/resources/build_test.go @@ -20,7 +20,7 @@ import ( build "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" "github.com/shipwright-io/build/pkg/controller/fakes" "github.com/shipwright-io/build/pkg/reconciler/buildrun/resources" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1alpha1_samples" ) var _ = Describe("Build Resource", func() { diff --git a/pkg/reconciler/buildrun/resources/conditions_test.go b/pkg/reconciler/buildrun/resources/conditions_test.go index 0560f6fc40..d9306fdd30 100644 --- a/pkg/reconciler/buildrun/resources/conditions_test.go +++ b/pkg/reconciler/buildrun/resources/conditions_test.go @@ -12,7 +12,7 @@ import ( build "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" "github.com/shipwright-io/build/pkg/controller/fakes" "github.com/shipwright-io/build/pkg/reconciler/buildrun/resources" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1alpha1_samples" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" diff --git a/pkg/reconciler/buildrun/resources/image_processing_test.go b/pkg/reconciler/buildrun/resources/image_processing_test.go index c67ecf772c..ebaaa0d741 100644 --- a/pkg/reconciler/buildrun/resources/image_processing_test.go +++ b/pkg/reconciler/buildrun/resources/image_processing_test.go @@ -13,7 +13,7 @@ import ( buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" "github.com/shipwright-io/build/pkg/config" "github.com/shipwright-io/build/pkg/reconciler/buildrun/resources" - "github.com/shipwright-io/build/test/utils" + utils "github.com/shipwright-io/build/test/utils/v1alpha1" pipeline "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" ) diff --git a/pkg/reconciler/buildrun/resources/results_test.go b/pkg/reconciler/buildrun/resources/results_test.go index 6249457ea2..3f09c9a1e6 100644 --- a/pkg/reconciler/buildrun/resources/results_test.go +++ b/pkg/reconciler/buildrun/resources/results_test.go @@ -12,7 +12,7 @@ import ( build "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" "github.com/shipwright-io/build/pkg/reconciler/buildrun/resources" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1alpha1_samples" pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "k8s.io/apimachinery/pkg/types" diff --git a/pkg/reconciler/buildrun/resources/service_accounts_test.go b/pkg/reconciler/buildrun/resources/service_accounts_test.go index 07a1990a8e..370ba73d98 100644 --- a/pkg/reconciler/buildrun/resources/service_accounts_test.go +++ b/pkg/reconciler/buildrun/resources/service_accounts_test.go @@ -13,7 +13,7 @@ import ( buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" "github.com/shipwright-io/build/pkg/controller/fakes" "github.com/shipwright-io/build/pkg/reconciler/buildrun/resources" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1alpha1_samples" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" diff --git a/pkg/reconciler/buildrun/resources/strategies_test.go b/pkg/reconciler/buildrun/resources/strategies_test.go index e4dab4ef70..8e6a5f5f25 100644 --- a/pkg/reconciler/buildrun/resources/strategies_test.go +++ b/pkg/reconciler/buildrun/resources/strategies_test.go @@ -12,7 +12,7 @@ import ( buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" "github.com/shipwright-io/build/pkg/controller/fakes" "github.com/shipwright-io/build/pkg/reconciler/buildrun/resources" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1alpha1_samples" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" diff --git a/pkg/reconciler/buildrun/resources/taskrun_test.go b/pkg/reconciler/buildrun/resources/taskrun_test.go index 954b3793a2..9cdd0f88c0 100644 --- a/pkg/reconciler/buildrun/resources/taskrun_test.go +++ b/pkg/reconciler/buildrun/resources/taskrun_test.go @@ -22,8 +22,8 @@ import ( "github.com/shipwright-io/build/pkg/config" "github.com/shipwright-io/build/pkg/env" "github.com/shipwright-io/build/pkg/reconciler/buildrun/resources" - "github.com/shipwright-io/build/test" - "github.com/shipwright-io/build/test/utils" + utils "github.com/shipwright-io/build/test/utils/v1alpha1" + test "github.com/shipwright-io/build/test/v1alpha1_samples" ) var _ = Describe("GenerateTaskrun", func() { diff --git a/test/e2e/common_suite_test.go b/test/e2e/v1alpha1/common_suite_test.go similarity index 100% rename from test/e2e/common_suite_test.go rename to test/e2e/v1alpha1/common_suite_test.go diff --git a/test/e2e/common_test.go b/test/e2e/v1alpha1/common_test.go similarity index 99% rename from test/e2e/common_test.go rename to test/e2e/v1alpha1/common_test.go index 87443e450e..4c3c9ba7ce 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/v1alpha1/common_test.go @@ -25,7 +25,7 @@ import ( buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" "github.com/shipwright-io/build/pkg/reconciler/buildrun/resources" - "github.com/shipwright-io/build/test/utils" + utils "github.com/shipwright-io/build/test/utils/v1alpha1" ) func generateTestID(id string) string { diff --git a/test/e2e/e2e_bundle_test.go b/test/e2e/v1alpha1/e2e_bundle_test.go similarity index 100% rename from test/e2e/e2e_bundle_test.go rename to test/e2e/v1alpha1/e2e_bundle_test.go diff --git a/test/e2e/e2e_image_mutate_test.go b/test/e2e/v1alpha1/e2e_image_mutate_test.go similarity index 100% rename from test/e2e/e2e_image_mutate_test.go rename to test/e2e/v1alpha1/e2e_image_mutate_test.go diff --git a/test/e2e/e2e_local_source_upload_test.go b/test/e2e/v1alpha1/e2e_local_source_upload_test.go similarity index 98% rename from test/e2e/e2e_local_source_upload_test.go rename to test/e2e/v1alpha1/e2e_local_source_upload_test.go index d3972fa4b5..565f1c8083 100644 --- a/test/e2e/e2e_local_source_upload_test.go +++ b/test/e2e/v1alpha1/e2e_local_source_upload_test.go @@ -13,7 +13,7 @@ import ( "k8s.io/apimachinery/pkg/types" buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" - "github.com/shipwright-io/build/test/utils" + utils "github.com/shipwright-io/build/test/utils/v1alpha1" ) var _ = Describe("For a Kubernetes cluster with Tekton and build installed", func() { diff --git a/test/e2e/e2e_one_off_builds_test.go b/test/e2e/v1alpha1/e2e_one_off_builds_test.go similarity index 100% rename from test/e2e/e2e_one_off_builds_test.go rename to test/e2e/v1alpha1/e2e_one_off_builds_test.go diff --git a/test/e2e/e2e_params_test.go b/test/e2e/v1alpha1/e2e_params_test.go similarity index 98% rename from test/e2e/e2e_params_test.go rename to test/e2e/v1alpha1/e2e_params_test.go index 96c2900282..de46de58d2 100644 --- a/test/e2e/e2e_params_test.go +++ b/test/e2e/v1alpha1/e2e_params_test.go @@ -9,7 +9,7 @@ import ( . "github.com/onsi/gomega" buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1alpha1_samples" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/pointer" diff --git a/test/e2e/e2e_rbac_test.go b/test/e2e/v1alpha1/e2e_rbac_test.go similarity index 100% rename from test/e2e/e2e_rbac_test.go rename to test/e2e/v1alpha1/e2e_rbac_test.go diff --git a/test/e2e/v1alpha1/e2e_suite_test.go b/test/e2e/v1alpha1/e2e_suite_test.go new file mode 100644 index 0000000000..967ee28cab --- /dev/null +++ b/test/e2e/v1alpha1/e2e_suite_test.go @@ -0,0 +1,53 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package e2e_test + +import ( + "os" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + utils "github.com/shipwright-io/build/test/utils/v1alpha1" +) + +var ( + testBuild *utils.TestBuild + stop = make(chan struct{}) +) + +func TestE2E(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "E2E Suite") +} + +var ( + _ = BeforeSuite(func() { + var ( + ok bool + err error + ) + + testBuild, err = utils.NewTestBuild() + Expect(err).ToNot(HaveOccurred()) + + testBuild.Namespace, ok = os.LookupEnv("TEST_NAMESPACE") + Expect(ok).To(BeTrue(), "TEST_NAMESPACE should be set") + Expect(testBuild.Namespace).ToNot(BeEmpty()) + + // create the pipeline service account + Logf("Creating the pipeline service account") + createPipelineServiceAccount(testBuild) + + // create the container registry secret + Logf("Creating the container registry secret") + createContainerRegistrySecret(testBuild) + }) + + _ = AfterSuite(func() { + close(stop) + }) +) diff --git a/test/e2e/e2e_test.go b/test/e2e/v1alpha1/e2e_test.go similarity index 100% rename from test/e2e/e2e_test.go rename to test/e2e/v1alpha1/e2e_test.go diff --git a/test/e2e/validators_test.go b/test/e2e/v1alpha1/validators_test.go similarity index 99% rename from test/e2e/validators_test.go rename to test/e2e/v1alpha1/validators_test.go index fb48b9a166..7bed584c86 100644 --- a/test/e2e/validators_test.go +++ b/test/e2e/v1alpha1/validators_test.go @@ -24,7 +24,7 @@ import ( "github.com/shipwright-io/build/pkg/apis" buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" - "github.com/shipwright-io/build/test/utils" + utils "github.com/shipwright-io/build/test/utils/v1alpha1" ) const ( diff --git a/test/e2e/v1beta1/common_suite_test.go b/test/e2e/v1beta1/common_suite_test.go new file mode 100644 index 0000000000..629df4a510 --- /dev/null +++ b/test/e2e/v1beta1/common_suite_test.go @@ -0,0 +1,430 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package e2e_test + +import ( + "context" + "fmt" + "os" + "strconv" + "strings" + "time" + + . "github.com/onsi/ginkgo/v2" + + core "k8s.io/api/core/v1" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/utils/pointer" + + buildv1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1" +) + +const ( + pollCreateInterval = 1 * time.Second + pollCreateTimeout = 10 * time.Second +) + +type buildPrototype struct{ build buildv1beta1.Build } +type buildRunPrototype struct{ buildRun buildv1beta1.BuildRun } + +func NewBuildPrototype() *buildPrototype { + return &buildPrototype{ + build: buildv1beta1.Build{}, + } +} + +func (b *buildPrototype) Name(name string) *buildPrototype { + b.build.ObjectMeta.Name = name + return b +} + +func (b *buildPrototype) Namespace(namespace string) *buildPrototype { + b.build.ObjectMeta.Namespace = namespace + return b +} + +func (b *buildPrototype) BuildStrategy(name string) *buildPrototype { + var bs = buildv1beta1.NamespacedBuildStrategyKind + b.build.Spec.Strategy = buildv1beta1.Strategy{ + Kind: &bs, + Name: name, + } + return b +} + +func (b *buildPrototype) ClusterBuildStrategy(name string) *buildPrototype { + var cbs = buildv1beta1.ClusterBuildStrategyKind + b.build.Spec.Strategy = buildv1beta1.Strategy{ + Kind: &cbs, + Name: name, + } + return b +} + +func (b *buildPrototype) SourceCredentials(name string) *buildPrototype { + if name != "" { + b.build.Spec.Source.GitSource.CloneSecret = &name + } + + return b +} + +func (b *buildPrototype) SourceGit(repository string) *buildPrototype { + b.build.Spec.Source.GitSource.URL = pointer.String(repository) + b.build.Spec.Source.OCIArtifact = nil + return b +} + +func (b *buildPrototype) SourceBundle(image string) *buildPrototype { + if b.build.Spec.Source.OCIArtifact == nil { + b.build.Spec.Source.OCIArtifact = &buildv1beta1.OCIArtifact{} + } + b.build.Spec.Source.OCIArtifact.Image = image + return b +} + +func (b *buildPrototype) SourceBundlePrune(prune buildv1beta1.PruneOption) *buildPrototype { + if b.build.Spec.Source.OCIArtifact == nil { + b.build.Spec.Source.OCIArtifact = &buildv1beta1.OCIArtifact{} + } + b.build.Spec.Source.OCIArtifact.Prune = &prune + return b +} + +func (b *buildPrototype) SourceContextDir(contextDir string) *buildPrototype { + b.build.Spec.Source.ContextDir = pointer.String(contextDir) + return b +} + +func (b *buildPrototype) Dockerfile(dockerfile string) *buildPrototype { + for _, paramValue := range b.build.Spec.ParamValues { + if paramValue.Name == "dockerfile" { + paramValue.Value = &dockerfile + } + } + return b +} + +func (b *buildPrototype) OutputImage(image string) *buildPrototype { + b.build.Spec.Output.Image = image + return b +} + +func (b *buildPrototype) determineParameterIndex(name string) int { + index := -1 + for i, paramValue := range b.build.Spec.ParamValues { + if paramValue.Name == name { + index = i + break + } + } + + if index == -1 { + index = len(b.build.Spec.ParamValues) + b.build.Spec.ParamValues = append(b.build.Spec.ParamValues, buildv1beta1.ParamValue{ + Name: name, + }) + } + + return index +} + +// ArrayParamValue adds an item to an array parameter, if the parameter is not yet present, it is being added +func (b *buildPrototype) ArrayParamValue(name string, value string) *buildPrototype { + index := b.determineParameterIndex(name) + b.build.Spec.ParamValues[index].Values = append(b.build.Spec.ParamValues[index].Values, buildv1beta1.SingleValue{ + Value: &value, + }) + + return b +} + +// ArrayParamValueFromConfigMap adds an item to an array parameter, if the parameter is not yet present, it is being added +func (b *buildPrototype) ArrayParamValueFromConfigMap(name string, configMapName string, configMapKey string, format *string) *buildPrototype { + index := b.determineParameterIndex(name) + b.build.Spec.ParamValues[index].Values = append(b.build.Spec.ParamValues[index].Values, buildv1beta1.SingleValue{ + ConfigMapValue: &buildv1beta1.ObjectKeyRef{ + Name: configMapName, + Key: configMapKey, + Format: format, + }, + }) + + return b +} + +// ArrayParamValueFromSecret adds an item to an array parameter, if the parameter is not yet present, it is being added +func (b *buildPrototype) ArrayParamValueFromSecret(name string, secretName string, secretKey string, format *string) *buildPrototype { + index := b.determineParameterIndex(name) + b.build.Spec.ParamValues[index].Values = append(b.build.Spec.ParamValues[index].Values, buildv1beta1.SingleValue{ + SecretValue: &buildv1beta1.ObjectKeyRef{ + Name: secretName, + Key: secretKey, + Format: format, + }, + }) + + return b +} + +func (b *buildPrototype) StringParamValue(name string, value string) *buildPrototype { + b.build.Spec.ParamValues = append(b.build.Spec.ParamValues, buildv1beta1.ParamValue{ + Name: name, + SingleValue: &buildv1beta1.SingleValue{ + Value: &value, + }, + }) + + return b +} + +func (b *buildPrototype) StringParamValueFromConfigMap(name string, configMapName string, configMapKey string, format *string) *buildPrototype { + b.build.Spec.ParamValues = append(b.build.Spec.ParamValues, buildv1beta1.ParamValue{ + Name: name, + SingleValue: &buildv1beta1.SingleValue{ + ConfigMapValue: &buildv1beta1.ObjectKeyRef{ + Name: configMapName, + Key: configMapKey, + Format: format, + }, + }, + }) + + return b +} + +func (b *buildPrototype) StringParamValueFromSecret(name string, secretName string, secretKey string, format *string) *buildPrototype { + b.build.Spec.ParamValues = append(b.build.Spec.ParamValues, buildv1beta1.ParamValue{ + Name: name, + SingleValue: &buildv1beta1.SingleValue{ + SecretValue: &buildv1beta1.ObjectKeyRef{ + Name: secretName, + Key: secretKey, + Format: format, + }, + }, + }) + + return b +} + +func (b *buildPrototype) OutputImageCredentials(name string) *buildPrototype { + if name != "" { + b.build.Spec.Output.PushSecret = &name + } + return b +} + +func (b *buildPrototype) OutputImageInsecure(insecure bool) *buildPrototype { + b.build.Spec.Output.Insecure = &insecure + + return b +} + +func (b buildPrototype) Create() (build *buildv1beta1.Build, err error) { + ctx := context.Background() + + _, err = testBuild. + BuildClientSet. + ShipwrightV1beta1(). + Builds(b.build.Namespace). + Create(ctx, &b.build, meta.CreateOptions{}) + + if err != nil { + return nil, err + } + + err = wait.PollImmediate(pollCreateInterval, pollCreateTimeout, func() (done bool, err error) { + build, err = testBuild.BuildClientSet.ShipwrightV1beta1().Builds(b.build.Namespace).Get(ctx, b.build.Name, meta.GetOptions{}) + if err != nil { + return false, err + } + + return build.Status.Registered != nil && *build.Status.Registered == core.ConditionTrue, nil + }) + + return +} + +// BuildSpec returns the BuildSpec of this Build (no cluster resource is created) +func (b buildPrototype) BuildSpec() (build *buildv1beta1.BuildSpec) { + return &b.build.Spec +} + +func NewBuildRunPrototype() *buildRunPrototype { + return &buildRunPrototype{buildRun: buildv1beta1.BuildRun{}} +} + +func (b *buildRunPrototype) Name(name string) *buildRunPrototype { + b.buildRun.ObjectMeta.Name = name + return b +} + +func (b *buildRunPrototype) Namespace(namespace string) *buildRunPrototype { + b.buildRun.Namespace = namespace + return b +} + +func (b *buildRunPrototype) ForBuild(build *buildv1beta1.Build) *buildRunPrototype { + b.buildRun.Spec.Build.Name = build.Name + b.buildRun.ObjectMeta.Namespace = build.Namespace + return b +} + +func (b *buildRunPrototype) WithBuildSpec(buildSpec *buildv1beta1.BuildSpec) *buildRunPrototype { + b.buildRun.Spec.Build.Build = buildSpec + return b +} + +func (b *buildRunPrototype) GenerateServiceAccount() *buildRunPrototype { + generate := ".generate" + if b.buildRun.Spec.ServiceAccount == nil { + b.buildRun.Spec.ServiceAccount = &generate + } + return b +} + +func (b *buildRunPrototype) determineParameterIndex(name string) int { + index := -1 + for i, paramValue := range b.buildRun.Spec.ParamValues { + if paramValue.Name == name { + index = i + break + } + } + + if index == -1 { + index = len(b.buildRun.Spec.ParamValues) + b.buildRun.Spec.ParamValues = append(b.buildRun.Spec.ParamValues, buildv1beta1.ParamValue{ + Name: name, + }) + } + + return index +} + +// ArrayParamValue adds an item to an array parameter, if the parameter is not yet present, it is being added +func (b *buildRunPrototype) ArrayParamValue(name string, value string) *buildRunPrototype { + index := b.determineParameterIndex(name) + b.buildRun.Spec.ParamValues[index].Values = append(b.buildRun.Spec.ParamValues[index].Values, buildv1beta1.SingleValue{ + Value: &value, + }) + + return b +} + +// ArrayParamValueFromConfigMap adds an item to an array parameter, if the parameter is not yet present, it is being added +func (b *buildRunPrototype) ArrayParamValueFromConfigMap(name string, configMapName string, configMapKey string, format *string) *buildRunPrototype { + index := b.determineParameterIndex(name) + b.buildRun.Spec.ParamValues[index].Values = append(b.buildRun.Spec.ParamValues[index].Values, buildv1beta1.SingleValue{ + ConfigMapValue: &buildv1beta1.ObjectKeyRef{ + Name: configMapName, + Key: configMapKey, + Format: format, + }, + }) + + return b +} + +// ArrayParamValueFromSecret adds an item to an array parameter, if the parameter is not yet present, it is being added +func (b *buildRunPrototype) ArrayParamValueFromSecret(name string, secretName string, secretKey string, format *string) *buildRunPrototype { + index := b.determineParameterIndex(name) + b.buildRun.Spec.ParamValues[index].Values = append(b.buildRun.Spec.ParamValues[index].Values, buildv1beta1.SingleValue{ + SecretValue: &buildv1beta1.ObjectKeyRef{ + Name: secretName, + Key: secretKey, + Format: format, + }, + }) + + return b +} + +func (b *buildRunPrototype) StringParamValue(name string, value string) *buildRunPrototype { + b.buildRun.Spec.ParamValues = append(b.buildRun.Spec.ParamValues, buildv1beta1.ParamValue{ + Name: name, + SingleValue: &buildv1beta1.SingleValue{ + Value: &value, + }, + }) + + return b +} + +func (b *buildRunPrototype) StringParamValueFromConfigMap(name string, configMapName string, configMapKey string, format *string) *buildRunPrototype { + b.buildRun.Spec.ParamValues = append(b.buildRun.Spec.ParamValues, buildv1beta1.ParamValue{ + Name: name, + SingleValue: &buildv1beta1.SingleValue{ + ConfigMapValue: &buildv1beta1.ObjectKeyRef{ + Name: configMapName, + Key: configMapKey, + Format: format, + }, + }, + }) + + return b +} + +func (b *buildRunPrototype) StringParamValueFromSecret(name string, secretName string, secretKey string, format *string) *buildRunPrototype { + b.buildRun.Spec.ParamValues = append(b.buildRun.Spec.ParamValues, buildv1beta1.ParamValue{ + Name: name, + SingleValue: &buildv1beta1.SingleValue{ + SecretValue: &buildv1beta1.ObjectKeyRef{ + Name: secretName, + Key: secretKey, + Format: format, + }, + }, + }) + + return b +} + +func (b *buildRunPrototype) Create() (*buildv1beta1.BuildRun, error) { + return testBuild. + BuildClientSet. + ShipwrightV1beta1(). + BuildRuns(b.buildRun.Namespace). + Create(context.Background(), &b.buildRun, meta.CreateOptions{}) +} + +// Logf logs data +func Logf(format string, args ...interface{}) { + currentTime := time.Now().UTC().Format(time.RFC3339) + + fmt.Fprintf( + GinkgoWriter, + fmt.Sprintf("%s %d %s\n", currentTime, getGinkgoNode(), format), + args..., + ) +} + +func getArg(argName string) (bool, string) { + for i, arg := range os.Args { + if arg == argName { + return true, os.Args[i+1] + } else if strings.HasPrefix(arg, argName+"=") { + argAndValue := strings.SplitN(arg, "=", 2) + return true, argAndValue[1] + } + } + return false, "" +} + +func getGinkgoNode() int { + defined, ginkgoNodeString := getArg("--ginkgo.parallel.node") + if !defined { + return 1 + } + ginkgoNode, err := strconv.Atoi(ginkgoNodeString) + if err != nil { + fmt.Printf("Error: %s", err.Error()) + return 0 + } + return ginkgoNode +} diff --git a/test/e2e/v1beta1/common_test.go b/test/e2e/v1beta1/common_test.go new file mode 100644 index 0000000000..4ba907bb44 --- /dev/null +++ b/test/e2e/v1beta1/common_test.go @@ -0,0 +1,269 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package e2e_test + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + "strconv" + "strings" + "time" + + . "github.com/onsi/gomega" + knativeapis "knative.dev/pkg/apis" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/rand" + + buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" + buildv1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1" + "github.com/shipwright-io/build/pkg/reconciler/buildrun/resources" + utils "github.com/shipwright-io/build/test/utils/v1beta1" +) + +func generateTestID(id string) string { + return id + "-" + rand.String(5) +} + +func removeTestIDSuffix(id string) string { + return id[:len(id)-6] +} + +func createBuild(testBuild *utils.TestBuild, identifier string, filePath string) *buildv1beta1.Build { + build, err := buildTestData(testBuild.Namespace, identifier, filePath) + Expect(err).ToNot(HaveOccurred(), "Error retrieving build test data") + + amendBuild(identifier, build) + + // For Builds that use a namespaces build strategy, there is a race condition: we just created the + // build strategy and it might be that the build controller does not yet have this in his cache + // and therefore marks the build as not registered with reason BuildStrategyNotFound + Eventually(func() buildv1beta1.BuildReason { + // cleanup the build of the previous try + if build.Status.Registered != nil && *build.Status.Registered != "" { + err = testBuild.DeleteBuild(build.Name) + Expect(err).ToNot(HaveOccurred()) + + // create a fresh object + build, err = buildTestData(testBuild.Namespace, identifier, filePath) + Expect(err).ToNot(HaveOccurred(), "Error retrieving build test data") + + amendBuild(identifier, build) + } + + err = testBuild.CreateBuild(build) + Expect(err).ToNot(HaveOccurred(), "Unable to create build %s", identifier) + Logf("Build %s created", identifier) + + build, err = testBuild.GetBuildTillValidation(build.Name) + Expect(err).ToNot(HaveOccurred()) + + return *build.Status.Reason + }, time.Duration(10*time.Second), time.Second).Should(Equal(buildv1beta1.SucceedStatus)) + + return build +} + +// amendOutputImage amend container image URL based on informed image repository. +func amendOutputImage(b *buildv1beta1.Build, imageRepo string, insecure bool) { + if imageRepo == "" { + return + } + + // image tag is the build name without the test id suffix as this would pollute the container registry + imageTag := removeTestIDSuffix(b.Name) + + imageURL := fmt.Sprintf("%s:%s", imageRepo, imageTag) + b.Spec.Output.Image = imageURL + b.Spec.Output.Insecure = &insecure + + Logf("Amended object: name='%s', image-url='%s'", b.Name, imageURL) +} + +// amendOutputCredentials amend secret-ref for output image. +func amendOutputCredentials(b *buildv1beta1.Build, secretName string) { + if secretName == "" { + return + } + b.Spec.Output.PushSecret = &secretName + Logf("Amended object: name='%s', secret-ref='%s'", b.Name, secretName) +} + +// amendSourceSecretName patch Build source.Credentials with secret name. +func amendSourceSecretName(b *buildv1beta1.Build, secretName string) { + if secretName == "" { + return + } + b.Spec.Source.GitSource.CloneSecret = &secretName +} + +// amendSourceURL patch Build source.URL with informed string. +func amendSourceURL(b *buildv1beta1.Build, sourceURL string) { + if sourceURL == "" { + return + } + b.Spec.Source.GitSource.URL = &sourceURL +} + +// amendBuild make changes on build object. +func amendBuild(identifier string, b *buildv1beta1.Build) { + if strings.Contains(identifier, "github") { + amendSourceSecretName(b, os.Getenv(EnvVarSourceURLSecret)) + amendSourceURL(b, os.Getenv(EnvVarSourceURLGithub)) + } else if strings.Contains(identifier, "gitlab") { + amendSourceSecretName(b, os.Getenv(EnvVarSourceURLSecret)) + amendSourceURL(b, os.Getenv(EnvVarSourceURLGitlab)) + } + + insecure := false + value, found := os.LookupEnv(EnvVarImageRepoInsecure) + if found { + var err error + insecure, err = strconv.ParseBool(value) + Expect(err).ToNot(HaveOccurred()) + } + + amendOutputImage(b, os.Getenv(EnvVarImageRepo), insecure) + amendOutputCredentials(b, os.Getenv(EnvVarImageRepoSecret)) +} + +// retrieveBuildAndBuildRun will retrieve the build and buildRun +func retrieveBuildAndBuildRun(testBuild *utils.TestBuild, namespace string, buildRunName string) (*buildv1beta1.Build, *buildv1beta1.BuildRun, error) { + buildRun, err := testBuild.LookupBuildRun(types.NamespacedName{Name: buildRunName, Namespace: namespace}) + if err != nil { + Logf("Failed to get BuildRun %q: %s", buildRunName, err) + return nil, nil, err + } + + var alphaBuild buildv1alpha1.Build + var obj unstructured.Unstructured + + buildRun.ConvertTo(testBuild.Context, &obj) + jsonData, err := json.Marshal(obj.Object) + if err != nil { + Logf("Failed to convert the buildRun to v1alpha1: %s", err) + } + + var alphaBuildRun buildv1alpha1.BuildRun + json.Unmarshal(jsonData, &alphaBuildRun) + + if err := resources.GetBuildObject(testBuild.Context, testBuild.ControllerRuntimeClient, &alphaBuildRun, &alphaBuild); err != nil { + Logf("Failed to get Build from BuildRun %s: %s", buildRunName, err) + return nil, buildRun, err + } + + alphaBuild.ConvertTo(testBuild.Context, &obj) + jsonData, err = json.Marshal(obj.Object) + if err != nil { + Logf("Failed to convert the build to v1beta1: %s", err) + } + var betaBuild buildv1beta1.Build + json.Unmarshal(jsonData, &betaBuild) + + return &betaBuild, buildRun, nil +} + +// printTestFailureDebugInfo will output the status of Build, BuildRun, TaskRun and Pod, also print logs of Pod +func printTestFailureDebugInfo(testBuild *utils.TestBuild, namespace string, buildRunName string) { + Logf("Print failed BuildRun's log") + + build, buildRun, err := retrieveBuildAndBuildRun(testBuild, namespace, buildRunName) + if err != nil { + Logf("Failed to retrieve build and buildrun logs: %v", err) + } + + if build != nil { + Logf("The status of Build %s: registered=%s, reason=%s", build.Name, *build.Status.Registered, *build.Status.Reason) + if buildJSON, err := json.Marshal(build); err == nil { + Logf("The full Build: %s", string(buildJSON)) + } + } + + if buildRun != nil { + brCondition := buildRun.Status.GetCondition(buildv1beta1.Succeeded) + if brCondition != nil { + Logf("The status of BuildRun %s: status=%s, reason=%s", buildRun.Name, brCondition.Status, brCondition.Reason) + } + if buildRunJSON, err := json.Marshal(buildRun); err == nil { + Logf("The full BuildRun: %s", string(buildRunJSON)) + } + + podName := "" + + // Only log details of TaskRun if Tekton objects can be accessed + if os.Getenv(EnvVarVerifyTektonObjects) == "true" { + if taskRun, _ := testBuild.LookupTaskRunUsingBuildRun(buildRun); taskRun != nil { + condition := taskRun.Status.GetCondition(knativeapis.ConditionSucceeded) + if condition != nil { + Logf("The status of TaskRun %s: reason=%s, message=%s", taskRun.Name, condition.Reason, condition.Message) + } + + if taskRunJSON, err := json.Marshal(taskRun); err == nil { + Logf("The full TaskRun: %s", string(taskRunJSON)) + } + + podName = taskRun.Status.PodName + } + } + + // retrieve or query pod depending on whether we have the pod name from the TaskRun + var pod *corev1.Pod + if podName != "" { + pod, err = testBuild.LookupPod(types.NamespacedName{Name: podName, Namespace: namespace}) + if err != nil { + Logf("Error retrieving pod %s: %v", podName, err) + pod = nil + } + } else { + podList, err := testBuild.Clientset.CoreV1().Pods(namespace).List(testBuild.Context, metav1.ListOptions{ + LabelSelector: labels.FormatLabels(map[string]string{ + buildv1beta1.LabelBuildRun: buildRunName, + }), + }) + + if err == nil && len(podList.Items) > 0 { + pod = &podList.Items[0] + } + } + + if pod != nil { + Logf("The status of Pod %s: phase=%s, reason=%s, message=%s", pod.Name, pod.Status.Phase, pod.Status.Reason, pod.Status.Message) + if podJSON, err := json.Marshal(pod); err == nil { + Logf("The full Pod: %s", string(podJSON)) + } + + // Loop through the containers to print their logs + for _, container := range pod.Spec.Containers { + req := testBuild.Clientset.CoreV1().Pods(namespace).GetLogs(pod.Name, &corev1.PodLogOptions{ + TypeMeta: metav1.TypeMeta{}, + Container: container.Name, + Follow: false, + }) + + podLogs, err := req.Stream(testBuild.Context) + if err != nil { + Logf("Failed to retrieve the logs of container %s: %v", container.Name, err) + continue + } + + buf := new(bytes.Buffer) + _, err = io.Copy(buf, podLogs) + if err != nil { + Logf("Failed to copy logs of container %s to buffer: %v", container.Name, err) + continue + } + + Logf("Logs of container %s: %s", container.Name, buf.String()) + } + } + } +} diff --git a/test/e2e/v1beta1/e2e_bundle_test.go b/test/e2e/v1beta1/e2e_bundle_test.go new file mode 100644 index 0000000000..c3dd381c96 --- /dev/null +++ b/test/e2e/v1beta1/e2e_bundle_test.go @@ -0,0 +1,276 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package e2e_test + +import ( + "bytes" + "fmt" + "os" + "strconv" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/docker/cli/cli/config" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + buildv1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1" +) + +var _ = Describe("Test local source code (bundle) functionality", func() { + + insecure := false + value, found := os.LookupEnv(EnvVarImageRepoInsecure) + if found { + var err error + insecure, err = strconv.ParseBool(value) + Expect(err).ToNot(HaveOccurred()) + } + + var ( + testID string + err error + + build *buildv1beta1.Build + buildRun *buildv1beta1.BuildRun + ) + + AfterEach(func() { + if CurrentSpecReport().Failed() { + printTestFailureDebugInfo(testBuild, testBuild.Namespace, testID) + } else if buildRun != nil { + validateServiceAccountDeletion(buildRun, testBuild.Namespace) + } + + if buildRun != nil { + testBuild.DeleteBR(buildRun.Name) + buildRun = nil + } + + if build != nil { + testBuild.DeleteBuild(build.Name) + build = nil + } + }) + + Context("when using local source code bundle images as input", func() { + var inputImage, outputImage string + + BeforeEach(func() { + testID = generateTestID("bundle") + + inputImage = "ghcr.io/shipwright-io/sample-go/source-bundle:latest" + outputImage = fmt.Sprintf("%s/%s:%s", + os.Getenv(EnvVarImageRepo), + testID, + "latest", + ) + }) + + It("should work with Kaniko build strategy", func() { + build, err = NewBuildPrototype(). + ClusterBuildStrategy("kaniko"). + Name(testID). + Namespace(testBuild.Namespace). + SourceBundle(inputImage). + SourceContextDir("docker-build"). + Dockerfile("Dockerfile"). + OutputImage(outputImage). + OutputImageCredentials(os.Getenv(EnvVarImageRepoSecret)). + OutputImageInsecure(insecure). + Create() + Expect(err).ToNot(HaveOccurred()) + + buildRun, err = NewBuildRunPrototype(). + Name(testID). + ForBuild(build). + GenerateServiceAccount(). + Create() + Expect(err).ToNot(HaveOccurred()) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + validateBuildRunResultsFromBundleSource(buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + + It("should work with Buildpacks build strategy", func() { + build, err = NewBuildPrototype(). + ClusterBuildStrategy("buildpacks-v3"). + Name(testID). + Namespace(testBuild.Namespace). + SourceBundle(inputImage). + SourceContextDir("source-build"). + OutputImage(outputImage). + OutputImageCredentials(os.Getenv(EnvVarImageRepoSecret)). + OutputImageInsecure(insecure). + Create() + Expect(err).ToNot(HaveOccurred()) + + buildRun, err = NewBuildRunPrototype(). + Name(testID). + ForBuild(build). + GenerateServiceAccount(). + Create() + Expect(err).ToNot(HaveOccurred()) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + validateBuildRunResultsFromBundleSource(buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + + It("should work with Buildah build strategy", func() { + buildPrototype := NewBuildPrototype(). + ClusterBuildStrategy("buildah-shipwright-managed-push"). + Name(testID). + Namespace(testBuild.Namespace). + SourceBundle(inputImage). + SourceContextDir("docker-build"). + Dockerfile("Dockerfile"). + OutputImage(outputImage). + OutputImageCredentials(os.Getenv(EnvVarImageRepoSecret)). + OutputImageInsecure(insecure) + + if strings.Contains(outputImage, "cluster.local") { + parts := strings.Split(outputImage, "/") + host := parts[0] + buildPrototype.ArrayParamValue("registries-insecure", host) + } + + build, err = buildPrototype.Create() + Expect(err).ToNot(HaveOccurred()) + + buildRun, err = NewBuildRunPrototype(). + Name(testID). + ForBuild(build). + GenerateServiceAccount(). + Create() + Expect(err).ToNot(HaveOccurred()) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + validateBuildRunResultsFromBundleSource(buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + + It("should prune the source image after pulling it", func() { + var secretName = os.Getenv(EnvVarImageRepoSecret) + var registryName string + var auth authn.Authenticator + var tmpImage = fmt.Sprintf("%s/source-%s:%s", + os.Getenv(EnvVarImageRepo), + testID, + "latest", + ) + + By("looking up the registry name", func() { + ref, err := name.ParseReference(outputImage) + Expect(err).ToNot(HaveOccurred()) + + registryName = ref.Context().RegistryStr() + }) + + By("setting up the respective authenticator", func() { + switch { + case secretName != "": + secret, err := testBuild.Clientset. + CoreV1(). + Secrets(testBuild.Namespace). + Get(testBuild.Context, secretName, v1.GetOptions{}) + Expect(err).ToNot(HaveOccurred()) + + dockerConfigJSON, ok := secret.Data[".dockerconfigjson"] + Expect(ok).To(BeTrue()) + + configFile, err := config.LoadFromReader(bytes.NewReader(dockerConfigJSON)) + Expect(err).ToNot(HaveOccurred()) + + authConfig, err := configFile.GetAuthConfig(registryName) + Expect(err).ToNot(HaveOccurred()) + + auth = authn.FromConfig(authn.AuthConfig{ + Username: authConfig.Username, + Password: authConfig.Password, + Auth: authConfig.Auth, + IdentityToken: authConfig.IdentityToken, + RegistryToken: authConfig.RegistryToken, + }) + + default: + auth = authn.Anonymous + } + }) + + By("creating a temporary new input image based on the default input image", func() { + src, err := name.ParseReference(inputImage) + Expect(err).ToNot(HaveOccurred()) + + // Special case for a local registry in the cluster: + // Since the test client is not running in the cluster, it relies on being able to + // reach the same registry via a local port. Therefore, the image name needs to be + // different for the image copy preparation step. + var dstImage = tmpImage + if strings.Contains(dstImage, "cluster.local") { + dstImage = strings.ReplaceAll( + dstImage, + "registry.registry.svc.cluster.local", + "localhost", + ) + } + + dst, err := name.ParseReference(dstImage) + Expect(err).ToNot(HaveOccurred()) + + srcDesc, err := remote.Get(src) + Expect(err).ToNot(HaveOccurred()) + + image, err := srcDesc.Image() + Expect(err).ToNot(HaveOccurred()) + + Expect(remote.Write( + dst, + image, + remote.WithContext(testBuild.Context), + remote.WithAuth(auth), + )).ToNot(HaveOccurred()) + }) + + By("eventually running the actual build with prune option", func() { + build, err = NewBuildPrototype(). + ClusterBuildStrategy("kaniko"). + Name(testID). + Namespace(testBuild.Namespace). + SourceBundle(tmpImage). + SourceBundlePrune(buildv1beta1.PruneAfterPull). + SourceCredentials(secretName). + SourceContextDir("docker-build"). + Dockerfile("Dockerfile"). + OutputImage(outputImage). + OutputImageCredentials(secretName). + OutputImageInsecure(insecure). + Create() + Expect(err).ToNot(HaveOccurred()) + + buildRun, err = NewBuildRunPrototype(). + Name(testID). + ForBuild(build). + GenerateServiceAccount(). + Create() + Expect(err).ToNot(HaveOccurred()) + validateBuildRunToSucceed(testBuild, buildRun) + }) + + By("checking the temporary input image was removed", func() { + tmp, err := name.ParseReference(tmpImage) + Expect(err).ToNot(HaveOccurred()) + + _, err = remote.Head(tmp, remote.WithContext(testBuild.Context), remote.WithAuth(auth)) + Expect(err).To(HaveOccurred()) + }) + }) + }) +}) diff --git a/test/e2e/v1beta1/e2e_image_mutate_test.go b/test/e2e/v1beta1/e2e_image_mutate_test.go new file mode 100644 index 0000000000..25d25d9041 --- /dev/null +++ b/test/e2e/v1beta1/e2e_image_mutate_test.go @@ -0,0 +1,89 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package e2e_test + +import ( + containerreg "github.com/google/go-containerregistry/pkg/v1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + buildv1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1" +) + +var _ = Describe("For a Kubernetes cluster with Tekton and build installed", func() { + var ( + err error + testID string + build *buildv1beta1.Build + buildRun *buildv1beta1.BuildRun + ) + + AfterEach(func() { + if CurrentSpecReport().Failed() { + printTestFailureDebugInfo(testBuild, testBuild.Namespace, testID) + } else if buildRun != nil { + validateServiceAccountDeletion(buildRun, testBuild.Namespace) + } + + if buildRun != nil { + testBuild.DeleteBR(buildRun.Name) + buildRun = nil + } + + if build != nil { + testBuild.DeleteBuild(build.Name) + build = nil + } + }) + + Context("when a Buildah build with label and annotation is defined", func() { + BeforeEach(func() { + testID = generateTestID("buildah-mutate") + + // create the build definition + build = createBuild( + testBuild, + testID, + "test/data/build_buildah_cr_mutate.yaml", + ) + }) + + It("should mutate an image with annotation and label", func() { + buildRun, err = buildRunTestData( + testBuild.Namespace, testID, + "test/data/buildrun_buildah_cr_mutate.yaml", + ) + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + appendRegistryInsecureParamValue(build, buildRun) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + + image := testBuild.GetImage(buildRun) + + Expect( + getImageAnnotation(image, "org.opencontainers.image.url"), + ).To(Equal("https://my-company.com/images")) + + Expect( + getImageLabel(image, "maintainer"), + ).To(Equal("team@my-company.com")) + }) + }) +}) + +func getImageAnnotation(img containerreg.Image, annotation string) string { + manifest, err := img.Manifest() + Expect(err).To(BeNil()) + + return manifest.Annotations[annotation] +} + +func getImageLabel(img containerreg.Image, label string) string { + config, err := img.ConfigFile() + Expect(err).To(BeNil()) + + return config.Config.Labels[label] +} diff --git a/test/e2e/v1beta1/e2e_local_source_upload_test.go b/test/e2e/v1beta1/e2e_local_source_upload_test.go new file mode 100644 index 0000000000..1429aefbc4 --- /dev/null +++ b/test/e2e/v1beta1/e2e_local_source_upload_test.go @@ -0,0 +1,104 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package e2e_test + +import ( + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/apimachinery/pkg/types" + + buildv1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1" + utils "github.com/shipwright-io/build/test/utils/v1beta1" +) + +var _ = Describe("For a Kubernetes cluster with Tekton and build installed", func() { + var ( + testID string + build *buildv1beta1.Build + buildRun *buildv1beta1.BuildRun + ) + + AfterEach(func() { + if buildRun != nil { + testBuild.DeleteBR(buildRun.Name) + buildRun = nil + } + + if build != nil { + testBuild.DeleteBuild(build.Name) + build = nil + } + }) + + Context("when LocalCopy BuildSource is defined", func() { + BeforeEach(func() { + testID = generateTestID("local-copy") + build = createBuild( + testBuild, + testID, + "test/data/build_buildah_cr_local_source_upload.yaml", + ) + }) + + It("should generate LocalCopy TaskRun, using the waiter", func() { + var err error + buildRun, err = buildRunTestData( + testBuild.Namespace, + testID, + "test/data/buildrun_buildah_cr_local_source_upload.yaml", + ) + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + + validateWaiterBuildRun(testBuild, buildRun) + }) + }) +}) + +func getBuildRunStatusCondition(name types.NamespacedName) *buildv1beta1.Condition { + testBuildRun, err := testBuild.LookupBuildRun(name) + Expect(err).ToNot(HaveOccurred(), "Error retrieving the BuildRun") + + if len(testBuildRun.Status.Conditions) == 0 { + return nil + } + return testBuildRun.Status.GetCondition(buildv1beta1.Succeeded) +} + +// validateWaiterBuildRun assert the BuildRun informed will fail, since Waiter's timeout is reached +// and it causes the actual build process to fail as well. +func validateWaiterBuildRun(testBuild *utils.TestBuild, testBuildRun *buildv1beta1.BuildRun) { + err := testBuild.CreateBR(testBuildRun) + Expect(err).ToNot(HaveOccurred(), "Failed to create BuildRun") + + buildRunName := types.NamespacedName{ + Namespace: testBuild.Namespace, + Name: testBuildRun.Name, + } + + // making sure the taskrun is schedule and becomes a pod, since the build controller will transit + // the object status from empty to unknown, when the actual build starts being executed + Eventually(func() bool { + condition := getBuildRunStatusCondition(buildRunName) + if condition == nil { + return false + } + Logf("BuildRun %q status %q...", buildRunName, condition.Status) + return condition.Reason == "Running" + }, time.Duration(1100*getTimeoutMultiplier())*time.Second, 5*time.Second). + Should(BeTrue(), "BuildRun should start running") + + // asserting the waiter step will end up in timeout, in other words, the build is terminated with + // the reason "failed" + Eventually(func() string { + condition := getBuildRunStatusCondition(buildRunName) + Expect(condition).ToNot(BeNil()) + Logf("BuildRun %q condition %v...", buildRunName, condition) + return condition.Reason + }, time.Duration(90*time.Second), 10*time.Second). + Should(Equal("Failed"), "BuildRun should end up in timeout") +} diff --git a/test/e2e/v1beta1/e2e_one_off_builds_test.go b/test/e2e/v1beta1/e2e_one_off_builds_test.go new file mode 100644 index 0000000000..cc807850dd --- /dev/null +++ b/test/e2e/v1beta1/e2e_one_off_builds_test.go @@ -0,0 +1,123 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package e2e_test + +import ( + "fmt" + "os" + "strconv" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/google/go-containerregistry/pkg/name" + buildv1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1" +) + +var _ = Describe("Using One-Off Builds", func() { + + insecure := false + value, found := os.LookupEnv(EnvVarImageRepoInsecure) + if found { + var err error + insecure, err = strconv.ParseBool(value) + Expect(err).ToNot(HaveOccurred()) + } + + var ( + testID string + err error + + buildRun *buildv1beta1.BuildRun + ) + + AfterEach(func() { + if CurrentSpecReport().Failed() { + printTestFailureDebugInfo(testBuild, testBuild.Namespace, testID) + + } else if buildRun != nil { + validateServiceAccountDeletion(buildRun, testBuild.Namespace) + } + + if buildRun != nil { + testBuild.DeleteBR(buildRun.Name) + buildRun = nil + } + }) + + Context("Embed BuildSpec in BuildRun", func() { + var outputImage name.Reference + + BeforeEach(func() { + testID = generateTestID("onoff") + + outputImage, err = name.ParseReference(fmt.Sprintf("%s/%s:%s", + os.Getenv(EnvVarImageRepo), + testID, + "latest", + )) + Expect(err).ToNot(HaveOccurred()) + }) + + It("should build an image using Buildpacks and a Git source", func() { + buildRun, err = NewBuildRunPrototype(). + Namespace(testBuild.Namespace). + Name(testID). + WithBuildSpec(NewBuildPrototype(). + ClusterBuildStrategy("buildpacks-v3"). + Namespace(testBuild.Namespace). + Name(testID). + SourceGit("https://github.com/shipwright-io/sample-go.git"). + SourceContextDir("source-build"). + OutputImage(outputImage.String()). + OutputImageCredentials(os.Getenv(EnvVarImageRepoSecret)). + OutputImageInsecure(insecure). + BuildSpec()). + Create() + Expect(err).ToNot(HaveOccurred()) + validateBuildRunToSucceed(testBuild, buildRun) + }) + + It("should build an image using Buildah and a Git source", func() { + buildRun, err = NewBuildRunPrototype(). + Namespace(testBuild.Namespace). + Name(testID). + WithBuildSpec(NewBuildPrototype(). + ClusterBuildStrategy("buildah-shipwright-managed-push"). + Namespace(testBuild.Namespace). + Name(testID). + SourceGit("https://github.com/shipwright-io/sample-go.git"). + SourceContextDir("docker-build"). + Dockerfile("Dockerfile"). + ArrayParamValue("registries-insecure", outputImage.Context().RegistryStr()). + OutputImage(outputImage.String()). + OutputImageCredentials(os.Getenv(EnvVarImageRepoSecret)). + OutputImageInsecure(insecure). + BuildSpec()). + Create() + Expect(err).ToNot(HaveOccurred()) + validateBuildRunToSucceed(testBuild, buildRun) + }) + + It("should build an image using Buildpacks and a bundle source", func() { + buildRun, err = NewBuildRunPrototype(). + Namespace(testBuild.Namespace). + Name(testID). + WithBuildSpec(NewBuildPrototype(). + ClusterBuildStrategy("buildpacks-v3"). + Namespace(testBuild.Namespace). + Name(testID). + SourceBundle("ghcr.io/shipwright-io/sample-go/source-bundle:latest"). + SourceContextDir("source-build"). + OutputImage(outputImage.String()). + OutputImageCredentials(os.Getenv(EnvVarImageRepoSecret)). + OutputImageInsecure(insecure). + BuildSpec()). + Create() + Expect(err).ToNot(HaveOccurred()) + validateBuildRunToSucceed(testBuild, buildRun) + }) + }) +}) diff --git a/test/e2e/v1beta1/e2e_params_test.go b/test/e2e/v1beta1/e2e_params_test.go new file mode 100644 index 0000000000..6aa2a83410 --- /dev/null +++ b/test/e2e/v1beta1/e2e_params_test.go @@ -0,0 +1,149 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package e2e_test + +import ( + "encoding/json" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" + buildv1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1" + test "github.com/shipwright-io/build/test/v1beta1_samples" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" +) + +var _ = Describe("For a Kubernetes cluster with Tekton and build installed", func() { + var ( + testID string + err error + + build *buildv1beta1.Build + buildRun *buildv1beta1.BuildRun + buildStrategy *buildv1beta1.BuildStrategy + configMap *corev1.ConfigMap + secret *corev1.Secret + ) + + AfterEach(func() { + if CurrentSpecReport().Failed() { + printTestFailureDebugInfo(testBuild, testBuild.Namespace, testID) + } else if buildRun != nil { + validateServiceAccountDeletion(buildRun, testBuild.Namespace) + } + + if buildRun != nil { + testBuild.DeleteBR(buildRun.Name) + buildRun = nil + } + + if build != nil { + testBuild.DeleteBuild(build.Name) + build = nil + } + + if buildStrategy != nil { + testBuild.DeleteBuildStrategy(buildStrategy.Name) + buildStrategy = nil + } + + if configMap != nil { + testBuild.DeleteConfigMap(configMap.Name) + configMap = nil + } + + if secret != nil { + testBuild.DeleteSecret(secret.Name) + secret = nil + } + }) + + Context("when using a cluster build strategy is used that uses a lot parameters", func() { + BeforeEach(func() { + var obj unstructured.Unstructured + buildStrategy.ConvertTo(testBuild.Context, &obj) + jsonData, _ := json.Marshal(obj.Object) + + var alphaBuildStrategy buildv1alpha1.BuildStrategy + json.Unmarshal(jsonData, &alphaBuildStrategy) + + buildStrategy, err = testBuild.Catalog.LoadBuildStrategyFromBytes([]byte(test.BuildStrategyWithParameterVerification)) + Expect(err).ToNot(HaveOccurred()) + err = testBuild.CreateBuildStrategy(buildStrategy) + Expect(err).ToNot(HaveOccurred()) + }) + + Context("when a secret and a configmap are in place with suitable values", func() { + BeforeEach(func() { + // prepare a ConfigMap + configMap = testBuild.Catalog.ConfigMapWithData("a-configmap", testBuild.Namespace, map[string]string{ + "number1": "1", + "shell": "/bin/bash", + }) + err = testBuild.CreateConfigMap(configMap) + Expect(err).ToNot(HaveOccurred()) + + // prepare a secret + secret = testBuild.Catalog.SecretWithStringData("a-secret", testBuild.Namespace, map[string]string{ + "number2": "2", + "number3": "3", + }) + err = testBuild.CreateSecret(secret) + Expect(err).ToNot(HaveOccurred()) + }) + + Context("when a Build is in place that sets some of the parameters", func() { + BeforeEach(func() { + testID = generateTestID("params") + + build, err = NewBuildPrototype(). + BuildStrategy(buildStrategy.Name). + Name(testID). + Namespace(testBuild.Namespace). + // The source is not actually used by the build, so just take a small one + SourceGit("https://github.com/shipwright-io/sample-go.git"). + // There is not actually an image pushed + OutputImage("dummy"). + // The parameters + StringParamValue("env1", "13"). + StringParamValueFromConfigMap("env2", "a-configmap", "number1", pointer.String("2${CONFIGMAP_VALUE}")). + ArrayParamValueFromConfigMap("commands", "a-configmap", "shell", nil). + ArrayParamValue("commands", "-c"). + Create() + Expect(err).ToNot(HaveOccurred()) + }) + + It("correctly runs a BuildRun that passes the remaining parameters", func() { + buildRun, err = NewBuildRunPrototype(). + ForBuild(build). + Name(testID). + GenerateServiceAccount(). + StringParamValue("image", "registry.access.redhat.com/ubi9/ubi-minimal"). + StringParamValueFromSecret("env3", "a-secret", "number2", nil). + ArrayParamValueFromSecret("args", "a-secret", "number3", pointer.String("${SECRET_VALUE}9")). + ArrayParamValue("args", "47"). + Create() + Expect(err).ToNot(HaveOccurred()) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + + // we verify the image digest here which is mis-used by the strategy to store a calculated sum + // 13 (env1) + 21 (env2 = 2${a-configmap:number1}) + 2 (env3 = ${a-secret:number2}) + 39 (args[0] = ${a-secret:number3}9) + 47 (args[1]) = 122 + buildRun, err = testBuild.LookupBuildRun(types.NamespacedName{ + Namespace: buildRun.Namespace, + Name: buildRun.Name, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(buildRun.Status.Output).NotTo(BeNil()) + Expect(buildRun.Status.Output.Size).To(BeEquivalentTo(122)) + }) + }) + }) + }) +}) diff --git a/test/e2e/v1beta1/e2e_rbac_test.go b/test/e2e/v1beta1/e2e_rbac_test.go new file mode 100644 index 0000000000..6fbd6a89db --- /dev/null +++ b/test/e2e/v1beta1/e2e_rbac_test.go @@ -0,0 +1,75 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package e2e_test + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var _ = Describe("User RBAC for Shipwright", func() { + + var ctx context.Context + + BeforeEach(func() { + ctx = context.Background() + }) + + It("should install an aggregated edit role for developers", func() { + editRole, err := testBuild.Clientset.RbacV1().ClusterRoles().Get(ctx, "shipwright-build-aggregate-edit", metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + expectedAggregates := []string{ + "rbac.authorization.k8s.io/aggregate-to-edit", + "rbac.authorization.k8s.io/aggregate-to-admin", + } + for _, aggregate := range expectedAggregates { + aggregateValue, exists := editRole.Labels[aggregate] + Expect(exists).To(BeTrue()) + Expect(aggregateValue).To(Equal("true")) + } + // We should have at least two rules - one for ClusterBuildStrategy, another for all else + // More than two rules is acceptable. + Expect(len(editRole.Rules)).To(BeNumerically(">=", 2)) + for _, rule := range editRole.Rules { + Expect(rule.APIGroups).To(ContainElement("shipwright.io")) + for _, resource := range rule.Resources { + if resource == "clusterbuildstrategies" { + Expect(rule.Verbs).To(ContainElements("get", "list", "watch")) + Expect(rule.Verbs).NotTo(ContainElement("create")) + Expect(rule.Verbs).NotTo(ContainElement("update")) + Expect(rule.Verbs).NotTo(ContainElement("patch")) + Expect(rule.Verbs).NotTo(ContainElement("delete")) + } else { + Expect(rule.Verbs).To(ContainElements("get", "list", "watch", "create", "update", "patch", "delete")) + } + } + } + }) + + It("should install an aggregated view role for all users", func() { + viewRole, err := testBuild.Clientset.RbacV1().ClusterRoles().Get(ctx, "shipwright-build-aggregate-view", metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + + aggregateValue, exists := viewRole.Labels["rbac.authorization.k8s.io/aggregate-to-view"] + Expect(exists).To(BeTrue()) + Expect(aggregateValue).To(Equal("true")) + // We should have at least one rule, as this applies "view" permissions to all Shipwright Build objects + // More rules are acceptable for future fine-grained controls. + Expect(len(viewRole.Rules)).To(BeNumerically(">=", 1)) + for _, rule := range viewRole.Rules { + Expect(rule.APIGroups).To(ContainElement("shipwright.io")) + Expect(rule.Verbs).To(ContainElements("get", "list", "watch")) + Expect(rule.Verbs).NotTo(ContainElement("create")) + Expect(rule.Verbs).NotTo(ContainElement("update")) + Expect(rule.Verbs).NotTo(ContainElement("patch")) + Expect(rule.Verbs).NotTo(ContainElement("delete")) + } + }) + +}) diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/v1beta1/e2e_suite_test.go similarity index 94% rename from test/e2e/e2e_suite_test.go rename to test/e2e/v1beta1/e2e_suite_test.go index ff8448cf6a..4f0db7668d 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/v1beta1/e2e_suite_test.go @@ -11,7 +11,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/shipwright-io/build/test/utils" + utils "github.com/shipwright-io/build/test/utils/v1beta1" ) var ( diff --git a/test/e2e/v1beta1/e2e_test.go b/test/e2e/v1beta1/e2e_test.go new file mode 100644 index 0000000000..375df7267c --- /dev/null +++ b/test/e2e/v1beta1/e2e_test.go @@ -0,0 +1,685 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package e2e_test + +import ( + "os" + + v1 "github.com/google/go-containerregistry/pkg/v1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + + buildv1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1" + shpgit "github.com/shipwright-io/build/pkg/git" +) + +var _ = Describe("For a Kubernetes cluster with Tekton and build installed", func() { + var ( + testID string + err error + + build *buildv1beta1.Build + buildRun *buildv1beta1.BuildRun + ) + + AfterEach(func() { + if CurrentSpecReport().Failed() { + printTestFailureDebugInfo(testBuild, testBuild.Namespace, testID) + + } else if buildRun != nil { + validateServiceAccountDeletion(buildRun, testBuild.Namespace) + } + + if buildRun != nil { + testBuild.DeleteBR(buildRun.Name) + buildRun = nil + } + + if build != nil { + testBuild.DeleteBuild(build.Name) + build = nil + } + }) + + Context("when a Buildah build is defined that is using shipwright-managed push", func() { + + BeforeEach(func() { + testID = generateTestID("buildah") + + // create the build definition + build = createBuild( + testBuild, + testID, + "samples/build/build_buildah_shipwright_managed_push_cr.yaml", + ) + }) + + It("successfully runs a build and surface results to BuildRun", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildah_cr.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + appendRegistryInsecureParamValue(build, buildRun) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + validateBuildRunResultsFromGitSource(buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a Buildah build is defined that is using strategy-managed push", func() { + + BeforeEach(func() { + testID = generateTestID("buildah") + + // create the build definition + build = createBuild( + testBuild, + testID, + "samples/build/build_buildah_strategy_managed_push_cr.yaml", + ) + }) + + It("successfully runs a build and surface results to BuildRun", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildah_cr.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + appendRegistryInsecureParamValue(build, buildRun) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + validateBuildRunResultsFromGitSource(buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a Buildah build with a contextDir and a custom Dockerfile name is defined", func() { + + BeforeEach(func() { + testID = generateTestID("buildah-custom-context-dockerfile") + + // create the build definition + build = createBuild( + testBuild, + testID, + "test/data/build_buildah_cr_custom_context+dockerfile.yaml", + ) + }) + + It("successfully runs a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_buildah_cr_custom_context+dockerfile.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + appendRegistryInsecureParamValue(build, buildRun) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a heroku Buildpacks build is defined using a cluster strategy", func() { + + BeforeEach(func() { + testID = generateTestID("buildpacks-v3-heroku") + + // create the build definition + build = createBuild( + testBuild, + testID, + "samples/build/build_buildpacks-v3-heroku_cr.yaml", + ) + }) + + It("successfully runs a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildpacks-v3-heroku_cr.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a heroku Buildpacks build is defined using a namespaced strategy", func() { + var buildStrategy *buildv1beta1.BuildStrategy + + BeforeEach(func() { + testID = generateTestID("buildpacks-v3-heroku-namespaced") + + buildStrategy, err = buildStrategyTestData(testBuild.Namespace, "samples/buildstrategy/buildpacks-v3/buildstrategy_buildpacks-v3-heroku_namespaced_cr.yaml") + Expect(err).ToNot(HaveOccurred()) + + err = testBuild.CreateBuildStrategy(buildStrategy) + Expect(err).ToNot(HaveOccurred()) + + // create the build definition + build = createBuild( + testBuild, + testID, + "samples/build/build_buildpacks-v3-heroku_namespaced_cr.yaml", + ) + }) + + It("successfully runs a build and surface results to BuildRun", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildpacks-v3-heroku_namespaced_cr.yaml") + Expect(err).ToNot(HaveOccurred()) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + validateBuildRunResultsFromGitSource(buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + + AfterEach(func() { + err = testBuild.DeleteBuildStrategy(buildStrategy.Name) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("when a Buildpacks v3 build is defined using a cluster strategy", func() { + + BeforeEach(func() { + testID = generateTestID("buildpacks-v3") + + // create the build definition + build = createBuild( + testBuild, + testID, + "samples/build/build_buildpacks-v3_cr.yaml", + ) + }) + + It("successfully runs a build and surface results to BuildRun", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildpacks-v3_cr.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + validateBuildRunResultsFromGitSource(buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a Buildpacks v3 build is defined using a namespaced strategy", func() { + var buildStrategy *buildv1beta1.BuildStrategy + + BeforeEach(func() { + testID = generateTestID("buildpacks-v3-namespaced") + + buildStrategy, err = buildStrategyTestData(testBuild.Namespace, "samples/buildstrategy/buildpacks-v3/buildstrategy_buildpacks-v3_namespaced_cr.yaml") + Expect(err).ToNot(HaveOccurred()) + + err = testBuild.CreateBuildStrategy(buildStrategy) + Expect(err).ToNot(HaveOccurred()) + + // create the build definition + build = createBuild( + testBuild, + testID, + "samples/build/build_buildpacks-v3_namespaced_cr.yaml", + ) + }) + + It("successfully runs a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildpacks-v3_namespaced_cr.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + + AfterEach(func() { + err = testBuild.DeleteBuildStrategy(buildStrategy.Name) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("when a Buildpacks v3 build is defined for a php runtime", func() { + + BeforeEach(func() { + testID = generateTestID("buildpacks-v3-php") + + // create the build definition + build = createBuild( + testBuild, + testID, + "test/data/build_buildpacks-v3_php_cr.yaml", + ) + }) + + It("successfully runs a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_buildpacks-v3_php_cr.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a Buildpacks v3 build is defined for a ruby runtime", func() { + + BeforeEach(func() { + testID = generateTestID("buildpacks-v3-ruby") + + // create the build definition + build = createBuild( + testBuild, + testID, + "test/data/build_buildpacks-v3_ruby_cr.yaml", + ) + }) + + It("successfully runs a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_buildpacks-v3_ruby_cr.yaml") + Expect(err).ToNot(HaveOccurred()) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a Buildpacks v3 build is defined for a golang runtime", func() { + + BeforeEach(func() { + testID = generateTestID("buildpacks-v3-golang") + + // create the build definition + build = createBuild( + testBuild, + testID, + "test/data/build_buildpacks-v3_golang_cr.yaml", + ) + }) + + It("successfully runs a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_buildpacks-v3_golang_cr.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a Buildpacks v3 build is defined for a golang runtime with `BP_GO_TARGETS` env", func() { + BeforeEach(func() { + testID = generateTestID("buildpacks-v3-golang") + + // create the build definition + build = createBuild( + testBuild, + testID, + "test/data/build_buildpacks-v3_golang_cr_env.yaml", + ) + }) + + It("successfully runs a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_buildpacks-v3_golang_cr.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a build uses the build-run-deletion annotation", func() { + + BeforeEach(func() { + testID = generateTestID("buildpacks-v3-golang") + + // create the build definition + build = createBuild( + testBuild, + testID, + "test/data/build_buildpacks-v3_golang_delete_cr.yaml", + ) + }) + + It("successfully deletes the BuildRun after the Build is deleted", func() { + By("running a build and expecting it to succeed") + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_buildpacks-v3_golang_cr.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + + By("deleting the parent Build object") + err = testBuild.DeleteBuild(build.Name) + Expect(err).NotTo(HaveOccurred(), "error deleting the parent Build") + Eventually(func() bool { + _, err = testBuild.GetBR(buildRun.Name) + if err == nil { + return false + } + if !errors.IsNotFound(err) { + return false + } + return true + }).Should(BeTrue()) + }) + }) + + Context("when a Buildpacks v3 build is defined for a java runtime", func() { + + BeforeEach(func() { + testID = generateTestID("buildpacks-v3-java") + + // create the build definition + build = createBuild( + testBuild, + testID, + "test/data/build_buildpacks-v3_java_cr.yaml", + ) + }) + + It("successfully runs a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_buildpacks-v3_java_cr.yaml") + Expect(err).ToNot(HaveOccurred()) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a Kaniko build is defined to use public GitHub", func() { + + BeforeEach(func() { + testID = generateTestID("kaniko") + + // create the build definition + build = createBuild( + testBuild, + testID, + "samples/build/build_kaniko_cr.yaml", + ) + }) + + It("successfully runs a build and surface results to BuildRun", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_kaniko_cr.yaml") + Expect(err).ToNot(HaveOccurred()) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + validateBuildRunResultsFromGitSource(buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a Kaniko build with a Dockerfile that requires advanced permissions is defined", func() { + + BeforeEach(func() { + testID = generateTestID("kaniko-advanced-dockerfile") + + // create the build definition + build = createBuild( + testBuild, + testID, + "test/data/build_kaniko_cr_advanced_dockerfile.yaml", + ) + }) + + It("successfully runs a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_kaniko_cr_advanced_dockerfile.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a Kaniko build with a contextDir and a custom Dockerfile name is defined", func() { + + BeforeEach(func() { + testID = generateTestID("kaniko-custom-context-dockerfile") + + // create the build definition + build = createBuild( + testBuild, + testID, + "test/data/build_kaniko_cr_custom_context+dockerfile.yaml", + ) + }) + + It("successfully runs a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "test/data/buildrun_kaniko_cr_custom_context+dockerfile.yaml") + Expect(err).ToNot(HaveOccurred()) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a Kaniko+Trivy build is defined to use an image with no critical CVEs", func() { + + BeforeEach(func() { + testID = generateTestID("kaniko-trivy-good") + + // create the build definition + build = createBuild( + testBuild, + testID, + "samples/build/build_kaniko-trivy-good_cr.yaml", + ) + }) + + It("successfully runs a build and surface results to BuildRun", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_kaniko-trivy-good_cr.yaml") + Expect(err).ToNot(HaveOccurred()) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + validateBuildRunResultsFromGitSource(buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a Kaniko+Trivy build is defined to use an image with a critical CVE", func() { + + BeforeEach(func() { + testID = generateTestID("kaniko-trivy-bad") + + // create the build definition + build = createBuild( + testBuild, + testID, + "samples/build/build_kaniko-trivy-bad_cr.yaml", + ) + }) + + It("fails to run a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_kaniko-trivy-bad_cr.yaml") + Expect(err).ToNot(HaveOccurred()) + + validateBuildRunToFail(testBuild, buildRun) + }) + }) + + Context("when a Buildkit build with a contextDir and a path to a Dockerfile is defined", func() { + + BeforeEach(func() { + testID = generateTestID("buildkit-custom-context") + + // create the build definition + build = createBuild( + testBuild, + testID, + "samples/build/build_buildkit_cr.yaml", + ) + }) + + It("successfully runs a build and surface results to BuildRun", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildkit_cr.yaml") + Expect(err).ToNot(HaveOccurred()) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + validateBuildRunResultsFromGitSource(buildRun) + testBuild.ValidateImagePlatformsExist(buildRun, []v1.Platform{ + { + Architecture: "amd64", + OS: "linux", + }, + { + Architecture: "arm64", + OS: "linux", + }, + }) + }) + }) + + Context("when a s2i build is defined", func() { + + BeforeEach(func() { + testID = generateTestID("s2i") + + // create the build definition + build = createBuild( + testBuild, + testID, + "samples/build/build_source-to-image_cr.yaml", + ) + }) + + It("successfully runs a build and surface results to BuildRun", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_source-to-image_cr.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + validateBuildRunResultsFromGitSource(buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a private source repository is used", func() { + + BeforeEach(func() { + if os.Getenv(EnvVarEnablePrivateRepos) != "true" { + Skip("Skipping test cases that use a private source repository") + } + }) + + Context("when a Buildah build is defined to use a private GitHub repository", func() { + + BeforeEach(func() { + testID = generateTestID("private-github-buildah") + + // create the build definition + build = createBuild( + testBuild, + testID, + "test/data/build_buildah_cr_private_github.yaml", + ) + }) + + It("successfully runs a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildah_cr.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a Buildah build is defined to use a private GitLab repository", func() { + + BeforeEach(func() { + testID = generateTestID("private-gitlab-buildah") + + // create the build definition + build = createBuild( + testBuild, + testID, + "test/data/build_buildah_cr_private_gitlab.yaml", + ) + }) + + It("successfully runs a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_buildah_cr.yaml") + Expect(err).ToNot(HaveOccurred()) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a Kaniko build is defined to use a private GitHub repository", func() { + + BeforeEach(func() { + testID = generateTestID("private-github-kaniko") + + // create the build definition + build = createBuild( + testBuild, + testID, + "test/data/build_kaniko_cr_private_github.yaml", + ) + }) + + It("successfully runs a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_kaniko_cr.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a Kaniko build is defined to use a private GitLab repository", func() { + + BeforeEach(func() { + testID = generateTestID("private-gitlab-kaniko") + + // create the build definition + build = createBuild( + testBuild, + testID, + "test/data/build_kaniko_cr_private_gitlab.yaml", + ) + }) + + It("successfully runs a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_kaniko_cr.yaml") + Expect(err).ToNot(HaveOccurred()) + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + + Context("when a s2i build is defined to use a private GitHub repository", func() { + + BeforeEach(func() { + testID = generateTestID("private-github-s2i") + + // create the build definition + build = createBuild( + testBuild, + testID, + "test/data/build_source-to-image_cr_private_github.yaml", + ) + }) + + It("successfully runs a build", func() { + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_source-to-image_cr.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + + buildRun = validateBuildRunToSucceed(testBuild, buildRun) + testBuild.ValidateImageDigest(buildRun) + }) + }) + }) + + Context("when a s2i build uses a non-existent git repository as source", func() { + It("fails because of prompted authentication which surfaces the to the BuildRun", func() { + testID = generateTestID("s2i-failing") + + build = createBuild( + testBuild, + testID, + "test/data/build_non_existing_repo.yaml", + ) + + buildRun, err = buildRunTestData(build.Namespace, testID, "test/data/buildrun_non_existing_repo.yaml") + Expect(err).ToNot(HaveOccurred()) + + validateBuildRunToFail(testBuild, buildRun) + buildRun, err = testBuild.LookupBuildRun(types.NamespacedName{Name: buildRun.Name, Namespace: testBuild.Namespace}) + + Expect(buildRun.Status.FailureDetails.Message).To(Equal(shpgit.AuthPrompted.ToMessage())) + Expect(buildRun.Status.FailureDetails.Reason).To(Equal(shpgit.AuthPrompted.String())) + }) + }) + +}) diff --git a/test/e2e/v1beta1/validators_test.go b/test/e2e/v1beta1/validators_test.go new file mode 100644 index 0000000000..d5931f37e0 --- /dev/null +++ b/test/e2e/v1beta1/validators_test.go @@ -0,0 +1,371 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package e2e_test + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubectl/pkg/scheme" + + "github.com/shipwright-io/build/pkg/apis" + buildv1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1" + utils "github.com/shipwright-io/build/test/utils/v1beta1" +) + +const ( + EnvVarServiceAccountName = "TEST_E2E_SERVICEACCOUNT_NAME" + EnvVarVerifyTektonObjects = "TEST_E2E_VERIFY_TEKTONOBJECTS" + EnvVarTimeoutMultiplier = "TEST_E2E_TIMEOUT_MULTIPLIER" + EnvVarImageRepo = "TEST_IMAGE_REPO" + EnvVarImageRepoInsecure = "TEST_IMAGE_REPO_INSECURE" + EnvVarEnablePrivateRepos = "TEST_PRIVATE_REPO" + EnvVarImageRepoSecret = "TEST_IMAGE_REPO_SECRET" + EnvVarSourceRepoSecretJSON = "TEST_IMAGE_REPO_DOCKERCONFIGJSON" + EnvVarSourceURLGithub = "TEST_PRIVATE_GITHUB" + EnvVarSourceURLGitlab = "TEST_PRIVATE_GITLAB" + EnvVarSourceURLSecret = "TEST_SOURCE_SECRET" +) + +// createPipelineServiceAccount reads the TEST_E2E_SERVICEACCOUNT_NAME environment variable. If the value is "generated", then nothing is done. +// Otherwise it will create the service account. No error occurs if the service account already exists. +func createPipelineServiceAccount(testBuild *utils.TestBuild) { + serviceAccountName, ok := os.LookupEnv(EnvVarServiceAccountName) + Expect(ok).To(BeTrue(), "environment variable "+EnvVarServiceAccountName+" is not set") + Expect(serviceAccountName).ToNot(BeEmpty()) + + if serviceAccountName == "generated" { + Logf("Skipping creation of service account, generated one will be used per build run.") + return + } + + if _, err := testBuild.LookupServiceAccount(types.NamespacedName{Namespace: testBuild.Namespace, Name: serviceAccountName}); err == nil { + Logf("Skipping creation of service account, reusing existing one.") + return + } + + Logf("Creating '%s' service-account", serviceAccountName) + _, err := testBuild.Clientset.CoreV1(). + ServiceAccounts(testBuild.Namespace). + Create(testBuild.Context, + &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testBuild.Namespace, + Name: serviceAccountName, + }}, + metav1.CreateOptions{}) + + // Due to concurrency, it could be that some other routine already finished creating the service-account + if err != nil && apierrors.IsAlreadyExists(err) { + Logf("Creation failed, because service-account %q is already in the system.", serviceAccountName) + return + } + + Expect(err).ToNot(HaveOccurred(), "Error creating service account") +} + +// createContainerRegistrySecret use environment variables to check for container registry +// credentials secret, when not found a new secret is created. +func createContainerRegistrySecret(testBuild *utils.TestBuild) { + secretName := os.Getenv(EnvVarImageRepoSecret) + secretPayload := os.Getenv(EnvVarSourceRepoSecretJSON) + if secretName == "" || secretPayload == "" { + Logf("Container registry secret won't be created.") + return + } + + _, err := testBuild.LookupSecret(types.NamespacedName{Namespace: testBuild.Namespace, Name: secretName}) + if err == nil { + Logf("Container registry secret is found at '%s/%s'", testBuild.Namespace, secretName) + return + } + + payload := []byte(secretPayload) + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testBuild.Namespace, + Name: secretName, + }, + Type: corev1.SecretTypeDockerConfigJson, + Data: map[string][]byte{ + ".dockerconfigjson": payload, + }, + } + + Logf("Creating container-registry secret '%s/%s' (%d bytes)", testBuild.Namespace, secretName, len(payload)) + err = testBuild.CreateSecret(secret) + Expect(err).ToNot(HaveOccurred(), "on creating container registry secret") +} + +// validateBuildRunToSucceed creates the build run and watches its flow until it succeeds. +func validateBuildRunToSucceed(testBuild *utils.TestBuild, testBuildRun *buildv1beta1.BuildRun) *buildv1beta1.BuildRun { + trueCondition := corev1.ConditionTrue + falseCondition := corev1.ConditionFalse + + // Ensure the BuildRun has been created + if _, err := testBuild.GetBR(testBuildRun.Name); err != nil { + Expect(testBuild.CreateBR(testBuildRun)). + ToNot(HaveOccurred(), "Failed to create BuildRun") + } + + // Ensure a BuildRun eventually moves to a succeeded TRUE status + nextStatusLog := time.Now().Add(60 * time.Second) + Eventually(func() corev1.ConditionStatus { + testBuildRun, err := testBuild.LookupBuildRun(types.NamespacedName{Name: testBuildRun.Name, Namespace: testBuild.Namespace}) + Expect(err).ToNot(HaveOccurred(), "Error retrieving a buildRun") + + if testBuildRun.Status.GetCondition(buildv1beta1.Succeeded) == nil { + return corev1.ConditionUnknown + } + + Expect(testBuildRun.Status.GetCondition(buildv1beta1.Succeeded).Status).ToNot(Equal(falseCondition), "BuildRun status doesn't move to Succeeded") + + now := time.Now() + if now.After(nextStatusLog) { + Logf("Still waiting for build run '%s' to succeed.", testBuildRun.Name) + nextStatusLog = time.Now().Add(60 * time.Second) + } + + return testBuildRun.Status.GetCondition(buildv1beta1.Succeeded).Status + + }, time.Duration(1100*getTimeoutMultiplier())*time.Second, 5*time.Second).Should(Equal(trueCondition), "BuildRun did not succeed") + + // Verify that the BuildSpec is still available in the status + testBuildRun, err := testBuild.GetBR(testBuildRun.Name) + Expect(err).ToNot(HaveOccurred()) + Expect(testBuildRun.Status.BuildSpec).ToNot(BeNil(), "BuildSpec is not available in the status") + + Logf("Test build '%s' is completed after %v !", testBuildRun.GetName(), testBuildRun.Status.CompletionTime.Time.Sub(testBuildRun.Status.StartTime.Time)) + + return testBuildRun +} + +func validateBuildRunResultsFromGitSource(testBuildRun *buildv1beta1.BuildRun) { + testBuildRun, err := testBuild.GetBR(testBuildRun.Name) + Expect(err).ToNot(HaveOccurred()) + + Expect(len(testBuildRun.Status.Sources)).To(Equal(1)) + + // Only run the TaskRun checks if Tekton objects can be accessed + if os.Getenv(EnvVarVerifyTektonObjects) == "true" { + tr, err := testBuild.GetTaskRunFromBuildRun(testBuildRun.Name) + Expect(err).ToNot(HaveOccurred()) + + for _, result := range tr.Status.TaskRunResults { + switch result.Name { + case "shp-source-default-commit-sha": + Expect(result.Value.StringVal).To(Equal(testBuildRun.Status.Sources[0].Git.CommitSha)) + case "shp-source-default-commit-author": + Expect(result.Value.StringVal).To(Equal(testBuildRun.Status.Sources[0].Git.CommitAuthor)) + case "shp-source-default-branch-name": + Expect(result.Value.StringVal).To(Equal(testBuildRun.Status.Sources[0].Git.BranchName)) + case "shp-image-digest": + Expect(result.Value.StringVal).To(Equal(testBuildRun.Status.Output.Digest)) + case "shp-image-size": + size, err := strconv.ParseInt(result.Value.StringVal, 10, 64) + Expect(err).To(BeNil()) + Expect(size).To(Equal(testBuildRun.Status.Output.Size)) + } + } + } +} + +func validateBuildRunResultsFromBundleSource(testBuildRun *buildv1beta1.BuildRun) { + testBuildRun, err := testBuild.GetBR(testBuildRun.Name) + Expect(err).ToNot(HaveOccurred()) + + Expect(len(testBuildRun.Status.Sources)).To(Equal(1)) + + // Only run the TaskRun checks if Tekton objects can be accessed + if os.Getenv(EnvVarVerifyTektonObjects) == "true" { + tr, err := testBuild.GetTaskRunFromBuildRun(testBuildRun.Name) + Expect(err).ToNot(HaveOccurred()) + + for _, result := range tr.Status.TaskRunResults { + switch result.Name { + case "shp-source-default-image-digest": + Expect(result.Value.StringVal).To(Equal(testBuildRun.Status.Sources[0].OciArtifact.Digest)) + case "shp-image-digest": + Expect(result.Value.StringVal).To(Equal(testBuildRun.Status.Output.Digest)) + case "shp-image-size": + size, err := strconv.ParseInt(result.Value.StringVal, 10, 64) + Expect(err).To(BeNil()) + Expect(size).To(Equal(testBuildRun.Status.Output.Size)) + } + } + } +} + +// validateBuildRunToFail creates the build run and watches its flow until it fails. +func validateBuildRunToFail(testBuild *utils.TestBuild, testBuildRun *buildv1beta1.BuildRun) { + trueCondition := corev1.ConditionTrue + falseCondition := corev1.ConditionFalse + + // Ensure the BuildRun has been created + err := testBuild.CreateBR(testBuildRun) + Expect(err).ToNot(HaveOccurred(), "Failed to create BuildRun") + + // Ensure a BuildRun eventually moves to a succeeded FALSE status + nextStatusLog := time.Now().Add(60 * time.Second) + Eventually(func() corev1.ConditionStatus { + testBuildRun, err = testBuild.LookupBuildRun(types.NamespacedName{Name: testBuildRun.Name, Namespace: testBuild.Namespace}) + Expect(err).ToNot(HaveOccurred(), "Error retrieving a buildRun") + + if testBuildRun.Status.GetCondition(buildv1beta1.Succeeded) == nil { + return corev1.ConditionUnknown + } + + Expect(testBuildRun.Status.GetCondition(buildv1beta1.Succeeded).Status).NotTo(Equal(trueCondition), "BuildRun status moves to Succeeded") + + now := time.Now() + if now.After(nextStatusLog) { + Logf("Still waiting for build run '%s' to fail.", testBuildRun.Name) + nextStatusLog = time.Now().Add(60 * time.Second) + } + + return testBuildRun.Status.GetCondition(buildv1beta1.Succeeded).Status + + }, time.Duration(1100*getTimeoutMultiplier())*time.Second, 5*time.Second).Should(Equal(falseCondition), "BuildRun did not succeed") + + // Verify that the BuildSpec is still available in the status + Expect(testBuildRun.Status.BuildSpec).ToNot(BeNil(), "BuildSpec is not available in the status") + + Logf("Test build '%s' is completed after %v !", testBuildRun.GetName(), testBuildRun.Status.CompletionTime.Time.Sub(testBuildRun.Status.StartTime.Time)) +} + +// validateServiceAccountDeletion validates that a service account is correctly deleted after the end of +// a build run and depending on the state of the build run +func validateServiceAccountDeletion(buildRun *buildv1beta1.BuildRun, namespace string) { + buildRunCondition := buildRun.Status.GetCondition(buildv1beta1.Succeeded) + if buildRunCondition != nil { + if buildRunCondition.Status == "" || buildRunCondition.Status == corev1.ConditionUnknown { + Logf("Skipping validation of service account deletion because build run did not end.") + return + } + } + + if buildRun.Spec.ServiceAccount == nil { + Logf("Skipping validation of service account deletion because service account is not generated") + return + } + + saNamespacedName := types.NamespacedName{ + Name: buildRun.Name, + Namespace: namespace, + } + + Logf("Verifying that service account '%s' has been deleted.", saNamespacedName.Name) + _, err := testBuild.LookupServiceAccount(saNamespacedName) + Expect(err).To(HaveOccurred(), "Expected error to retrieve the generated service account after build run completion.") + Expect(apierrors.IsNotFound(err)).To(BeTrue(), "Expected service account to be deleted.") +} + +func readAndDecode(filePath string) (runtime.Object, error) { + decode := scheme.Codecs.UniversalDeserializer().Decode + if err := apis.AddToScheme(scheme.Scheme); err != nil { + return nil, err + } + + payload, err := os.ReadFile(filepath.Join("..", "..", filePath)) + if err != nil { + return nil, err + } + + obj, _, err := decode(payload, nil, nil) + return obj, err +} + +// buildStrategyTestData gets the us the BuildStrategy test data set up +func buildStrategyTestData(ns string, buildStrategyCRPath string) (*buildv1beta1.BuildStrategy, error) { + obj, err := readAndDecode(buildStrategyCRPath) + if err != nil { + return nil, err + } + + buildStrategy := obj.(*buildv1beta1.BuildStrategy) + buildStrategy.SetNamespace(ns) + + return buildStrategy, err +} + +func buildTestData(namespace string, identifier string, filePath string) (*buildv1beta1.Build, error) { + obj, err := readAndDecode(filePath) + if err != nil { + return nil, err + } + + build, ok := obj.(*buildv1beta1.Build) + if !ok { + return nil, fmt.Errorf("failed to use the content of %s as a Build runtime object", filePath) + } + + build.SetNamespace(namespace) + build.SetName(identifier) + return build, nil +} + +// buildTestData gets the us the Build test data set up +func buildRunTestData(ns string, identifier string, filePath string) (*buildv1beta1.BuildRun, error) { + obj, err := readAndDecode(filePath) + if err != nil { + return nil, err + } + + buildRun, ok := obj.(*buildv1beta1.BuildRun) + if !ok { + return nil, fmt.Errorf("failed to use the content of %s as a BuildRun runtime object", filePath) + } + + buildRun.SetNamespace(ns) + buildRun.SetName(identifier) + buildRun.Spec.Build.Name = identifier + + serviceAccountName := os.Getenv(EnvVarServiceAccountName) + if serviceAccountName == "generated" { + generate := ".generate" + buildRun.Spec.ServiceAccount = &generate + } else { + buildRun.Spec.ServiceAccount = &serviceAccountName + } + + return buildRun, nil +} + +func appendRegistryInsecureParamValue(build *buildv1beta1.Build, buildRun *buildv1beta1.BuildRun) { + if strings.Contains(build.Spec.Output.Image, "cluster.local") { + parts := strings.Split(build.Spec.Output.Image, "/") + host := parts[0] + buildRun.Spec.ParamValues = append(buildRun.Spec.ParamValues, buildv1beta1.ParamValue{ + Name: "registries-insecure", + Values: []buildv1beta1.SingleValue{ + { + Value: &host, + }, + }, + }) + } +} + +func getTimeoutMultiplier() int64 { + value := os.Getenv(EnvVarTimeoutMultiplier) + if value == "" { + return 1 + } + + intValue, err := strconv.ParseInt(value, 10, 64) + Expect(err).ToNot(HaveOccurred(), "Failed to parse EnvVarTimeoutMultiplier to integer") + return intValue +} diff --git a/test/integration/build_to_buildruns_test.go b/test/integration/build_to_buildruns_test.go index 0748562ef4..ccae8c58a2 100644 --- a/test/integration/build_to_buildruns_test.go +++ b/test/integration/build_to_buildruns_test.go @@ -17,7 +17,7 @@ import ( "k8s.io/apimachinery/pkg/types" "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1alpha1_samples" ) var _ = Describe("Integration tests Build and BuildRuns", func() { diff --git a/test/integration/build_to_git_test.go b/test/integration/build_to_git_test.go index 745f4e207c..0f0cefdc80 100644 --- a/test/integration/build_to_git_test.go +++ b/test/integration/build_to_git_test.go @@ -8,7 +8,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1alpha1_samples" corev1 "k8s.io/api/core/v1" "k8s.io/utils/pointer" ) diff --git a/test/integration/build_to_secrets_test.go b/test/integration/build_to_secrets_test.go index 2fa6cb5429..dcb476ecaf 100644 --- a/test/integration/build_to_secrets_test.go +++ b/test/integration/build_to_secrets_test.go @@ -10,7 +10,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1alpha1_samples" corev1 "k8s.io/api/core/v1" ) diff --git a/test/integration/build_to_taskruns_test.go b/test/integration/build_to_taskruns_test.go index a6469fd5d7..6766cfcd3b 100644 --- a/test/integration/build_to_taskruns_test.go +++ b/test/integration/build_to_taskruns_test.go @@ -10,8 +10,8 @@ import ( corev1 "k8s.io/api/core/v1" "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" - "github.com/shipwright-io/build/test" - "github.com/shipwright-io/build/test/utils" + utils "github.com/shipwright-io/build/test/utils/v1alpha1" + test "github.com/shipwright-io/build/test/v1alpha1_samples" ) var _ = Describe("Integration tests Build and TaskRun", func() { diff --git a/test/integration/buildrun_cleanup_test.go b/test/integration/buildrun_cleanup_test.go index 51c587ad6d..f5ba42d993 100644 --- a/test/integration/buildrun_cleanup_test.go +++ b/test/integration/buildrun_cleanup_test.go @@ -9,7 +9,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1alpha1_samples" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" ) diff --git a/test/integration/buildruns_to_sa_test.go b/test/integration/buildruns_to_sa_test.go index c7d09d9bf1..85e998bb03 100644 --- a/test/integration/buildruns_to_sa_test.go +++ b/test/integration/buildruns_to_sa_test.go @@ -15,7 +15,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1alpha1_samples" ) var _ = Describe("Integration tests BuildRuns and Service-accounts", func() { diff --git a/test/integration/buildruns_to_taskruns_test.go b/test/integration/buildruns_to_taskruns_test.go index 9e3a32400b..5c80107f8e 100644 --- a/test/integration/buildruns_to_taskruns_test.go +++ b/test/integration/buildruns_to_taskruns_test.go @@ -16,7 +16,7 @@ import ( "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" "github.com/shipwright-io/build/pkg/reconciler/buildrun/resources" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1alpha1_samples" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/test/integration/buildstrategy_to_taskruns_test.go b/test/integration/buildstrategy_to_taskruns_test.go index 7b5909734e..1584d16755 100644 --- a/test/integration/buildstrategy_to_taskruns_test.go +++ b/test/integration/buildstrategy_to_taskruns_test.go @@ -11,8 +11,8 @@ import ( . "github.com/onsi/gomega" "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" - "github.com/shipwright-io/build/test" - "github.com/shipwright-io/build/test/utils" + utils "github.com/shipwright-io/build/test/utils/v1alpha1" + test "github.com/shipwright-io/build/test/v1alpha1_samples" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" corev1 "k8s.io/api/core/v1" ) diff --git a/test/integration/clusterbuildstrategy_to_taskruns_test.go b/test/integration/clusterbuildstrategy_to_taskruns_test.go index 1f1d53bda6..1fce846285 100644 --- a/test/integration/clusterbuildstrategy_to_taskruns_test.go +++ b/test/integration/clusterbuildstrategy_to_taskruns_test.go @@ -9,7 +9,7 @@ import ( . "github.com/onsi/gomega" "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1alpha1_samples" ) var _ = Describe("Integration tests ClusterBuildStrategies and TaskRuns", func() { @@ -55,13 +55,13 @@ var _ = Describe("Integration tests ClusterBuildStrategies and TaskRuns", func() } }) - Context("when a buildrun is created", func() { + Context("when a buildrun is created", func() { BeforeEach(func() { buildSample = []byte(test.BuildCBSMinimal) buildRunSample = []byte(test.MinimalBuildRun) }) - It("should create a taskrun with the correct annotations", func() { + It("should create a taskrun with the correct annotations", func() { Expect(tb.CreateBuild(buildObject)).To(BeNil()) buildObject, err = tb.GetBuildTillValidation(buildObject.Name) diff --git a/test/integration/integration_suite_test.go b/test/integration/integration_suite_test.go index 7fa9f095a1..568a7aafb5 100644 --- a/test/integration/integration_suite_test.go +++ b/test/integration/integration_suite_test.go @@ -11,7 +11,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/shipwright-io/build/test/utils" + utils "github.com/shipwright-io/build/test/utils/v1alpha1" ) const ( diff --git a/test/utils/buildruns.go b/test/utils/v1alpha1/buildruns.go similarity index 100% rename from test/utils/buildruns.go rename to test/utils/v1alpha1/buildruns.go diff --git a/test/utils/builds.go b/test/utils/v1alpha1/builds.go similarity index 100% rename from test/utils/builds.go rename to test/utils/v1alpha1/builds.go diff --git a/test/utils/buildstrategies.go b/test/utils/v1alpha1/buildstrategies.go similarity index 100% rename from test/utils/buildstrategies.go rename to test/utils/v1alpha1/buildstrategies.go diff --git a/test/utils/clusterbuildstrategies.go b/test/utils/v1alpha1/clusterbuildstrategies.go similarity index 100% rename from test/utils/clusterbuildstrategies.go rename to test/utils/v1alpha1/clusterbuildstrategies.go diff --git a/test/utils/configmaps.go b/test/utils/v1alpha1/configmaps.go similarity index 100% rename from test/utils/configmaps.go rename to test/utils/v1alpha1/configmaps.go diff --git a/test/utils/controllers.go b/test/utils/v1alpha1/controllers.go similarity index 100% rename from test/utils/controllers.go rename to test/utils/v1alpha1/controllers.go diff --git a/test/utils/v1alpha1/environment.go b/test/utils/v1alpha1/environment.go new file mode 100644 index 0000000000..b614f782f8 --- /dev/null +++ b/test/utils/v1alpha1/environment.go @@ -0,0 +1,128 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "bytes" + "context" + "os" + "path/filepath" + "strconv" + "sync/atomic" + "time" + + "github.com/onsi/ginkgo/v2" + tektonClient "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" + + "k8s.io/client-go/kubernetes" + _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "sigs.k8s.io/controller-runtime/pkg/client" + + buildClient "github.com/shipwright-io/build/pkg/client/clientset/versioned" + "github.com/shipwright-io/build/pkg/ctxlog" + test "github.com/shipwright-io/build/test/v1alpha1_samples" + // from https://github.com/kubernetes/client-go/issues/345 +) + +var ( + namespaceCounter int32 +) + +// TestBuild wraps all required clients to run integration +// tests and also the namespace and operator channel used +// per each test case +type TestBuild struct { + // TODO: Adding specific field for polling here, interval and timeout + // but I think we need a small refactoring to make them global for all + // tests under /test dir + Interval time.Duration + TimeOut time.Duration + KubeConfig *rest.Config + Clientset *kubernetes.Clientset + Namespace string + StopBuildControllers context.CancelFunc + BuildClientSet *buildClient.Clientset + PipelineClientSet *tektonClient.Clientset + ControllerRuntimeClient client.Client + Catalog test.Catalog + Context context.Context + BuildControllerLogBuffer *bytes.Buffer +} + +// NewTestBuild returns an initialized instance of TestBuild +func NewTestBuild() (*TestBuild, error) { + namespaceID := ginkgo.GinkgoParallelProcess()*200 + int(atomic.AddInt32(&namespaceCounter, 1)) + testNamespace := "test-build-" + strconv.Itoa(namespaceID) + + logBuffer := &bytes.Buffer{} + l := ctxlog.NewLoggerTo(logBuffer, testNamespace) + + ctx := ctxlog.NewParentContext(l) + + kubeConfig, restConfig, err := KubeConfig() + if err != nil { + return nil, err + } + + // clientSet is required to communicate with our CRDs objects + // see https://www.openshift.com/blog/kubernetes-deep-dive-code-generation-customresources + buildClientSet, err := buildClient.NewForConfig(restConfig) + if err != nil { + return nil, err + } + + pipelineClientSet, err := tektonClient.NewForConfig(restConfig) + if err != nil { + return nil, err + } + + controllerRuntimeClient, err := client.New(restConfig, client.Options{}) + if err != nil { + return nil, err + } + + ctx, cancelFn := context.WithCancel(ctx) + + return &TestBuild{ + // TODO: interval and timeout can be configured via ENV vars + Interval: time.Second * 3, + TimeOut: time.Second * 180, + KubeConfig: restConfig, + Clientset: kubeConfig, + Namespace: testNamespace, + BuildClientSet: buildClientSet, + PipelineClientSet: pipelineClientSet, + ControllerRuntimeClient: controllerRuntimeClient, + Context: ctx, + BuildControllerLogBuffer: logBuffer, + StopBuildControllers: cancelFn, + }, nil +} + +// KubeConfig returns all required clients to speak with +// the k8s API +func KubeConfig() (*kubernetes.Clientset, *rest.Config, error) { + location := os.Getenv("KUBECONFIG") + if location == "" { + location = filepath.Join(os.Getenv("HOME"), ".kube", "config") + } + + config, err := clientcmd.BuildConfigFromFlags("", location) + if err != nil { + config, err = rest.InClusterConfig() + if err != nil { + return nil, nil, err + } + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, nil, err + } + + return clientset, config, nil +} diff --git a/test/utils/gomega.go b/test/utils/v1alpha1/gomega.go similarity index 100% rename from test/utils/gomega.go rename to test/utils/v1alpha1/gomega.go diff --git a/test/utils/image.go b/test/utils/v1alpha1/image.go similarity index 100% rename from test/utils/image.go rename to test/utils/v1alpha1/image.go diff --git a/test/utils/lookup.go b/test/utils/v1alpha1/lookup.go similarity index 100% rename from test/utils/lookup.go rename to test/utils/v1alpha1/lookup.go diff --git a/test/utils/namespaces.go b/test/utils/v1alpha1/namespaces.go similarity index 100% rename from test/utils/namespaces.go rename to test/utils/v1alpha1/namespaces.go diff --git a/test/utils/secrets.go b/test/utils/v1alpha1/secrets.go similarity index 100% rename from test/utils/secrets.go rename to test/utils/v1alpha1/secrets.go diff --git a/test/utils/service_accounts.go b/test/utils/v1alpha1/service_accounts.go similarity index 100% rename from test/utils/service_accounts.go rename to test/utils/v1alpha1/service_accounts.go diff --git a/test/utils/taskruns.go b/test/utils/v1alpha1/taskruns.go similarity index 100% rename from test/utils/taskruns.go rename to test/utils/v1alpha1/taskruns.go diff --git a/test/utils/v1beta1/buildruns.go b/test/utils/v1beta1/buildruns.go new file mode 100644 index 0000000000..2bb4c5d059 --- /dev/null +++ b/test/utils/v1beta1/buildruns.go @@ -0,0 +1,283 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + + "github.com/shipwright-io/build/pkg/apis/build/v1beta1" +) + +// This class is intended to host all CRUD calls for testing BuildRun CRDs resources + +// CreateBR generates a BuildRun on the current test namespace +func (t *TestBuild) CreateBR(buildRun *v1beta1.BuildRun) error { + brInterface := t.BuildClientSet.ShipwrightV1beta1().BuildRuns(t.Namespace) + + _, err := brInterface.Create(context.TODO(), buildRun, metav1.CreateOptions{}) + if err != nil { + return err + } + return nil +} + +// UpdateBR updates a BuildRun on the current test namespace +func (t *TestBuild) UpdateBR(buildRun *v1beta1.BuildRun) error { + brInterface := t.BuildClientSet.ShipwrightV1beta1().BuildRuns(t.Namespace) + _, err := brInterface.Update(context.TODO(), buildRun, metav1.UpdateOptions{}) + if err != nil { + return err + } + return nil +} + +// GetBR retrieves a BuildRun from a desired namespace +// Deprecated: Use LookupBuildRun instead. +func (t *TestBuild) GetBR(name string) (*v1beta1.BuildRun, error) { + brInterface := t.BuildClientSet.ShipwrightV1beta1().BuildRuns(t.Namespace) + + br, err := brInterface.Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return br, nil +} + +// DeleteBR deletes a BuildRun from a desired namespace +func (t *TestBuild) DeleteBR(name string) error { + brInterface := t.BuildClientSet.ShipwrightV1beta1().BuildRuns(t.Namespace) + + if err := brInterface.Delete(context.TODO(), name, metav1.DeleteOptions{}); err != nil { + return err + } + + return nil +} + +// GetBRReason ... +func (t *TestBuild) GetBRReason(name string) (string, error) { + br, err := t.GetBR(name) + if err != nil { + return "", err + } + cond := br.Status.GetCondition(v1beta1.Succeeded) + if cond == nil { + return "", errors.New("BuildRun had no Succeeded condition") + } + return cond.Reason, nil +} + +// GetBRTillCompletion returns a BuildRun that have a CompletionTime set. +// If the timeout is reached or it fails when retrieving the BuildRun it will +// stop polling and return +func (t *TestBuild) GetBRTillCompletion(name string) (*v1beta1.BuildRun, error) { + + var ( + pollBRTillCompletion = func() (bool, error) { + + bInterface := t.BuildClientSet.ShipwrightV1beta1().BuildRuns(t.Namespace) + + buildRun, err := bInterface.Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return false, err + } + if buildRun.Status.CompletionTime != nil { + return true, nil + } + + return false, nil + } + ) + + brInterface := t.BuildClientSet.ShipwrightV1beta1().BuildRuns(t.Namespace) + + err := wait.PollImmediate(t.Interval, t.TimeOut, pollBRTillCompletion) + if err != nil { + return nil, err + } + + return brInterface.Get(context.TODO(), name, metav1.GetOptions{}) +} + +// GetBRTillNotFound waits for the buildrun to get deleted. It returns an error if BuildRun is not found +func (t *TestBuild) GetBRTillNotFound(name string, interval time.Duration, timeout time.Duration) (*v1beta1.BuildRun, error) { + + var ( + GetBRTillNotFound = func() (bool, error) { + + bInterface := t.BuildClientSet.ShipwrightV1beta1().BuildRuns(t.Namespace) + _, err := bInterface.Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil && apierrors.IsNotFound(err) { + return true, err + } + return false, nil + } + ) + + brInterface := t.BuildClientSet.ShipwrightV1beta1().BuildRuns(t.Namespace) + + err := wait.PollImmediate(interval, timeout, GetBRTillNotFound) + if err != nil { + return nil, err + } + + return brInterface.Get(context.TODO(), name, metav1.GetOptions{}) +} + +// GetBRTillNotOwner returns a BuildRun that has not an owner. +// If the timeout is reached or it fails when retrieving the BuildRun it will +// stop polling and return +func (t *TestBuild) GetBRTillNotOwner(name string, owner string) (*v1beta1.BuildRun, error) { + + brInterface := t.BuildClientSet.ShipwrightV1beta1().BuildRuns(t.Namespace) + + var ( + pollBRTillNotOwner = func() (bool, error) { + + buildRun, err := brInterface.Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return false, err + } + + for _, ownerReference := range buildRun.OwnerReferences { + if ownerReference.Name == owner { + return false, nil + } + } + + return true, nil + } + ) + + if err := wait.PollImmediate(t.Interval, t.TimeOut, pollBRTillNotOwner); err != nil { + return nil, err + } + + return brInterface.Get(context.TODO(), name, metav1.GetOptions{}) +} + +// GetBRTillOwner returns a BuildRun that has an owner. +// If the timeout is reached or it fails when retrieving the BuildRun it will +// stop polling and return +func (t *TestBuild) GetBRTillOwner(name string, owner string) (*v1beta1.BuildRun, error) { + + brInterface := t.BuildClientSet.ShipwrightV1beta1().BuildRuns(t.Namespace) + + var ( + pollBRTillOwner = func() (bool, error) { + + buildRun, err := brInterface.Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return false, err + } + + for _, ownerReference := range buildRun.OwnerReferences { + if ownerReference.Name == owner { + return true, nil + } + } + + return false, nil + } + ) + + if err := wait.PollImmediate(t.Interval, t.TimeOut, pollBRTillOwner); err != nil { + return nil, err + } + + return brInterface.Get(context.TODO(), name, metav1.GetOptions{}) +} + +// GetBRTillStartTime returns a BuildRun that have a StartTime set. +// If the timeout is reached or it fails when retrieving the BuildRun it will +// stop polling and return +func (t *TestBuild) GetBRTillStartTime(name string) (*v1beta1.BuildRun, error) { + + var ( + pollBRTillCompletion = func() (bool, error) { + + bInterface := t.BuildClientSet.ShipwrightV1beta1().BuildRuns(t.Namespace) + + buildRun, err := bInterface.Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return false, err + } + if buildRun.Status.StartTime != nil { + return true, nil + } + + // early exit + if buildRun.Status.CompletionTime != nil { + if buildRunJSON, err := json.Marshal(buildRun); err == nil { + return false, fmt.Errorf("buildrun is completed: %s", buildRunJSON) + } + + return false, fmt.Errorf("buildrun is completed") + } + + return false, nil + } + ) + + brInterface := t.BuildClientSet.ShipwrightV1beta1().BuildRuns(t.Namespace) + + err := wait.PollImmediate(t.Interval, t.TimeOut, pollBRTillCompletion) + if err != nil { + return nil, err + } + + return brInterface.Get(context.TODO(), name, metav1.GetOptions{}) +} + +// GetBRTillDesiredReason polls until a BuildRun gets a particular Reason +// it exit if an error happens or the timeout is reached +func (t *TestBuild) GetBRTillDesiredReason(buildRunname string, reason string) (currentReason string, err error) { + err = wait.PollImmediate(t.Interval, t.TimeOut, func() (bool, error) { + currentReason, err = t.GetBRReason(buildRunname) + if err != nil { + return false, err + } + if currentReason == reason { + return true, nil + } + + return false, nil + }) + + return +} + +// GetBRTillDeletion polls until a BuildRun is not found, it returns +// if a timeout is reached +func (t *TestBuild) GetBRTillDeletion(name string) (bool, error) { + + var ( + pollBRTillCompletion = func() (bool, error) { + + bInterface := t.BuildClientSet.ShipwrightV1beta1().BuildRuns(t.Namespace) + + _, err := bInterface.Get(context.TODO(), name, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + return true, nil + } + return false, nil + } + ) + + err := wait.PollImmediate(t.Interval, t.TimeOut, pollBRTillCompletion) + if err != nil { + return false, err + } + + return true, nil +} diff --git a/test/utils/v1beta1/builds.go b/test/utils/v1beta1/builds.go new file mode 100644 index 0000000000..e6e5704b4c --- /dev/null +++ b/test/utils/v1beta1/builds.go @@ -0,0 +1,149 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "context" + "strings" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + + "github.com/shipwright-io/build/pkg/apis/build/v1beta1" +) + +// This class is intended to host all CRUD calls for testing Build CRDs resources + +// CreateBuild generates a Build on the current test namespace +func (t *TestBuild) CreateBuild(build *v1beta1.Build) error { + bInterface := t.BuildClientSet.ShipwrightV1beta1().Builds(t.Namespace) + + _, err := bInterface.Create(context.TODO(), build, metav1.CreateOptions{}) + return err +} + +// DeleteBuild deletes a Build on the desired namespace +func (t *TestBuild) DeleteBuild(name string) error { + bInterface := t.BuildClientSet.ShipwrightV1beta1().Builds(t.Namespace) + + err := bInterface.Delete(context.TODO(), name, metav1.DeleteOptions{}) + + return err +} + +// GetBuild returns a Build based on name +// Deprecated: Use LookupBuild instead +func (t *TestBuild) GetBuild(name string) (*v1beta1.Build, error) { + return t.BuildClientSet.ShipwrightV1beta1(). + Builds(t.Namespace).Get(context.TODO(), name, metav1.GetOptions{}) +} + +// ListBuilds returns existing Builds from the desired namespace +func (t *TestBuild) ListBuilds(namespace string) (*v1beta1.BuildList, error) { + return t.BuildClientSet.ShipwrightV1beta1().Builds(namespace).List(t.Context, metav1.ListOptions{}) +} + +// PatchBuild patches an existing Build using the merge patch type +func (t *TestBuild) PatchBuild(buildName string, data []byte) (*v1beta1.Build, error) { + return t.PatchBuildWithPatchType(buildName, data, types.MergePatchType) +} + +// PatchBuildWithPatchType patches an existing Build and allows specifying the patch type +func (t *TestBuild) PatchBuildWithPatchType(buildName string, data []byte, pt types.PatchType) (*v1beta1.Build, error) { + bInterface := t.BuildClientSet.ShipwrightV1beta1().Builds(t.Namespace) + b, err := bInterface.Patch(context.TODO(), buildName, pt, data, metav1.PatchOptions{}) + if err != nil { + return nil, err + } + return b, nil +} + +// GetBuildTillValidation polls until a Build gets a validation and updates +// it´s registered field. If timeout is reached or an error is found, it will +// return with an error +func (t *TestBuild) GetBuildTillValidation(name string) (build *v1beta1.Build, err error) { + err = wait.PollImmediate(t.Interval, t.TimeOut, func() (bool, error) { + build, err = t.LookupBuild(types.NamespacedName{Namespace: t.Namespace, Name: name}) + if err != nil && !apierrors.IsNotFound(err) { + return false, err + } + + // TODO: we might improve the conditional here + if build.Status.Registered != nil && *build.Status.Registered != "" { + return true, nil + } + + return false, nil + }) + + return +} + +// GetBuildTillRegistration polls until a Build gets a desired validation and updates +// it´s registered field. If timeout is reached or an error is found, it will +// return with an error +func (t *TestBuild) GetBuildTillRegistration(name string, condition corev1.ConditionStatus) (*v1beta1.Build, error) { + + var ( + pollBuildTillRegistration = func() (bool, error) { + + bInterface := t.BuildClientSet.ShipwrightV1beta1().Builds(t.Namespace) + + buildRun, err := bInterface.Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return false, err + } + // TODO: we might improve the conditional here + if buildRun.Status.Registered != nil && *buildRun.Status.Registered == condition { + return true, nil + } + + return false, nil + } + ) + + brInterface := t.BuildClientSet.ShipwrightV1beta1().Builds(t.Namespace) + + if err := wait.PollImmediate(t.Interval, t.TimeOut, pollBuildTillRegistration); err != nil { + return nil, err + } + + return brInterface.Get(context.TODO(), name, metav1.GetOptions{}) +} + +// GetBuildTillMessageContainsSubstring polls until a Build message contains the desired +// substring value and updates it´s registered field. If timeout is reached or an error is found, +// it will return with an error +func (t *TestBuild) GetBuildTillMessageContainsSubstring(name string, partOfMessage string) (*v1beta1.Build, error) { + + var ( + pollBuildTillMessageContainsSubString = func() (bool, error) { + + bInterface := t.BuildClientSet.ShipwrightV1beta1().Builds(t.Namespace) + + buildRun, err := bInterface.Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return false, err + } + + if strings.Contains(*buildRun.Status.Message, partOfMessage) { + return true, nil + } + + return false, nil + } + ) + + brInterface := t.BuildClientSet.ShipwrightV1beta1().Builds(t.Namespace) + + if err := wait.PollImmediate(t.Interval, t.TimeOut, pollBuildTillMessageContainsSubString); err != nil { + return nil, err + } + + return brInterface.Get(context.TODO(), name, metav1.GetOptions{}) +} diff --git a/test/utils/v1beta1/buildstrategies.go b/test/utils/v1beta1/buildstrategies.go new file mode 100644 index 0000000000..1c480bc97b --- /dev/null +++ b/test/utils/v1beta1/buildstrategies.go @@ -0,0 +1,33 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/shipwright-io/build/pkg/apis/build/v1beta1" +) + +// This class is intended to host all CRUD calls for testing BuildStrategy CRDs resources + +// CreateBuildStrategy generates a BuildStrategy on the current test namespace +func (t *TestBuild) CreateBuildStrategy(bs *v1beta1.BuildStrategy) error { + bsInterface := t.BuildClientSet.ShipwrightV1beta1().BuildStrategies(t.Namespace) + + _, err := bsInterface.Create(context.TODO(), bs, metav1.CreateOptions{}) + if err != nil { + return err + } + return nil +} + +// DeleteBuildStrategy deletes a BuildStrategy on the current test namespace +func (t *TestBuild) DeleteBuildStrategy(name string) error { + bsInterface := t.BuildClientSet.ShipwrightV1beta1().BuildStrategies(t.Namespace) + + return bsInterface.Delete(context.TODO(), name, metav1.DeleteOptions{}) +} diff --git a/test/utils/v1beta1/clusterbuildstrategies.go b/test/utils/v1beta1/clusterbuildstrategies.go new file mode 100644 index 0000000000..bf8ffbc293 --- /dev/null +++ b/test/utils/v1beta1/clusterbuildstrategies.go @@ -0,0 +1,37 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/shipwright-io/build/pkg/apis/build/v1beta1" +) + +// This class is intended to host all CRUD calls for testing ClusterBuildStrategy CRDs resources + +// CreateClusterBuildStrategy generates a ClusterBuildStrategy on the current test namespace +func (t *TestBuild) CreateClusterBuildStrategy(cbs *v1beta1.ClusterBuildStrategy) error { + cbsInterface := t.BuildClientSet.ShipwrightV1beta1().ClusterBuildStrategies() + + _, err := cbsInterface.Create(context.TODO(), cbs, metav1.CreateOptions{}) + if err != nil { + return err + } + return nil +} + +// DeleteClusterBuildStrategy deletes a ClusterBuildStrategy on the desired namespace +func (t *TestBuild) DeleteClusterBuildStrategy(name string) error { + cbsInterface := t.BuildClientSet.ShipwrightV1beta1().ClusterBuildStrategies() + + err := cbsInterface.Delete(context.TODO(), name, metav1.DeleteOptions{}) + if err != nil { + return err + } + return nil +} diff --git a/test/utils/v1beta1/configmaps.go b/test/utils/v1beta1/configmaps.go new file mode 100644 index 0000000000..748bbd803b --- /dev/null +++ b/test/utils/v1beta1/configmaps.go @@ -0,0 +1,33 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// This class is intended to host all CRUD calls for testing configmap primitive resources + +// CreateConfigMap generates a ConfigMap on the current test namespace +func (t *TestBuild) CreateConfigMap(configMap *corev1.ConfigMap) error { + client := t.Clientset.CoreV1().ConfigMaps(t.Namespace) + _, err := client.Create(context.TODO(), configMap, metav1.CreateOptions{}) + if err != nil { + return err + } + return nil +} + +// DeleteConfigMap removes the desired configMap +func (t *TestBuild) DeleteConfigMap(name string) error { + client := t.Clientset.CoreV1().ConfigMaps(t.Namespace) + if err := client.Delete(context.TODO(), name, metav1.DeleteOptions{}); err != nil { + return err + } + return nil +} diff --git a/test/utils/v1beta1/controllers.go b/test/utils/v1beta1/controllers.go new file mode 100644 index 0000000000..539dd58f06 --- /dev/null +++ b/test/utils/v1beta1/controllers.go @@ -0,0 +1,41 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "sigs.k8s.io/controller-runtime/pkg/manager" + + buildconfig "github.com/shipwright-io/build/pkg/config" + "github.com/shipwright-io/build/pkg/controller" +) + +// StartBuildControllers initialize an operator as if being call from main, +// but it disables the prometheus metrics and leader election. This intended +// to for testing. +func (t *TestBuild) StartBuildControllers() error { + c := buildconfig.NewDefaultConfig() + + // read configuration from environment variables, especially the GIT_CONTAINER_IMAGE + c.SetConfigFromEnv() + + mgr, err := controller.NewManager(t.Context, c, t.KubeConfig, manager.Options{ + Namespace: t.Namespace, + LeaderElection: false, + MetricsBindAddress: "0", + }) + if err != nil { + return err + } + + go func() { + // set stopChan with the channel for future closing + err := mgr.Start(t.Context) + if err != nil { + panic(err) + } + }() + + return nil +} diff --git a/test/utils/environment.go b/test/utils/v1beta1/environment.go similarity index 98% rename from test/utils/environment.go rename to test/utils/v1beta1/environment.go index b38d0e8d69..df541a02f3 100644 --- a/test/utils/environment.go +++ b/test/utils/v1beta1/environment.go @@ -24,7 +24,7 @@ import ( buildClient "github.com/shipwright-io/build/pkg/client/clientset/versioned" "github.com/shipwright-io/build/pkg/ctxlog" - "github.com/shipwright-io/build/test" + test "github.com/shipwright-io/build/test/v1beta1_samples" // from https://github.com/kubernetes/client-go/issues/345 ) diff --git a/test/utils/v1beta1/gomega.go b/test/utils/v1beta1/gomega.go new file mode 100644 index 0000000000..db7bc8bdbc --- /dev/null +++ b/test/utils/v1beta1/gomega.go @@ -0,0 +1,142 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "fmt" + "net/http" + "reflect" + + "github.com/onsi/gomega/format" + "github.com/onsi/gomega/types" +) + +type containNamedElementMatcher struct { + Name string +} + +func (matcher *containNamedElementMatcher) FailureMessage(actual interface{}) (message string) { + return format.Message(actual, "to contain element with name", matcher.Name) +} + +func (matcher *containNamedElementMatcher) NegatedFailureMessage(actual interface{}) (message string) { + return format.Message(actual, "not to contain element with name", matcher.Name) +} + +func (matcher *containNamedElementMatcher) Match(actual interface{}) (success bool, err error) { + if actual == nil { + return false, nil + } + + kind := reflect.TypeOf(actual).Kind() + if kind == reflect.Array || kind == reflect.Slice { + value := reflect.ValueOf(actual) + + for i := 0; i < value.Len(); i++ { + vItem := value.Index(i) + vName := vItem.FieldByName("Name") + if !vName.IsZero() && matcher.Name == vName.String() { + return true, nil + } + } + } + + return false, nil +} + +// ContainNamedElement can be applied for an array or slice of objects which have a Name field, to check if any item has a matching name +func ContainNamedElement(name string) types.GomegaMatcher { + return &containNamedElementMatcher{ + Name: name, + } +} + +type containNamedWithValueElementMatcher struct { + Name string + Value string +} + +func (matcher *containNamedWithValueElementMatcher) FailureMessage(actual interface{}) (message string) { + return format.Message(actual, "to contain element with name and value", fmt.Sprintf("%s=%s", matcher.Name, matcher.Value)) +} + +func (matcher *containNamedWithValueElementMatcher) NegatedFailureMessage(actual interface{}) (message string) { + return format.Message(actual, "not to contain element with name and value", fmt.Sprintf("%s=%s", matcher.Name, matcher.Value)) +} + +func (matcher *containNamedWithValueElementMatcher) Match(actual interface{}) (success bool, err error) { + if actual == nil { + return false, nil + } + + kind := reflect.TypeOf(actual).Kind() + if kind == reflect.Array || kind == reflect.Slice { + value := reflect.ValueOf(actual) + + for i := 0; i < value.Len(); i++ { + vItem := value.Index(i) + vName := vItem.FieldByName("Name") + if !vName.IsZero() && matcher.Name == vName.String() { + vValue := vItem.FieldByName("Value") + if !vValue.IsZero() && matcher.Value == vValue.String() { + return true, nil + } + } + } + } + + return false, nil +} + +// ContainNamedElementWithValue can be applied for an array or slice of objects which have a Name and Value field, to check if any item has a matching name and value +func ContainNamedElementWithValue(name string, value string) types.GomegaMatcher { + return &containNamedWithValueElementMatcher{ + Name: name, + Value: value, + } +} + +type returnMatcher struct { + actualStatusCode int + expectedStatusCode int +} + +func (matcher *returnMatcher) FailureMessage(actual interface{}) (message string) { + return format.Message(matcher.expectedStatusCode, fmt.Sprintf("to be the HTTP response for %s, but received", actual), matcher.actualStatusCode) +} + +func (matcher *returnMatcher) NegatedFailureMessage(actual interface{}) (message string) { + return format.Message(matcher.expectedStatusCode, fmt.Sprintf("to not be the HTTP response for %s, but received", actual), matcher.actualStatusCode) +} + +func (matcher *returnMatcher) Match(actual interface{}) (success bool, err error) { + if actual == nil { + return false, nil + } + + kind := reflect.TypeOf(actual).Kind() + if kind == reflect.String { + url := reflect.ValueOf(actual).String() + + // #nosec:G107 test code + resp, err := http.Get(url) + if err != nil { + return false, err + } + + matcher.actualStatusCode = resp.StatusCode + + return resp.StatusCode == matcher.expectedStatusCode, nil + } + + return false, nil +} + +// Return can be applied for a string, it will call the URL and check the status code +func Return(statusCode int) types.GomegaMatcher { + return &returnMatcher{ + expectedStatusCode: statusCode, + } +} diff --git a/test/utils/v1beta1/image.go b/test/utils/v1beta1/image.go new file mode 100644 index 0000000000..a46e78457a --- /dev/null +++ b/test/utils/v1beta1/image.go @@ -0,0 +1,115 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "encoding/json" + "fmt" + "log" + "strings" + + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + containerreg "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" + buildv1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1" + "k8s.io/apimachinery/pkg/types" + + . "github.com/onsi/gomega" +) + +func getImageURL(buildRun *buildv1beta1.BuildRun) string { + image := "" + if buildRun.Spec.Output != nil { + image = buildRun.Spec.Output.Image + } else { + image = buildRun.Status.BuildSpec.Output.Image + } + + if buildRun.Status.Output != nil && buildRun.Status.Output.Digest != "" { + image = fmt.Sprintf("%s@%s", image, buildRun.Status.Output.Digest) + } + + // In the GitHub action, we are using a registry inside the cluster to + // push the image created by `buildRun`. The registry inside the cluster + // is not directly accessible from the local, so that we have mapped + // the cluster registry port to the local system + // by providing `test/kind/config.yaml` config to the kind + return strings.Replace(image, "registry.registry.svc.cluster.local", "localhost", 1) +} + +// GetImage loads the image manifest for the image produced by a BuildRun +func (t *TestBuild) GetImage(buildRun *buildv1beta1.BuildRun) containerreg.Image { + ref, err := name.ParseReference(getImageURL(buildRun)) + Expect(err).ToNot(HaveOccurred()) + + img, err := remote.Image(ref, remote.WithAuth(t.getRegistryAuthentication(buildRun, ref))) + Expect(err).ToNot(HaveOccurred()) + + return img +} + +func (t *TestBuild) getRegistryAuthentication( + buildRun *buildv1beta1.BuildRun, + ref name.Reference, +) authn.Authenticator { + secretName := "" + if buildRun.Spec.Output != nil && buildRun.Spec.Output.PushSecret != nil { + secretName = *buildRun.Spec.Output.PushSecret + } else if buildRun.Status.BuildSpec.Output.PushSecret != nil { + secretName = *buildRun.Status.BuildSpec.Output.PushSecret + } + + // In case no secret is mounted, use anonymous + if secretName == "" { + log.Println("No access credentials provided, using anonymous mode") + return authn.Anonymous + } + + secret, err := t.LookupSecret( + types.NamespacedName{ + Namespace: buildRun.Namespace, + Name: secretName, + }, + ) + Expect(err).ToNot(HaveOccurred(), "Error retrieving registry secret") + + type auth struct { + Auths map[string]authn.AuthConfig `json:"auths,omitempty"` + } + + var authConfig auth + + Expect(json.Unmarshal(secret.Data[".dockerconfigjson"], &authConfig)).ToNot(HaveOccurred(), "Error parsing secrets docker config") + + // Look-up the respective registry server inside the credentials + registryName := ref.Context().RegistryStr() + if registryName == name.DefaultRegistry { + registryName = authn.DefaultAuthKey + } + + return authn.FromConfig(authConfig.Auths[registryName]) +} + +// ValidateImagePlatformsExist that the image produced by a BuildRun exists for a set of platforms +func (t *TestBuild) ValidateImagePlatformsExist(buildRun *buildv1beta1.BuildRun, expectedPlatforms []containerreg.Platform) { + ref, err := name.ParseReference(getImageURL(buildRun)) + Expect(err).ToNot(HaveOccurred()) + + for _, expectedPlatform := range expectedPlatforms { + _, err := remote.Image(ref, remote.WithAuth(t.getRegistryAuthentication(buildRun, ref)), remote.WithPlatform(expectedPlatform)) + Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("Failed to validate %s/%s", expectedPlatform.OS, expectedPlatform.Architecture)) + } +} + +// ValidateImageDigest ensures that an image digest is set in the BuildRun status and that this digest is pointing to an image +func (t *TestBuild) ValidateImageDigest(buildRun *buildv1beta1.BuildRun) { + // Verify that the status contains a digest + Expect(buildRun.Status.Output).NotTo(BeNil(), ".status.output is nil") + Expect(buildRun.Status.Output.Digest).NotTo(Equal(""), ".status.output.digest is empty") + + // Verify that the digest is valid by retrieving the image manifest + t.GetImage(buildRun) +} diff --git a/test/utils/v1beta1/lookup.go b/test/utils/v1beta1/lookup.go new file mode 100644 index 0000000000..595c9a6e5b --- /dev/null +++ b/test/utils/v1beta1/lookup.go @@ -0,0 +1,151 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "fmt" + "strings" + "time" + + pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + + buildv1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1" +) + +func (t *TestBuild) LookupSecret(entity types.NamespacedName) (*corev1.Secret, error) { + result, err := lookupRuntimeObject(func() (runtime.Object, error) { + return t.Clientset. + CoreV1(). + Secrets(entity.Namespace). + Get(t.Context, entity.Name, metav1.GetOptions{}) + }) + + return result.(*corev1.Secret), err +} + +func (t *TestBuild) LookupPod(entity types.NamespacedName) (*corev1.Pod, error) { + result, err := lookupRuntimeObject(func() (runtime.Object, error) { + return t.Clientset. + CoreV1(). + Pods(entity.Namespace). + Get(t.Context, entity.Name, metav1.GetOptions{}) + }) + + return result.(*corev1.Pod), err +} + +func (t *TestBuild) LookupBuild(entity types.NamespacedName) (*buildv1beta1.Build, error) { + result, err := lookupRuntimeObject(func() (runtime.Object, error) { + return t.BuildClientSet.ShipwrightV1beta1(). + Builds(entity.Namespace).Get(t.Context, entity.Name, metav1.GetOptions{}) + }) + + return result.(*buildv1beta1.Build), err +} + +func (t *TestBuild) LookupBuildRun(entity types.NamespacedName) (*buildv1beta1.BuildRun, error) { + result, err := lookupRuntimeObject(func() (runtime.Object, error) { + return t.BuildClientSet.ShipwrightV1beta1(). + BuildRuns(entity.Namespace).Get(t.Context, entity.Name, metav1.GetOptions{}) + }) + + return result.(*buildv1beta1.BuildRun), err +} + +func (t *TestBuild) LookupTaskRun(entity types.NamespacedName) (*pipelinev1beta1.TaskRun, error) { + result, err := lookupRuntimeObject(func() (runtime.Object, error) { + return t.PipelineClientSet. + TektonV1beta1(). + TaskRuns(entity.Namespace). + Get(t.Context, entity.Name, metav1.GetOptions{}) + }) + + return result.(*pipelinev1beta1.TaskRun), err +} + +func (t *TestBuild) LookupTaskRunUsingBuildRun(buildRun *buildv1beta1.BuildRun) (*pipelinev1beta1.TaskRun, error) { + if buildRun == nil { + return nil, fmt.Errorf("no BuildRun specified to lookup TaskRun") + } + + if buildRun.Status.TaskRunName != nil { + return t.LookupTaskRun(types.NamespacedName{Namespace: buildRun.Namespace, Name: *buildRun.Status.TaskRunName}) + } + + tmp, err := lookupRuntimeObject(func() (runtime.Object, error) { + return t.PipelineClientSet. + TektonV1beta1(). + TaskRuns(buildRun.Namespace). + List(t.Context, metav1.ListOptions{ + LabelSelector: labels.SelectorFromSet( + map[string]string{ + buildv1beta1.LabelBuildRun: buildRun.Name, + }).String(), + }) + }) + + if err != nil { + return nil, err + } + + var taskRunList = tmp.(*pipelinev1beta1.TaskRunList) + switch len(taskRunList.Items) { + case 0: + return nil, fmt.Errorf("no TaskRun found for BuildRun %s/%s", buildRun.Namespace, buildRun.Name) + + case 1: + return &taskRunList.Items[0], nil + + default: + return nil, fmt.Errorf("multiple TaskRuns found for BuildRun %s/%s, which should not have happened", buildRun.Namespace, buildRun.Name) + } +} + +func (t *TestBuild) LookupServiceAccount(entity types.NamespacedName) (*corev1.ServiceAccount, error) { + result, err := lookupRuntimeObject(func() (runtime.Object, error) { + return t.Clientset. + CoreV1(). + ServiceAccounts(entity.Namespace). + Get(t.Context, entity.Name, metav1.GetOptions{}) + }) + + return result.(*corev1.ServiceAccount), err +} + +func lookupRuntimeObject(f func() (runtime.Object, error)) (result runtime.Object, err error) { + err = wait.PollImmediate(4*time.Second, 60*time.Second, func() (bool, error) { + result, err = f() + if err != nil { + // check if we have an error that we want to retry + if isRetryableError(err) { + return false, nil + } + + return true, err + } + + // successful call + return true, nil + }) + + return +} + +func isRetryableError(err error) bool { + return apierrors.IsServerTimeout(err) || + apierrors.IsTimeout(err) || + apierrors.IsTooManyRequests(err) || + apierrors.IsInternalError(err) || + err.Error() == "etcdserver: request timed out" || + err.Error() == "rpc error: code = Unavailable desc = transport is closing" || + strings.Contains(err.Error(), "net/http: request canceled while waiting for connection") +} diff --git a/test/utils/v1beta1/namespaces.go b/test/utils/v1beta1/namespaces.go new file mode 100644 index 0000000000..6d9cfd456c --- /dev/null +++ b/test/utils/v1beta1/namespaces.go @@ -0,0 +1,72 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" +) + +// This class is intended to host all CRUD calls for Namespace primitive resources + +// CreateNamespace generates a Namespace with the current test name +func (t *TestBuild) CreateNamespace() error { + client := t.Clientset.CoreV1().Namespaces() + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: t.Namespace, + }, + } + _, err := client.Create(context.TODO(), ns, metav1.CreateOptions{}) + if err != nil { + return err + } + + // wait for the default service account to exist + pollServiceAccount := func() (bool, error) { + + serviceAccountInterface := t.Clientset.CoreV1().ServiceAccounts(t.Namespace) + + _, err := serviceAccountInterface.Get(context.TODO(), "default", metav1.GetOptions{}) + if err != nil { + if !apierrors.IsNotFound(err) { + return false, err + } + return false, nil + } + + return true, nil + } + + return wait.PollImmediate(t.Interval, t.TimeOut, pollServiceAccount) +} + +// DeleteNamespace deletes the namespace with the current test name +func (t *TestBuild) DeleteNamespace() error { + client := t.Clientset.CoreV1().Namespaces() + + if err := client.Delete(context.TODO(), t.Namespace, metav1.DeleteOptions{}); err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return err + } + + // wait for the namespace to be deleted + pollNamespace := func() (bool, error) { + + if _, err := client.Get(context.TODO(), t.Namespace, metav1.GetOptions{}); err != nil && apierrors.IsNotFound(err) { + return true, nil + } + + return false, nil + } + + return wait.PollImmediate(t.Interval, t.TimeOut, pollNamespace) +} diff --git a/test/utils/v1beta1/secrets.go b/test/utils/v1beta1/secrets.go new file mode 100644 index 0000000000..9bd8016b95 --- /dev/null +++ b/test/utils/v1beta1/secrets.go @@ -0,0 +1,50 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +// This class is intended to host all CRUD calls for testing secrets primitive resources + +// CreateSecret generates a Secret on the current test namespace +func (t *TestBuild) CreateSecret(secret *corev1.Secret) error { + client := t.Clientset.CoreV1().Secrets(t.Namespace) + _, err := client.Create(context.TODO(), secret, metav1.CreateOptions{}) + if err != nil { + return err + } + return nil +} + +// DeleteSecret removes the desired secret +func (t *TestBuild) DeleteSecret(name string) error { + client := t.Clientset.CoreV1().Secrets(t.Namespace) + if err := client.Delete(context.TODO(), name, metav1.DeleteOptions{}); err != nil { + return err + } + return nil +} + +// PatchSecret patches a secret based on name and with the provided data. +// It used the merge type strategy +func (t *TestBuild) PatchSecret(name string, data []byte) (*corev1.Secret, error) { + return t.PatchSecretWithPatchType(name, data, types.MergePatchType) +} + +// PatchSecretWithPatchType patches a secret with a desire data and patch strategy +func (t *TestBuild) PatchSecretWithPatchType(name string, data []byte, pt types.PatchType) (*corev1.Secret, error) { + secInterface := t.Clientset.CoreV1().Secrets(t.Namespace) + b, err := secInterface.Patch(context.TODO(), name, pt, data, metav1.PatchOptions{}) + if err != nil { + return nil, err + } + return b, nil +} diff --git a/test/utils/v1beta1/service_accounts.go b/test/utils/v1beta1/service_accounts.go new file mode 100644 index 0000000000..27de19f5ed --- /dev/null +++ b/test/utils/v1beta1/service_accounts.go @@ -0,0 +1,44 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// This class is intended to host all CRUD calls for testing secrets primitive resources + +// CreateSAFromName creates a simple ServiceAccount with the provided name if it does not exist. +func (t *TestBuild) CreateSAFromName(saName string) error { + client := t.Clientset.CoreV1().ServiceAccounts(t.Namespace) + _, err := client.Get(context.TODO(), saName, metav1.GetOptions{}) + // If the service account already exists, no error is returned + if err == nil { + return nil + } + if !apierrors.IsNotFound(err) { + return err + } + _, err = client.Create(context.TODO(), &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: saName, + }}, metav1.CreateOptions{}) + return err +} + +// GetSA retrieves an existing service-account by name +// Deprecated: Use LookupServiceAccount instead. +func (t *TestBuild) GetSA(saName string) (*corev1.ServiceAccount, error) { + client := t.Clientset.CoreV1().ServiceAccounts(t.Namespace) + sa, err := client.Get(context.TODO(), saName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return sa, nil +} diff --git a/test/utils/v1beta1/taskruns.go b/test/utils/v1beta1/taskruns.go new file mode 100644 index 0000000000..aef9a53919 --- /dev/null +++ b/test/utils/v1beta1/taskruns.go @@ -0,0 +1,115 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "context" + "errors" + "fmt" + + v1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + "knative.dev/pkg/apis" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" +) + +// This class is intended to host all CRUD calls for testing TaskRuns CRDs resources + +// GetTaskRunFromBuildRun retrieves an owned TaskRun based on the BuildRunName +func (t *TestBuild) GetTaskRunFromBuildRun(buildRunName string) (*v1beta1.TaskRun, error) { + taskRunLabelSelector := fmt.Sprintf("buildrun.shipwright.io/name=%s", buildRunName) + + trInterface := t.PipelineClientSet.TektonV1beta1().TaskRuns(t.Namespace) + + trList, err := trInterface.List(context.TODO(), metav1.ListOptions{ + LabelSelector: taskRunLabelSelector, + }) + if err != nil { + return nil, err + } + + if len(trList.Items) != 1 { + return nil, fmt.Errorf("failed to find an owned TaskRun based on a Buildrun %s/%s name", t.Namespace, buildRunName) + } + + return &trList.Items[0], nil +} + +// UpdateTaskRun applies changes to a TaskRun object +func (t *TestBuild) UpdateTaskRun(name string, apply func(tr *v1beta1.TaskRun)) (*v1beta1.TaskRun, error) { + var tr *v1beta1.TaskRun + var err error + for i := 0; i < 5; i++ { + tr, err = t.LookupTaskRun(types.NamespacedName{ + Namespace: t.Namespace, + Name: name, + }) + if err != nil { + return nil, err + } + + apply(tr) + + tr, err = t.PipelineClientSet.TektonV1beta1().TaskRuns(t.Namespace).Update(context.TODO(), tr, metav1.UpdateOptions{}) + if err == nil { + return tr, nil + } + // retry the famous ""Operation cannot be fulfilled on taskruns.tekton.dev \"buildrun-test-build-225-xkw6k\": the object has been modified; please apply your changes to the latest version and try again" error + if !apierrors.IsConflict(err) { + return nil, err + } + } + + return nil, err +} + +// GetTRReason returns the Reason of the Succeeded condition +// of an existing TaskRun based on a BuildRun name +func (t *TestBuild) GetTRReason(buildRunName string) (string, error) { + tr, err := t.GetTaskRunFromBuildRun(buildRunName) + if err != nil { + return "", err + } + + trCondition := tr.Status.GetCondition(apis.ConditionSucceeded) + if trCondition != nil { + return trCondition.Reason, nil + } + + return "", errors.New("foo") +} + +// GetTRTillDesiredReason polls until a TaskRun matches a desired Reason +// or it exits if an error happen or a timeout is reach. +func (t *TestBuild) GetTRTillDesiredReason(buildRunName string, reason string) (trReason string, err error) { + err = wait.PollImmediate(t.Interval, t.TimeOut, func() (bool, error) { + trReason, err = t.GetTRReason(buildRunName) + if err != nil { + return false, err + } + + if trReason == reason { + return true, nil + } + + return false, nil + }) + + return +} + +// DeleteTR deletes a TaskRun from a desired namespace +func (t *TestBuild) DeleteTR(name string) error { + trInterface := t.PipelineClientSet.TektonV1beta1().TaskRuns(t.Namespace) + + if err := trInterface.Delete(context.TODO(), name, metav1.DeleteOptions{}); err != nil { + return err + } + + return nil +} diff --git a/test/build_samples.go b/test/v1alpha1_samples/build_samples.go similarity index 99% rename from test/build_samples.go rename to test/v1alpha1_samples/build_samples.go index 9351f4adba..48e6b1fabc 100644 --- a/test/build_samples.go +++ b/test/v1alpha1_samples/build_samples.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package test +package testalpha // MinimalBuildahBuildWithEnvVars defines a simple // Build with a source, strategy, and env vars diff --git a/test/buildrun_samples.go b/test/v1alpha1_samples/buildrun_samples.go similarity index 99% rename from test/buildrun_samples.go rename to test/v1alpha1_samples/buildrun_samples.go index 84a0964e12..02e505104d 100644 --- a/test/buildrun_samples.go +++ b/test/v1alpha1_samples/buildrun_samples.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package test +package testalpha // MinimalBuildahBuildRunWithEnvVars defines a simple // BuildRun with a referenced Build and env vars diff --git a/test/buildstrategy_samples.go b/test/v1alpha1_samples/buildstrategy_samples.go similarity index 99% rename from test/buildstrategy_samples.go rename to test/v1alpha1_samples/buildstrategy_samples.go index 8446c4b643..210b984ae9 100644 --- a/test/buildstrategy_samples.go +++ b/test/v1alpha1_samples/buildstrategy_samples.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package test +package testalpha // MinimalBuildahBuildStrategy defines a // BuildStrategy for Buildah with two steps diff --git a/test/catalog.go b/test/v1alpha1_samples/catalog.go similarity index 99% rename from test/catalog.go rename to test/v1alpha1_samples/catalog.go index ea2e885d70..df8b7290ca 100644 --- a/test/catalog.go +++ b/test/v1alpha1_samples/catalog.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package test +package testalpha import ( "context" diff --git a/test/clusterbuildstrategy_samples.go b/test/v1alpha1_samples/clusterbuildstrategy_samples.go similarity index 99% rename from test/clusterbuildstrategy_samples.go rename to test/v1alpha1_samples/clusterbuildstrategy_samples.go index c76eac43c0..2158dddec5 100644 --- a/test/clusterbuildstrategy_samples.go +++ b/test/v1alpha1_samples/clusterbuildstrategy_samples.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package test +package testalpha // MinimalBuildahClusterBuildStrategy defines a // BuildStrategy for Buildah with two steps diff --git a/test/v1beta1_samples/build_samples.go b/test/v1beta1_samples/build_samples.go new file mode 100644 index 0000000000..20427ec0ce --- /dev/null +++ b/test/v1beta1_samples/build_samples.go @@ -0,0 +1,631 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package testbeta + +// MinimalBuildahBuildWithEnvVars defines a simple +// Build with a source, strategy, and env vars +const MinimalBuildahBuildWithEnvVars = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +metadata: + name: buildah +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + strategy: + name: buildah + kind: ClusterBuildStrategy + paramValues: + - name: dockerfile + value: Dockerfile + env: + - name: MY_VAR_1 + value: "my-var-1-build-value" + - name: MY_VAR_2 + valueFrom: + fieldRef: + fieldPath: "my-fieldpath" +` + +// MinimalBuildahBuild defines a simple +// Build with a source and a strategy +const MinimalBuildahBuild = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +metadata: + name: buildah +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + strategy: + name: buildah + kind: ClusterBuildStrategy + paramValues: + - name: dockerfile + value: Dockerfile +` + +// BuildahBuildWithOutput defines a simple +// Build with a source, strategy and output +const BuildahBuildWithOutput = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +metadata: + name: buildah + namespace: build-test +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + strategy: + name: buildah + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// BuildahBuildWithAnnotationAndLabel defines a simple +// Build with a source, strategy, output, +// annotations and labels +const BuildahBuildWithAnnotationAndLabel = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +metadata: + name: buildah +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + strategy: + name: buildah + kind: ClusterBuildStrategy + paramValues: + - name: dockerfile + value: Dockerfile + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + labels: + "maintainer": "team@my-company.com" + annotations: + "org.opencontainers.image.url": https://my-company.com/images +` + +// BuildahBuildWithMultipleAnnotationAndLabel defines a +// Build with a source, strategy, output, +// multiple annotations and labels +const BuildahBuildWithMultipleAnnotationAndLabel = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +metadata: + name: buildah +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + strategy: + name: buildah + kind: ClusterBuildStrategy + paramValues: + - name: dockerfile + value: Dockerfile + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + labels: + "maintainer": "team@my-company.com" + "description": "This is my cool image" + annotations: + "org.opencontainers.image.url": https://my-company.com/images + "org.opencontainers.image.source": "https://github.com/org/repo" +` + +// BuildpacksBuildWithBuilderAndTimeOut defines a Build with +// source, strategy, builder, output and +// timeout +const BuildpacksBuildWithBuilderAndTimeOut = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +metadata: + name: buildpacks-v3 + namespace: build-test +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + contextDir: docker-build + strategy: + name: buildpacks-v3 + kind: ClusterBuildStrategy + paramValues: + - name: dockerfile + value: Dockerfile + - name: builder-image + value: heroku/buildpacks:18 + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + timeout: 30s +` + +// BuildahBuildWithTimeOut defines a Build for +// Buildah with source, strategy, output and +// timeout +const BuildahBuildWithTimeOut = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +metadata: + name: buildah + namespace: build-test +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + strategy: + name: buildah + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + timeout: 30s +` + +// BuildBSMinimal defines a Build with a BuildStrategy +const BuildBSMinimal = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + strategy: + kind: BuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// BuildBSMinimalNoSource defines a Build with a BuildStrategy without sources +const BuildBSMinimalNoSource = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: {} + strategy: + kind: BuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// BuildCBSMinimal defines a Build with a +// ClusterBuildStrategy +const BuildCBSMinimal = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + contextDir: docker-build + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// BuildCBSMinimalWithFakeSecret defines a Build with a +// ClusterBuildStrategy and an not existing secret +const BuildCBSMinimalWithFakeSecret = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + pushSecret: fake-secret +` + +// BuildWithOutputRefSecret defines a Build with a +// referenced secret under spec.output +const BuildWithOutputRefSecret = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + pushSecret: output-secret + timeout: 5s +` + +// BuildWithSourceRefSecret defines a Build with a +// referenced secret under spec.source +const BuildWithSourceRefSecret = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + cloneSecret: source-secret + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + timeout: 5s +` + +// BuildWithBuilderRefSecret defines a Build with a +// referenced secret under spec.builder +const BuildWithBuilderRefSecret = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + paramValues: + - name: builder-image + value: heroku/buildpacks:18 + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + timeout: 5s +` + +// BuildWithMultipleRefSecrets defines a Build with +// multiple referenced secrets under spec +const BuildWithMultipleRefSecrets = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + cloneSecret: source-secret + paramValues: + - name: builder-image + value: heroku/buildpacks:18 + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + timeout: 5s +` + +// BuildCBSWithShortTimeOut defines a Build with a +// ClusterBuildStrategy and a short timeout +const BuildCBSWithShortTimeOut = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + timeout: 5s +` + +// BuildCBSWithShortTimeOutAndRefOutputSecret defines a Build with a +// ClusterBuildStrategy, a short timeout and an output secret +const BuildCBSWithShortTimeOutAndRefOutputSecret = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + pushSecret: foobarsecret + timeout: 5s +` + +// BuildCBSWithWrongURL defines a Build with a +// ClusterBuildStrategy and a non-existing url +const BuildCBSWithWrongURL = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +metadata: + annotations: + build.shipwright.io/verify.repository: "true" +spec: + source: + git: + url: "https://github.foobar.com/sbose78/taxi" + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// BuildCBSWithVerifyRepositoryAnnotation defines a Build +// with the verify repository annotation key +const BuildCBSWithVerifyRepositoryAnnotation = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +metadata: + annotations: + build.shipwright.io/verify.repository: "" +spec: + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// BuildCBSWithoutVerifyRepositoryAnnotation defines a minimal +// Build without source url and annotation +const BuildCBSWithoutVerifyRepositoryAnnotation = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// BuildCBSWithBuildRunDeletion defines a Build with a +// ClusterBuildStrategy and the annotation for automatic BuildRun +// deletion +const BuildCBSWithBuildRunDeletion = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + strategy: + kind: ClusterBuildStrategy + retention: + atBuildDeletion: true + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// BuildWithSleepTimeParam defines a Build with a parameter +const BuildWithSleepTimeParam = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + paramValues: + - name: sleep-time + value: "30" + strategy: + kind: BuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// BuildWithArrayParam defines a Build with an array parameter +const BuildWithArrayParam = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + paramValues: + - name: array-param + values: + - value: "3" + - value: "-1" + strategy: + kind: BuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// BuildWithConfigMapSecretParams defines a Build with parameter values referencing a ConfigMap and Secret +const BuildWithConfigMapSecretParams = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + paramValues: + - name: array-param + values: + - value: "3" + - configMapValue: + name: a-configmap + key: a-cm-key + - value: "-1" + - name: sleep-time + secretValue: + name: a-secret + key: a-secret-key + strategy: + kind: BuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// BuildWithRestrictedParam defines a Build using params that are reserved only +// for shipwright +const BuildWithRestrictedParam = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + paramValues: + - name: shp-something + value: "30" + - name: DOCKERFILE + value: "30" + strategy: + kind: BuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// BuildWithUndefinedParameter defines a param that was not declared under the +// strategy parameters +const BuildWithUndefinedParam = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + paramValues: + - name: sleep-not + value: "30" + strategy: + kind: BuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// BuildWithEmptyStringParam defines a param that with an empty string value +const BuildWithEmptyStringParam = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + paramValues: + - name: sleep-time + value: "" + strategy: + kind: BuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// BuildWithUndefinedParamAndCBS defines a param that was not declared under the +// strategy parameters of a ClusterBuildStrategy +const BuildWithUndefinedParamAndCBS = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + paramValues: + - name: sleep-not + value: "30" + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// BuildWithSleepTimeParamAndCBS defines a Build with a parameter +const BuildWithSleepTimeParamAndCBS = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + paramValues: + - name: sleep-time + value: "30" + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// MinimalBuildWithRetentionTTLFive defines a simple +// Build with a source, a strategy and ttl +const MinimalBuildWithRetentionTTLFive = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +metadata: + name: build-retention-ttl +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + contextDir: docker-build + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + retention: + ttlAfterFailed: 5s + ttlAfterSucceeded: 5s +` + +// MinimalBuildWithRetentionLimitOne defines a simple +// Build with a source, a strategy and limits set as 1 +const MinimalBuildWithRetentionLimitOne = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +metadata: + name: build-retention-limit +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + contextDir: docker-build + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + retention: + failedLimit: 1 + succeededLimit: 1 +` + +// MinimalBuildWithRetentionLimitDiff defines a simple Build with a source, +// a strategy and different failed and succeeded limits +const MinimalBuildWithRetentionLimitDiff = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +metadata: + name: build-retention-limit +spec: + source: + url: "https://github.com/shipwright-io/sample-go" + contextDir: docker-build + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + retention: + failedLimit: 1 + succeededLimit: 2 +` + +// MinimalBuildWithRetentionTTL defines a simple +// Build with a source, a strategy ttl +const MinimalBuildWithRetentionTTLOneMin = ` +apiVersion: shipwright.io/v1beta1 +kind: Build +metadata: + name: build-retention-ttl +spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + contextDir: docker-build + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + retention: + ttlAfterFailed: 1m + ttlAfterSucceeded: 1m +` diff --git a/test/v1beta1_samples/buildrun_samples.go b/test/v1beta1_samples/buildrun_samples.go new file mode 100644 index 0000000000..0d8676400c --- /dev/null +++ b/test/v1beta1_samples/buildrun_samples.go @@ -0,0 +1,253 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package testbeta + +// MinimalBuildahBuildRunWithEnvVars defines a simple +// BuildRun with a referenced Build and env vars +const MinimalBuildahBuildRunWithEnvVars = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +metadata: + name: buildah-run +spec: + build: + name: buildah + env: + - name: MY_VAR_2 + value: "my-var-2-buildrun-value" + - name: MY_VAR_3 + valueFrom: + fieldRef: + fieldPath: "my-fieldpath" +` + +// BuildahBuildRunWithOutputImageLabelsAndAnnotations defines a BuildRun +// with a output image labels and annotation +const BuildahBuildRunWithOutputImageLabelsAndAnnotations = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +metadata: + name: buildah-run + namespace: build-test +spec: + build: + name: buildah + serviceAccount: buildpacks-v3-serviceaccount + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app-v2 + labels: + "maintainer": "new-team@my-company.com" + "foo": "bar" + annotations: + "org.opencontainers.owner": "my-company" +` + +// MinimalBuildahBuildRun defines a simple +// BuildRun with a referenced Build +const MinimalBuildahBuildRun = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +metadata: + name: buildah-run +spec: + build: + name: buildah +` + +// BuildahBuildRunWithSA defines a BuildRun +// with a service-account +const BuildahBuildRunWithSA = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +metadata: + name: buildah-run + namespace: build-test +spec: + build: + name: buildah + serviceAccount: buildpacks-v3-serviceaccount +` + +// BuildahBuildRunWithSAAndOutput defines a BuildRun +// with a service-account and output overrides +const BuildahBuildRunWithSAAndOutput = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +metadata: + name: buildah-run + namespace: build-test +spec: + build: + name: buildah + serviceAccount: buildpacks-v3-serviceaccount + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app-v2 +` + +// BuildpacksBuildRunWithSA defines a BuildRun +// with a service-account +const BuildpacksBuildRunWithSA = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +metadata: + name: buildpacks-v3-run + namespace: build-test +spec: + build: + name: buildpacks-v3 + serviceAccount: buildpacks-v3-serviceaccount +` + +// BuildahBuildRunWithTimeOutAndSA defines a BuildRun +// with a service-account and timeout +const BuildahBuildRunWithTimeOutAndSA = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +metadata: + name: buildah-run + namespace: build-test +spec: + build: + name: buildah + serviceAccount: buildpacks-v3-serviceaccount + timeout: 1m +` + +// MinimalBuildRun defines a minimal BuildRun +// with a reference to a not existing Build +const MinimalBuildRun = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +spec: + build: + name: foobar +` + +const MinimalOneOffBuildRun = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +metadata: + name: standalone-buildrun +spec: + build: + spec: + source: + git: + url: "https://github.com/shipwright-io/sample-go" + contextDir: docker-build + strategy: + kind: ClusterBuildStrategy + name: buildah + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app +` + +// MinimalBuildRunWithParams defines a param override +const MinimalBuildRunWithParams = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +spec: + paramValues: + - name: sleep-time + value: "15" + build: + name: foobar +` + +const MinimalBuildRunWithReservedParams = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +spec: + paramValues: + - name: shp-sleep-time + value: "15" + build: + name: foobar +` + +// MinimalBuildRunWithSpecifiedServiceAccount defines a minimal BuildRun +// with a reference to a not existing serviceAccount +const MinimalBuildRunWithSpecifiedServiceAccount = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +spec: + build: + name: buildah + serviceAccount: foobar +` + +// MinimalBuildRunWithSAGeneration defines a minimal BuildRun +// with a reference to a not existing Build +const MinimalBuildRunWithSAGeneration = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +spec: + serviceAccount: ".generate" + build: + name: foobar +` + +// MinimalBuildRunWithTimeOut defines a BuildRun with +// an override for the Build Timeout +const MinimalBuildRunWithTimeOut = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +spec: + timeout: 1s + build: + name: foobar +` + +// MinimalBuildRunWithOutput defines a BuildRun with +// an override for the Build Output +const MinimalBuildRunWithOutput = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +spec: + output: + image: foobar.registry.com + build: + name: foobar +` + +// MinimalBuildRunRetention defines a minimal BuildRun +// with a reference used to test retention fields +const MinimalBuildRunRetention = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +metadata: + name: buidrun-retention-ttl +spec: + build: + name: build-retention-ttl +` + +// MinimalBuildRunRetention defines a minimal BuildRun +// with a reference used to test retention fields +const MinimalBuildRunRetentionTTLFive = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +metadata: + name: buidrun-retention-ttl +spec: + build: + name: build-retention-ttl + retention: + ttlAfterFailed: 5s + ttlAfterSucceeded: 5s +` + +const MinimalBuildahBuildRunWithExitCode = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildRun +metadata: + name: buildah-run +spec: + paramValues: + - name: exit-command + value: "true" + build: + name: buildah +` diff --git a/test/v1beta1_samples/buildstrategy_samples.go b/test/v1beta1_samples/buildstrategy_samples.go new file mode 100644 index 0000000000..fcf19c81f9 --- /dev/null +++ b/test/v1beta1_samples/buildstrategy_samples.go @@ -0,0 +1,369 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package testbeta + +// MinimalBuildahBuildStrategy defines a +// BuildStrategy for Buildah with two steps +// each of them with different container resources +const MinimalBuildahBuildStrategy = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildStrategy +metadata: + name: buildah +spec: + volumes: + - name: buildah-images + emptyDir: {} + steps: + - name: buildah-bud + image: quay.io/containers/buildah:v1.31.0 + workingDir: $(params.shp-source-root) + securityContext: + privileged: true + command: + - /usr/bin/buildah + args: + - bud + - --tag=$(params.shp-output-image) + - --file=$(build.dockerfile) + - $(params.shp-source-context) + resources: + limits: + cpu: 500m + memory: 1Gi + requests: + cpu: 500m + memory: 1Gi + volumeMounts: + - name: buildah-images + mountPath: /var/lib/containers/storage + - name: buildah-push + image: quay.io/containers/buildah:v1.31.0 + securityContext: + privileged: true + command: + - /usr/bin/buildah + args: + - push + - --tls-verify=false + - docker://$(params.shp-output-image) + resources: + limits: + cpu: 100m + memory: 65Mi + requests: + cpu: 100m + memory: 65Mi + volumeMounts: + - name: buildah-images + mountPath: /var/lib/containers/storage +` + +// MinimalBuildahBuildStrategyWithEnvs defines a +// BuildStrategy for Buildah with two steps +// each of them with different container resources +// and env vars +const MinimalBuildahBuildStrategyWithEnvs = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildStrategy +metadata: + name: buildah +spec: + volumes: + - name: buildah-images + emptyDir: {} + steps: + - name: buildah-bud + image: quay.io/containers/buildah:v1.31.0 + workingDir: $(params.shp-source-root) + securityContext: + privileged: true + command: + - /usr/bin/buildah + args: + - bud + - --tag=$(params.shp-output-image) + - --file=$(build.dockerfile) + - $(params.shp-source-context) + resources: + limits: + cpu: 500m + memory: 1Gi + requests: + cpu: 500m + memory: 1Gi + volumeMounts: + - name: buildah-images + mountPath: /var/lib/containers/storage + env: + - name: MY_VAR_1 + value: "my-var-1-buildstrategy-value" + - name: MY_VAR_2 + valueFrom: + fieldRef: + fieldPath: "my-fieldpath" + - name: buildah-push + image: quay.io/containers/buildah:v1.31.0 + securityContext: + privileged: true + command: + - /usr/bin/buildah + args: + - push + - --tls-verify=false + - docker://$(params.shp-output-image) + resources: + limits: + cpu: 100m + memory: 65Mi + requests: + cpu: 100m + memory: 65Mi + volumeMounts: + - name: buildah-images + mountPath: /var/lib/containers/storage +` + +// BuildahBuildStrategySingleStep defines a +// BuildStrategy for Buildah with a single step +// and container resources +const BuildahBuildStrategySingleStep = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildStrategy +metadata: + annotations: + kubernetes.io/ingress-bandwidth: 1M + clusterbuildstrategy.shipwright.io/dummy: aValue + kubectl.kubernetes.io/last-applied-configuration: anotherValue + kubernetes.io/egress-bandwidth: 1M + name: buildah +spec: + volumes: + - name: varlibcontainers + emptyDir: {} + steps: + - name: build + image: "$(build.builder.image)" + workingDir: $(params.shp-source-root) + command: + - buildah + - bud + - --tls-verify=false + - --layers + - -f + - $(build.dockerfile) + - -t + - $(params.shp-output-image) + - $(params.shp-source-context) + resources: + limits: + cpu: 500m + memory: 2Gi + requests: + cpu: 500m + memory: 2Gi + volumeMounts: + - name: varlibcontainers + mountPath: /var/lib/containers +` + +// BuildpacksBuildStrategySingleStep defines a +// BuildStrategy for Buildpacks with a single step +// and container resources +const BuildpacksBuildStrategySingleStep = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildStrategy +metadata: + name: buildpacks-v3 +spec: + volumes: + - name: varlibcontainers + emptyDir: {} + steps: + - name: build + image: "$(build.builder.image)" + workingDir: $(params.shp-source-root) + command: + - /cnb/lifecycle/builder + - -app + - $(params.shp-source-context) + - -layers + - /layers + - -group + - /layers/group.toml + - plan + - /layers/plan.toml + resources: + limits: + cpu: 500m + memory: 2Gi + requests: + cpu: 500m + memory: 2Gi + volumeMounts: + - name: varlibcontainers + mountPath: /var/lib/containers +` + +// BuildStrategyWithParameters is a strategy that uses a +// sleep command with a value for its spec.parameters +const BuildStrategyWithParameters = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildStrategy +metadata: + name: strategy-with-param +spec: + parameters: + - name: sleep-time + description: "time in seconds for sleeping" + default: "1" + - name: array-param + description: "An arbitrary array" + type: array + defaults: [] + steps: + - name: sleep30 + image: alpine:latest + command: + - sleep + args: + - $(params.sleep-time) + - name: echo-array-sum + image: alpine:latest + command: + - /bin/bash + args: + - -c + - | + set -euo pipefail + + sum=0 + + for var in "$@" + do + sum=$((sum+var)) + done + + echo "Sum: ${sum}" + - -- + - $(params.array-param[*]) +` + +// BuildStrategyWithoutDefaultInParameter is a strategy that uses a +// sleep command with a value from its spec.parameters, where the parameter +// have no default +const BuildStrategyWithoutDefaultInParameter = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildStrategy +metadata: + name: strategy-with-param-and-no-default +spec: + parameters: + - name: sleep-time + description: "time in seconds for sleeping" + steps: + - name: sleep30 + image: alpine:latest + command: + - sleep + args: + - $(params.sleep-time) +` + +// BuildStrategyWithErrorResult is a strategy that always fails +// and surfaces and error reason and message to the user +const BuildStrategyWithErrorResult = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildStrategy +metadata: + name: strategy-with-error-results +spec: + steps: + - name: fail-with-error-result + image: alpine:latest + command: + - sh + args: + - -c + - | + printf "integration test error reason" > $(results.shp-error-reason.path); + printf "integration test error message" > $(results.shp-error-message.path); + return 1 +` + +// BuildStrategyWithParameterVerification is a strategy that verifies that parameters can be used at all expected places +const BuildStrategyWithParameterVerification = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildStrategy +metadata: + name: strategy-with-parameter-verification +spec: + parameters: + - name: env1 + description: "This parameter will be used in the env of the build step" + type: string + - name: env2 + description: "This parameter will be used in the env of the build step" + type: string + - name: env3 + description: "This parameter will be used in the env of the build step" + type: string + - name: image + description: "This parameter will be used as the image of the build step" + type: string + - name: commands + description: "This parameter will be used as the command of the build step" + type: array + - name: args + description: "This parameter will be used as the args of the build step" + type: array + steps: + - name: calculate-sum + image: $(params.image) + env: + - name: ENV_FROM_PARAMETER1 + value: $(params.env1) + - name: ENV_FROM_PARAMETER2 + value: $(params.env2) + - name: ENV_FROM_PARAMETER3 + value: $(params.env3) + command: + - $(params.commands[*]) + args: + - | + set -euo pipefail + + sum=$((ENV_FROM_PARAMETER1 + ENV_FROM_PARAMETER2 + ENV_FROM_PARAMETER3)) + + for var in "$@" + do + sum=$((sum+var)) + done + + echo "Sum: ${sum}" + # Once we have strategy-defined results, then those would be better suitable + # Until then, just store it as image size :-) + echo -n "${sum}" > '$(results.shp-image-size.path)' + - -- + - $(params.args[*]) +` + +// BuildStrategyWithoutPush is a strategy that writes an image tarball and pushes nothing +const BuildStrategyWithoutPush = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildStrategy +metadata: + name: strategy-without-push +spec: + steps: + - name: store-tarball + image: gcr.io/go-containerregistry/crane:v0.16.1 + command: + - crane + args: + - export + - busybox + - $(params.shp-output-directory)/image.tar +` diff --git a/test/v1beta1_samples/catalog.go b/test/v1beta1_samples/catalog.go new file mode 100644 index 0000000000..df2f24186d --- /dev/null +++ b/test/v1beta1_samples/catalog.go @@ -0,0 +1,1093 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package testbeta + +import ( + "context" + "fmt" + "strconv" + "time" + + . "github.com/onsi/gomega" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + "knative.dev/pkg/apis" + knativev1 "knative.dev/pkg/apis/duck/v1" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + utils "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" + + build "github.com/shipwright-io/build/pkg/apis/build/v1beta1" +) + +// Catalog allows you to access helper functions +type Catalog struct{} + +// SecretWithAnnotation gives you a secret with build annotation +func (c *Catalog) SecretWithAnnotation(name string, ns string) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + Annotations: map[string]string{build.AnnotationBuildRefSecret: "true"}, + }, + } +} + +// SecretWithoutAnnotation gives you a secret without build annotation +func (c *Catalog) SecretWithoutAnnotation(name string, ns string) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + } +} + +// SecretWithStringData creates a Secret with stringData (not base64 encoded) +func (c *Catalog) SecretWithStringData(name string, ns string, stringData map[string]string) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + StringData: stringData, + } +} + +// SecretWithDockerConfigJson creates a secret of type dockerconfigjson +func (c *Catalog) SecretWithDockerConfigJson(name string, ns string, host string, username string, password string) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Type: corev1.SecretTypeDockerConfigJson, + StringData: map[string]string{ + ".dockerconfigjson": fmt.Sprintf("{\"auths\":{%q:{\"username\":%q,\"password\":%q}}}", host, username, password), + }, + } +} + +// ConfigMapWithData creates a ConfigMap with data +func (c *Catalog) ConfigMapWithData(name string, ns string, data map[string]string) *corev1.ConfigMap { + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Data: data, + } +} + +// BuildWithClusterBuildStrategyAndFalseSourceAnnotation gives you an specific Build CRD +func (c *Catalog) BuildWithClusterBuildStrategyAndFalseSourceAnnotation(name string, ns string, strategyName string) *build.Build { + buildStrategy := build.ClusterBuildStrategyKind + return &build.Build{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + Annotations: map[string]string{build.AnnotationBuildVerifyRepository: "false"}, + }, + Spec: build.BuildSpec{ + Source: build.Source{ + GitSource: &build.Git{ + URL: pointer.String("foobar"), + }, + }, + Strategy: build.Strategy{ + Name: strategyName, + Kind: &buildStrategy, + }, + Output: build.Image{ + Image: "foobar", + }, + }, + } +} + +// BuildWithClusterBuildStrategy gives you an specific Build CRD +func (c *Catalog) BuildWithClusterBuildStrategy(name string, ns string, strategyName string, secretName string) *build.Build { + buildStrategy := build.ClusterBuildStrategyKind + return &build.Build{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Spec: build.BuildSpec{ + Source: build.Source{ + GitSource: &build.Git{ + URL: pointer.String("https://github.com/shipwright-io/sample-go"), + }, + }, + Strategy: build.Strategy{ + Name: strategyName, + Kind: &buildStrategy, + }, + Output: build.Image{ + Image: "foobar", + PushSecret: &secretName, + }, + }, + } +} + +// BuildWithClusterBuildStrategyAndSourceSecret gives you an specific Build CRD +func (c *Catalog) BuildWithClusterBuildStrategyAndSourceSecret(name string, ns string, strategyName string) *build.Build { + buildStrategy := build.ClusterBuildStrategyKind + secret := "foobar" + return &build.Build{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Spec: build.BuildSpec{ + Source: build.Source{ + GitSource: &build.Git{ + URL: pointer.String("https://github.com/shipwright-io/sample-go"), + CloneSecret: &secret, + }, + }, + Strategy: build.Strategy{ + Name: strategyName, + Kind: &buildStrategy, + }, + Output: build.Image{ + Image: "foobar", + }, + }, + } +} + +// BuildWithBuildStrategy gives you an specific Build CRD +func (c *Catalog) BuildWithBuildStrategy(name string, ns string, strategyName string) *build.Build { + buildStrategy := build.NamespacedBuildStrategyKind + return &build.Build{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Spec: build.BuildSpec{ + Source: build.Source{ + GitSource: &build.Git{ + URL: pointer.String("https://github.com/shipwright-io/sample-go"), + }, + }, + Strategy: build.Strategy{ + Name: strategyName, + Kind: &buildStrategy, + }, + }, + } +} + +// BuildWithNilBuildStrategyKind gives you an Build CRD with nil build strategy kind +func (c *Catalog) BuildWithNilBuildStrategyKind(name string, ns string, strategyName string) *build.Build { + return &build.Build{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Spec: build.BuildSpec{ + Source: build.Source{ + GitSource: &build.Git{ + URL: pointer.String("https://github.com/shipwright-io/sample-go"), + }, + }, + Strategy: build.Strategy{ + Name: strategyName, + }, + }, + } +} + +// BuildWithOutputSecret .... +func (c *Catalog) BuildWithOutputSecret(name string, ns string, secretName string) *build.Build { + return &build.Build{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Spec: build.BuildSpec{ + Source: build.Source{ + GitSource: &build.Git{ + URL: pointer.String("https://github.com/shipwright-io/sample-go"), + }, + }, + Output: build.Image{ + PushSecret: &secretName, + }, + }, + } +} + +// ClusterBuildStrategy to support tests +func (c *Catalog) ClusterBuildStrategy(name string) *build.ClusterBuildStrategy { + return &build.ClusterBuildStrategy{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } +} + +// FakeClusterBuildStrategyNotFound returns a not found error +func (c *Catalog) FakeClusterBuildStrategyNotFound(name string) error { + return errors.NewNotFound(schema.GroupResource{}, name) +} + +// StubFunc is used to simulate the status of the Build +// after a .Status().Update() call in the controller. This +// receives a parameter to return an specific status state +func (c *Catalog) StubFunc(status corev1.ConditionStatus, reason build.BuildReason, message string) func(context context.Context, object client.Object, _ ...client.UpdateOption) error { + return func(context context.Context, object client.Object, _ ...client.UpdateOption) error { + switch object := object.(type) { + case *build.Build: + Expect(*object.Status.Registered).To(Equal(status)) + Expect(*object.Status.Reason).To(Equal(reason)) + Expect(*object.Status.Message).To(Equal(message)) + } + return nil + } +} + +// StubBuildUpdateOwnerReferences simulates and assert an updated +// BuildRun object ownerreferences +func (c *Catalog) StubBuildUpdateOwnerReferences(ownerKind string, ownerName string, isOwnerController *bool, blockOwnerDeletion *bool) func(context context.Context, object client.Object, _ ...client.UpdateOption) error { + return func(context context.Context, object client.Object, _ ...client.UpdateOption) error { + switch object := object.(type) { + case *build.BuildRun: + Expect(object.OwnerReferences[0].Kind).To(Equal(ownerKind)) + Expect(object.OwnerReferences[0].Name).To(Equal(ownerName)) + Expect(object.OwnerReferences[0].Controller).To(Equal(isOwnerController)) + Expect(object.OwnerReferences[0].BlockOwnerDeletion).To(Equal(blockOwnerDeletion)) + Expect(len(object.OwnerReferences)).ToNot(Equal(0)) + } + return nil + } +} + +// StubBuildRun is used to simulate the existence of a BuildRun +// only when there is a client GET on this object type +func (c *Catalog) StubBuildRun( + b *build.BuildRun, +) func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + return func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + switch object := object.(type) { + case *build.BuildRun: + b.DeepCopyInto(object) + return nil + } + return errors.NewNotFound(schema.GroupResource{}, nn.Name) + } +} + +// StubBuildRunAndTaskRun is used to simulate the existence of a BuildRun +// and a TaskRun when there is a client GET on this two objects +func (c *Catalog) StubBuildRunAndTaskRun( + b *build.BuildRun, + tr *v1beta1.TaskRun, +) func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + return func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + switch object := object.(type) { + case *build.BuildRun: + b.DeepCopyInto(object) + return nil + case *v1beta1.TaskRun: + tr.DeepCopyInto(object) + return nil + } + return errors.NewNotFound(schema.GroupResource{}, nn.Name) + } +} + +// StubBuildAndTaskRun is used to simulate the existence of a Build +// and a TaskRun when there is a client GET on this two objects +func (c *Catalog) StubBuildAndTaskRun( + b *build.Build, + tr *v1beta1.TaskRun, +) func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + return func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + switch object := object.(type) { + case *build.Build: + b.DeepCopyInto(object) + return nil + case *v1beta1.TaskRun: + tr.DeepCopyInto(object) + return nil + } + return errors.NewNotFound(schema.GroupResource{}, nn.Name) + } +} + +// StubBuildStatusReason asserts Status fields on a Build resource +func (c *Catalog) StubBuildStatusReason(reason build.BuildReason, message string) func(context context.Context, object client.Object, _ ...client.UpdateOption) error { + return func(context context.Context, object client.Object, _ ...client.UpdateOption) error { + switch object := object.(type) { + case *build.Build: + if object.Status.Message != nil && *object.Status.Message != "" { + Expect(*object.Status.Message).To(Equal(message)) + } + if object.Status.Reason != nil && *object.Status.Reason != "" { + Expect(*object.Status.Reason).To(Equal(reason)) + } + } + return nil + } +} + +// StubBuildRunStatus asserts Status fields on a BuildRun resource +func (c *Catalog) StubBuildRunStatus(reason string, name *string, condition build.Condition, status corev1.ConditionStatus, buildSpec build.BuildSpec, tolerateEmptyStatus bool) func(context context.Context, object client.Object, _ ...client.UpdateOption) error { + return func(context context.Context, object client.Object, _ ...client.UpdateOption) error { + switch object := object.(type) { + case *build.BuildRun: + if !tolerateEmptyStatus { + Expect(object.Status.GetCondition(build.Succeeded).Status).To(Equal(condition.Status)) + Expect(object.Status.GetCondition(build.Succeeded).Reason).To(Equal(condition.Reason)) + Expect(object.Status.TaskRunName).To(Equal(name)) + } + if object.Status.BuildSpec != nil { + Expect(*object.Status.BuildSpec).To(Equal(buildSpec)) + } + } + return nil + } +} + +// StubBuildRunLabel asserts Label fields on a BuildRun resource +func (c *Catalog) StubBuildRunLabel(buildSample *build.Build) func(context context.Context, object client.Object, _ ...client.UpdateOption) error { + return func(context context.Context, object client.Object, _ ...client.UpdateOption) error { + switch object := object.(type) { + case *build.BuildRun: + Expect(object.Labels[build.LabelBuild]).To(Equal(buildSample.Name)) + Expect(object.Labels[build.LabelBuildGeneration]).To(Equal(strconv.FormatInt(buildSample.Generation, 10))) + } + return nil + } +} + +// StubBuildRunGetWithoutSA simulates the output of client GET calls +// for the BuildRun unit tests +func (c *Catalog) StubBuildRunGetWithoutSA( + b *build.Build, + br *build.BuildRun, +) func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + return func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + switch object := object.(type) { + case *build.Build: + b.DeepCopyInto(object) + return nil + case *build.BuildRun: + br.DeepCopyInto(object) + return nil + } + return errors.NewNotFound(schema.GroupResource{}, nn.Name) + } +} + +// StubBuildRunGetWithTaskRunAndSA returns fake object for different +// client calls +func (c *Catalog) StubBuildRunGetWithTaskRunAndSA( + b *build.Build, + br *build.BuildRun, + tr *v1beta1.TaskRun, + sa *corev1.ServiceAccount, +) func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + return func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + switch object := object.(type) { + case *build.Build: + b.DeepCopyInto(object) + return nil + case *build.BuildRun: + br.DeepCopyInto(object) + return nil + case *v1beta1.TaskRun: + tr.DeepCopyInto(object) + return nil + case *corev1.ServiceAccount: + sa.DeepCopyInto(object) + return nil + } + return errors.NewNotFound(schema.GroupResource{}, nn.Name) + } +} + +// StubBuildRunGetWithSA returns fake object for different +// client calls +func (c *Catalog) StubBuildRunGetWithSA( + b *build.Build, + br *build.BuildRun, + sa *corev1.ServiceAccount, +) func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + return func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + switch object := object.(type) { + case *build.Build: + b.DeepCopyInto(object) + return nil + case *build.BuildRun: + br.DeepCopyInto(object) + return nil + case *corev1.ServiceAccount: + sa.DeepCopyInto(object) + return nil + } + return errors.NewNotFound(schema.GroupResource{}, nn.Name) + } +} + +// StubBuildRunGetWithSAandStrategies simulates the output of client GET +// calls for the BuildRun unit tests +func (c *Catalog) StubBuildRunGetWithSAandStrategies( + b *build.Build, + br *build.BuildRun, + sa *corev1.ServiceAccount, + cb *build.ClusterBuildStrategy, + bs *build.BuildStrategy, +) func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + return func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + switch object := object.(type) { + case *build.Build: + if b != nil { + b.DeepCopyInto(object) + return nil + } + case *build.BuildRun: + if br != nil { + br.DeepCopyInto(object) + return nil + } + case *corev1.ServiceAccount: + if sa != nil { + sa.DeepCopyInto(object) + return nil + } + case *build.ClusterBuildStrategy: + if cb != nil { + cb.DeepCopyInto(object) + return nil + } + case *build.BuildStrategy: + if bs != nil { + bs.DeepCopyInto(object) + return nil + } + } + return errors.NewNotFound(schema.GroupResource{}, nn.Name) + } +} + +func (c *Catalog) StubBuildCRDs( + b *build.Build, + br *build.BuildRun, + sa *corev1.ServiceAccount, + cb *build.ClusterBuildStrategy, + bs *build.BuildStrategy, +) func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + return func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + switch object := object.(type) { + case *build.Build: + b.DeepCopyInto(object) + return nil + case *build.BuildRun: + br.DeepCopyInto(object) + return nil + case *corev1.ServiceAccount: + sa.DeepCopyInto(object) + return nil + case *build.ClusterBuildStrategy: + cb.DeepCopyInto(object) + return nil + case *build.BuildStrategy: + bs.DeepCopyInto(object) + return nil + } + return errors.NewNotFound(schema.GroupResource{}, nn.Name) + } +} + +// StubBuildCRDsPodAndTaskRun stubs different objects in case a client +// GET call is executed against them +func (c *Catalog) StubBuildCRDsPodAndTaskRun( + b *build.Build, + br *build.BuildRun, + sa *corev1.ServiceAccount, + cb *build.ClusterBuildStrategy, + bs *build.BuildStrategy, + tr *v1beta1.TaskRun, + pod *corev1.Pod, +) func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + return func(context context.Context, nn types.NamespacedName, object client.Object, getOptions ...client.GetOption) error { + switch object := object.(type) { + case *build.Build: + b.DeepCopyInto(object) + return nil + case *build.BuildRun: + br.DeepCopyInto(object) + return nil + case *corev1.ServiceAccount: + sa.DeepCopyInto(object) + return nil + case *build.ClusterBuildStrategy: + cb.DeepCopyInto(object) + return nil + case *build.BuildStrategy: + bs.DeepCopyInto(object) + return nil + case *v1beta1.TaskRun: + tr.DeepCopyInto(object) + return nil + case *corev1.Pod: + pod.DeepCopyInto(object) + return nil + } + return errors.NewNotFound(schema.GroupResource{}, nn.Name) + } +} + +// TaskRunWithStatus returns a minimal tekton TaskRun with an Status +func (c *Catalog) TaskRunWithStatus(trName string, ns string) *v1beta1.TaskRun { + return &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: trName, + Namespace: ns, + }, + Spec: v1beta1.TaskRunSpec{ + Timeout: &metav1.Duration{ + Duration: time.Minute * 2, + }, + }, + Status: v1beta1.TaskRunStatus{ + Status: knativev1.Status{ + Conditions: knativev1.Conditions{ + { + Type: apis.ConditionSucceeded, + Reason: "Unknown", + Status: corev1.ConditionUnknown, + }, + }, + }, + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + PodName: "foobar-pod", + StartTime: &metav1.Time{ + Time: time.Now(), + }, + CompletionTime: &metav1.Time{ + Time: time.Now(), + }, + }, + }, + } +} + +// DefaultTaskRunWithStatus returns a minimal tekton TaskRun with an Status +func (c *Catalog) DefaultTaskRunWithStatus(trName string, buildRunName string, ns string, status corev1.ConditionStatus, reason string) *v1beta1.TaskRun { + return &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: trName, + Namespace: ns, + Labels: map[string]string{"buildrun.shipwright.io/name": buildRunName}, + }, + Spec: v1beta1.TaskRunSpec{}, + Status: v1beta1.TaskRunStatus{ + Status: knativev1.Status{ + Conditions: knativev1.Conditions{ + { + Type: apis.ConditionSucceeded, + Reason: reason, + Status: status, + }, + }, + }, + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + StartTime: &metav1.Time{ + Time: time.Now(), + }, + }, + }, + } +} + +// TaskRunWithCompletionAndStartTime provides a TaskRun object with a +// Completion and StartTime +func (c *Catalog) TaskRunWithCompletionAndStartTime(trName string, buildRunName string, ns string) *v1beta1.TaskRun { + return &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: trName, + Namespace: ns, + Labels: map[string]string{"buildrun.shipwright.io/name": buildRunName}, + }, + Spec: v1beta1.TaskRunSpec{}, + Status: v1beta1.TaskRunStatus{ + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + CompletionTime: &metav1.Time{ + Time: time.Now(), + }, + StartTime: &metav1.Time{ + Time: time.Now(), + }, + PodName: "foobar", + }, + Status: knativev1.Status{ + Conditions: knativev1.Conditions{ + { + Type: apis.ConditionSucceeded, + Reason: "something bad happened", + Status: corev1.ConditionFalse, + Message: "some message", + }, + }, + }, + }, + } +} + +// DefaultTaskRunWithFalseStatus returns a minimal tektont TaskRun with a FALSE status +func (c *Catalog) DefaultTaskRunWithFalseStatus(trName string, buildRunName string, ns string) *v1beta1.TaskRun { + return &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: trName, + Namespace: ns, + Labels: map[string]string{"buildrun.shipwright.io/name": buildRunName}, + }, + Spec: v1beta1.TaskRunSpec{}, + Status: v1beta1.TaskRunStatus{ + Status: knativev1.Status{ + Conditions: knativev1.Conditions{ + { + Type: apis.ConditionSucceeded, + Reason: "something bad happened", + Status: corev1.ConditionFalse, + Message: "some message", + }, + }, + }, + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + StartTime: &metav1.Time{ + Time: time.Now(), + }, + }, + }, + } +} + +// DefaultBuild returns a minimal Build object +func (c *Catalog) DefaultBuild(buildName string, strategyName string, strategyKind build.BuildStrategyKind) *build.Build { + return &build.Build{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildName, + }, + Spec: build.BuildSpec{ + Strategy: build.Strategy{ + Name: strategyName, + Kind: &strategyKind, + }, + }, + Status: build.BuildStatus{ + Registered: build.ConditionStatusPtr(corev1.ConditionTrue), + }, + } +} + +// BuildWithoutStrategyKind returns a minimal Build object without an strategy kind definition +func (c *Catalog) BuildWithoutStrategyKind(buildName string, strategyName string) *build.Build { + return &build.Build{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildName, + }, + Spec: build.BuildSpec{ + Strategy: build.Strategy{ + Name: strategyName, + }, + }, + Status: build.BuildStatus{ + Registered: build.ConditionStatusPtr(corev1.ConditionTrue), + }, + } +} + +// BuildWithBuildRunDeletions returns a minimal Build object with the +// build.shipwright.io/build-run-deletion annotation set to true +func (c *Catalog) BuildWithBuildRunDeletions(buildName string, strategyName string, strategyKind build.BuildStrategyKind) *build.Build { + return &build.Build{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildName, + }, + Spec: build.BuildSpec{ + Strategy: build.Strategy{ + Name: strategyName, + Kind: &strategyKind, + }, + Retention: &build.BuildRetention{ + AtBuildDeletion: pointer.Bool(true), + }, + }, + Status: build.BuildStatus{ + Registered: build.ConditionStatusPtr(corev1.ConditionTrue), + }, + } +} + +// BuildWithBuildRunDeletionsAndFakeNS returns a minimal Build object with the +// build.shipwright.io/build-run-deletion annotation set to true in a fake namespace +func (c *Catalog) BuildWithBuildRunDeletionsAndFakeNS(buildName string, strategyName string, strategyKind build.BuildStrategyKind) *build.Build { + return &build.Build{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildName, + Namespace: "fakens", + }, + Spec: build.BuildSpec{ + Strategy: build.Strategy{ + Name: strategyName, + Kind: &strategyKind, + }, + Retention: &build.BuildRetention{ + AtBuildDeletion: pointer.Bool(true), + }, + }, + Status: build.BuildStatus{ + Registered: build.ConditionStatusPtr(corev1.ConditionTrue), + }, + } +} + +// DefaultBuildWithFalseRegistered returns a minimal Build object with a FALSE Registered +func (c *Catalog) DefaultBuildWithFalseRegistered(buildName string, strategyName string, strategyKind build.BuildStrategyKind) *build.Build { + return &build.Build{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildName, + }, + Spec: build.BuildSpec{ + Strategy: build.Strategy{ + Name: strategyName, + Kind: &strategyKind, + }, + }, + Status: build.BuildStatus{ + Registered: build.ConditionStatusPtr(corev1.ConditionFalse), + Reason: build.BuildReasonPtr("something bad happened"), + }, + } +} + +// DefaultBuildRun returns a minimal BuildRun object +func (c *Catalog) DefaultBuildRun(buildRunName string, buildName string) *build.BuildRun { + var defaultBuild = c.DefaultBuild(buildName, "foobar-strategy", build.ClusterBuildStrategyKind) + return &build.BuildRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildRunName, + }, + Spec: build.BuildRunSpec{ + Build: &build.ReferencedBuild{ + Name: buildName, + }, + }, + Status: build.BuildRunStatus{ + BuildSpec: &defaultBuild.Spec, + }, + } +} + +// PodWithInitContainerStatus returns a pod with a single +// entry under the Status field for InitContainer Status +func (c *Catalog) PodWithInitContainerStatus(podName string, initContainerName string) *corev1.Pod { + return &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + }, + Status: corev1.PodStatus{ + InitContainerStatuses: []corev1.ContainerStatus{ + { + Name: initContainerName, + }, + }, + }, + } +} + +// BuildRunWithBuildSnapshot returns BuildRun Object with a populated +// BuildSpec in the Status field +func (c *Catalog) BuildRunWithBuildSnapshot(buildRunName string, buildName string) *build.BuildRun { + return &build.BuildRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildRunName, + CreationTimestamp: metav1.Time{ + Time: time.Now(), + }, + }, + Status: build.BuildRunStatus{ + BuildSpec: &build.BuildSpec{ + Strategy: build.Strategy{ + Name: "foobar", + }, + }, + }, + Spec: build.BuildRunSpec{ + Build: &build.ReferencedBuild{ + Name: buildName, + }, + }, + } +} + +// BuildRunWithExistingOwnerReferences returns a BuildRun object that is +// already owned by some fake object +func (c *Catalog) BuildRunWithExistingOwnerReferences(buildRunName string, buildName string, ownerName string) *build.BuildRun { + + managingController := true + + fakeOwnerRef := metav1.OwnerReference{ + APIVersion: ownerName, + Kind: ownerName, + Controller: &managingController, + } + + return &build.BuildRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildRunName, + OwnerReferences: []metav1.OwnerReference{fakeOwnerRef}, + }, + Spec: build.BuildRunSpec{ + Build: &build.ReferencedBuild{ + Name: buildName, + }, + }, + } +} + +// BuildRunWithFakeNamespace returns a BuildRun object with +// a namespace that does not exist +func (c *Catalog) BuildRunWithFakeNamespace(buildRunName string, buildName string) *build.BuildRun { + return &build.BuildRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildRunName, + Namespace: "foobarns", + }, + Spec: build.BuildRunSpec{ + Build: &build.ReferencedBuild{ + Name: buildName, + }, + }, + } +} + +// DefaultTaskRun returns a minimal TaskRun object +func (c *Catalog) DefaultTaskRun(taskRunName string, ns string) *v1beta1.TaskRun { + return &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: taskRunName, + Namespace: ns, + }, + } +} + +// DefaultServiceAccount returns a minimal SA object +func (c *Catalog) DefaultServiceAccount(name string) *corev1.ServiceAccount { + return &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } +} + +// ServiceAccountWithControllerRef ... TODO +func (c *Catalog) ServiceAccountWithControllerRef(name string) *corev1.ServiceAccount { + return &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + OwnerReferences: []metav1.OwnerReference{ + { + Name: "ss", + Kind: "BuildRun", + }, + }, + }, + } +} + +// DefaultClusterBuildStrategy returns a minimal ClusterBuildStrategy +// object with a inmutable name +func (c *Catalog) DefaultClusterBuildStrategy() *build.ClusterBuildStrategy { + return &build.ClusterBuildStrategy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar", + }, + } +} + +// DefaultNamespacedBuildStrategy returns a minimal BuildStrategy +// object with a inmutable name +func (c *Catalog) DefaultNamespacedBuildStrategy() *build.BuildStrategy { + return &build.BuildStrategy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar", + }, + } +} + +// BuildRunWithSucceededCondition returns a BuildRun with a single condition +// of the type Succeeded +func (c *Catalog) BuildRunWithSucceededCondition() *build.BuildRun { + return &build.BuildRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar", + }, + Status: build.BuildRunStatus{ + Conditions: build.Conditions{ + build.Condition{ + Type: build.Succeeded, + Reason: "foobar", + Message: "foo is not bar", + Status: corev1.ConditionUnknown, + }, + }, + }, + } +} + +// BuildRunWithSA returns a customized BuildRun object that defines a +// service account +func (c *Catalog) BuildRunWithSA(buildRunName string, buildName string, saName string) *build.BuildRun { + return &build.BuildRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildRunName, + }, + Spec: build.BuildRunSpec{ + Build: &build.ReferencedBuild{ + Name: buildName, + }, + ServiceAccount: &saName, + }, + Status: build.BuildRunStatus{}, + } +} + +// BuildRunWithoutSA returns a buildrun without serviceAccountName and generate serviceAccount is false +func (c *Catalog) BuildRunWithoutSA(buildRunName string, buildName string) *build.BuildRun { + return &build.BuildRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildRunName, + }, + Spec: build.BuildRunSpec{ + Build: &build.ReferencedBuild{ + Name: buildName, + }, + ServiceAccount: nil, + }, + } +} + +// BuildRunWithSAGenerate returns a customized BuildRun object that defines a +// service account +func (c *Catalog) BuildRunWithSAGenerate(buildRunName string, buildName string) *build.BuildRun { + return &build.BuildRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildRunName, + }, + Spec: build.BuildRunSpec{ + Build: &build.ReferencedBuild{ + Name: buildName, + }, + ServiceAccount: utils.StringPtr(".generate"), + }, + } +} + +// LoadCustomResources returns a container set of resources based on cpu and memory +func (c *Catalog) LoadCustomResources(cpu string, mem string) corev1.ResourceRequirements { + return corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(cpu), + corev1.ResourceMemory: resource.MustParse(mem), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(cpu), + corev1.ResourceMemory: resource.MustParse(mem), + }, + } +} + +// LoadBuildYAML parses YAML bytes into JSON and from JSON +// into a Build struct +func (c *Catalog) LoadBuildYAML(d []byte) (*build.Build, error) { + b := &build.Build{} + err := yaml.Unmarshal(d, b) + if err != nil { + return nil, err + } + return b, nil +} + +// LoadBuildWithNameAndStrategy returns a populated Build with name and a referenced strategy +func (c *Catalog) LoadBuildWithNameAndStrategy(name string, strategy string, d []byte) (*build.Build, error) { + b := &build.Build{} + err := yaml.Unmarshal(d, b) + if err != nil { + return nil, err + } + b.Name = name + b.Spec.Strategy.Name = strategy + return b, nil +} + +// LoadBuildRunFromBytes returns a populated BuildRun +func (c *Catalog) LoadBuildRunFromBytes(d []byte) (*build.BuildRun, error) { + b := &build.BuildRun{} + err := yaml.Unmarshal(d, b) + if err != nil { + return nil, err + } + return b, nil +} + +// LoadBRWithNameAndRef returns a populated BuildRun with a name and a referenced Build +func (c *Catalog) LoadBRWithNameAndRef(name string, buildName string, d []byte) (*build.BuildRun, error) { + b := &build.BuildRun{} + err := yaml.Unmarshal(d, b) + if err != nil { + return nil, err + } + b.Name = name + b.Spec.Build.Name = buildName + return b, nil +} + +func (c *Catalog) LoadStandAloneBuildRunWithNameAndStrategy(name string, strategy *build.ClusterBuildStrategy, d []byte) (*build.BuildRun, error) { + b := &build.BuildRun{} + err := yaml.Unmarshal(d, b) + if err != nil { + return nil, err + } + b.Name = name + b.Spec.Build.Build.Strategy = build.Strategy{Kind: (*build.BuildStrategyKind)(&strategy.Kind), Name: strategy.Name} + + return b, nil +} + +// LoadBuildStrategyFromBytes returns a populated BuildStrategy +func (c *Catalog) LoadBuildStrategyFromBytes(d []byte) (*build.BuildStrategy, error) { + b := &build.BuildStrategy{} + err := yaml.Unmarshal(d, b) + if err != nil { + return nil, err + } + return b, nil +} + +// LoadCBSWithName returns a populated ClusterBuildStrategy with a name +func (c *Catalog) LoadCBSWithName(name string, d []byte) (*build.ClusterBuildStrategy, error) { + b := &build.ClusterBuildStrategy{} + err := yaml.Unmarshal(d, b) + if err != nil { + return nil, err + } + b.Name = name + return b, nil +} diff --git a/test/v1beta1_samples/clusterbuildstrategy_samples.go b/test/v1beta1_samples/clusterbuildstrategy_samples.go new file mode 100644 index 0000000000..2f9a6a22f5 --- /dev/null +++ b/test/v1beta1_samples/clusterbuildstrategy_samples.go @@ -0,0 +1,358 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package testbeta + +// MinimalBuildahClusterBuildStrategy defines a +// BuildStrategy for Buildah with two steps +// each of them with different container resources +const MinimalBuildahClusterBuildStrategy = ` +apiVersion: shipwright.io/v1beta1 +kind: BuildStrategy +metadata: + name: buildah +spec: + volumes: + - name: buildah-images + volumeSource: + emptyDir: {} + buildSteps: + - name: buildah-bud + image: quay.io/containers/buildah:v1.31.0 + workingDir: $(params.shp-source-root) + securityContext: + privileged: true + command: + - /usr/bin/buildah + args: + - bud + - --tag=$(params.shp-output-image) + - --file=$(build.dockerfile) + - $(params.shp-source-context) + resources: + limits: + cpu: 500m + memory: 1Gi + requests: + cpu: 500m + memory: 1Gi + volumeMounts: + - name: buildah-images + mountPath: /var/lib/containers/storage + - name: buildah-push + image: quay.io/containers/buildah:v1.31.0 + securityContext: + privileged: true + command: + - /usr/bin/buildah + args: + - push + - --tls-verify=false + - docker://$(params.shp-output-image) + resources: + limits: + cpu: 100m + memory: 65Mi + requests: + cpu: 100m + memory: 65Mi + volumeMounts: + - name: buildah-images + mountPath: /var/lib/containers/storage +` + +// ClusterBuildStrategySingleStep defines a +// BuildStrategy for Buildah with a single step +// and container resources +const ClusterBuildStrategySingleStep = ` +apiVersion: shipwright.io/v1beta1 +kind: ClusterBuildStrategy +metadata: + name: buildah +spec: + volumes: + - name: buildah-images + volumeSource: + emptyDir: {} + buildSteps: + - name: buildah-bud + image: quay.io/containers/buildah:v1.31.0 + workingDir: $(params.shp-source-root) + securityContext: + privileged: true + command: + - /usr/bin/buildah + args: + - bud + - --tag=$(params.shp-output-image) + - --file=$(build.dockerfile) + - $(params.shp-source-context) + resources: + limits: + cpu: 500m + memory: 1Gi + requests: + cpu: 250m + memory: 65Mi + volumeMounts: + - name: buildah-images + mountPath: /var/lib/containers/storage + - name: buildah-push + image: quay.io/containers/buildah:v1.31.0 + securityContext: + privileged: true + command: + - /usr/bin/buildah + args: + - push + - --tls-verify=false + - docker://$(params.shp-output-image) + resources: + limits: + cpu: 500m + memory: 1Gi + requests: + cpu: 250m + memory: 65Mi + volumeMounts: + - name: buildah-images + mountPath: /var/lib/containers/storage +` + +// ClusterBuildStrategySingleStepKaniko is a cluster build strategy based on +// Kaniko, which is very close to the actual Kaniko build strategy example in +// the project +const ClusterBuildStrategySingleStepKaniko = ` +apiVersion: shipwright.io/v1beta1 +kind: ClusterBuildStrategy +metadata: + name: kaniko +spec: + buildSteps: + - name: step-build-and-push + image: gcr.io/kaniko-project/executor:v1.15.0 + workingDir: $(params.shp-source-root) + securityContext: + runAsUser: 0 + capabilities: + add: + - CHOWN + - DAC_OVERRIDE + - FOWNER + - SETGID + - SETUID + - SETFCAP + - KILL + env: + - name: DOCKER_CONFIG + value: /tekton/home/.docker + - name: AWS_ACCESS_KEY_ID + value: NOT_SET + - name: AWS_SECRET_KEY + value: NOT_SET + command: + - /kaniko/executor + args: + - --skip-tls-verify=true + - --dockerfile=$(build.dockerfile) + - --context=$(params.shp-source-context) + - --destination=$(params.shp-output-image) + - --snapshot-mode=redo + - --push-retry=3 + resources: + limits: + cpu: 500m + memory: 1Gi + requests: + cpu: 250m + memory: 65Mi +` + +// ClusterBuildStrategySingleStepKanikoError is a Kaniko based cluster build +// strategy that has a configuration error (misspelled command flag) so that +// it will fail in Tekton +const ClusterBuildStrategySingleStepKanikoError = ` +apiVersion: shipwright.io/v1beta1 +kind: ClusterBuildStrategy +metadata: + name: kaniko +spec: + buildSteps: + - name: step-build-and-push + image: gcr.io/kaniko-project/executor:v1.15.0 + workingDir: $(params.shp-source-root) + securityContext: + runAsUser: 0 + capabilities: + add: + - CHOWN + - DAC_OVERRIDE + - FOWNER + - SETGID + - SETUID + - SETFCAP + - KILL + env: + - name: DOCKER_CONFIG + value: /tekton/home/.docker + - name: AWS_ACCESS_KEY_ID + value: NOT_SET + - name: AWS_SECRET_KEY + value: NOT_SET + command: + - /kaniko/executor + args: + - --skips-tlss-verifys=true + - --dockerfile=$(build.dockerfile) + - --context=$(params.shp-source-context) + - --destination=$(params.shp-output-image) + - --snapshot-mode=redo + - --push-retry=3 + resources: + limits: + cpu: 500m + memory: 1Gi + requests: + cpu: 250m + memory: 65Mi +` + +// ClusterBuildStrategyNoOp is a strategy that does nothing and has no dependencies +const ClusterBuildStrategyNoOp = ` +apiVersion: shipwright.io/v1beta1 +kind: ClusterBuildStrategy +metadata: + name: noop +spec: + parameters: + - name: exit-command + description: "Exit command for the pod" + default: "true" + buildSteps: + - name: step-no-and-op + image: alpine:latest + workingDir: $(params.shp-source-root) + securityContext: + runAsUser: 0 + capabilities: + add: + - CHOWN + - DAC_OVERRIDE + - FOWNER + - SETGID + - SETUID + - SETFCAP + - KILL + env: + - name: DOCKER_CONFIG + value: /tekton/home/.docker + - name: AWS_ACCESS_KEY_ID + value: NOT_SET + - name: AWS_SECRET_KEY + value: NOT_SET + command: + - $(params.exit-command) + resources: + limits: + cpu: 250m + memory: 128Mi + requests: + cpu: 250m + memory: 128Mi +` + +// ClusterBuildStrategySleep30s is a strategy that does only sleep 30 seconds +const ClusterBuildStrategySleep30s = ` +apiVersion: build.dev/v1beta1 +kind: ClusterBuildStrategy +metadata: + name: noop +spec: + buildSteps: + - name: sleep30 + image: alpine:latest + command: + - sleep + args: + - "30s" + resources: + limits: + cpu: 250m + memory: 128Mi + requests: + cpu: 250m + memory: 128Mi +` + +// ClusterBuildStrategyWithAnnotations is a cluster build strategy that contains annotations +const ClusterBuildStrategyWithAnnotations = ` +apiVersion: shipwright.io/v1beta1 +kind: ClusterBuildStrategy +metadata: + annotations: + kubernetes.io/ingress-bandwidth: 1M + clusterbuildstrategy.shipwright.io/dummy: aValue + kubectl.kubernetes.io/last-applied-configuration: anotherValue + name: kaniko +spec: + buildSteps: + - name: step-build-and-push + image: gcr.io/kaniko-project/executor:v1.15.0 + workingDir: $(params.shp-source-root) + securityContext: + runAsUser: 0 + capabilities: + add: + - CHOWN + - DAC_OVERRIDE + - FOWNER + - SETGID + - SETUID + - SETFCAP + - KILL + env: + - name: DOCKER_CONFIG + value: /tekton/home/.docker + - name: AWS_ACCESS_KEY_ID + value: NOT_SET + - name: AWS_SECRET_KEY + value: NOT_SET + command: + - /kaniko/executor + args: + - --skip-tls-verify=true + - --dockerfile=$(build.dockerfile) + - --context=$(params.shp-source-root) + - --destination=$(params.shp-output-image) + - --snapshot-mode=redo + - --push-retry=3 + resources: + limits: + cpu: 500m + memory: 1Gi + requests: + cpu: 250m + memory: 65Mi +` + +// ClusterBuildStrategyWithParameters is a strategy that uses a +// sleep command with a value for its spec.parameters +const ClusterBuildStrategyWithParameters = ` +apiVersion: build.dev/v1beta1 +kind: ClusterBuildStrategy +metadata: + name: strategy-with-param +spec: + parameters: + - name: sleep-time + description: "time in seconds for sleeping" + default: "1" + buildSteps: + - name: sleep30 + image: alpine:latest + command: + - sleep + args: + - $(params.sleep-time) +` diff --git a/vendor/k8s.io/utils/net/ipfamily.go b/vendor/k8s.io/utils/net/ipfamily.go new file mode 100644 index 0000000000..1a51fa3918 --- /dev/null +++ b/vendor/k8s.io/utils/net/ipfamily.go @@ -0,0 +1,181 @@ +/* +Copyright 2018 The Kubernetes 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 net + +import ( + "fmt" + "net" +) + +// IPFamily refers to a specific family if not empty, i.e. "4" or "6". +type IPFamily string + +// Constants for valid IPFamilys: +const ( + IPFamilyUnknown IPFamily = "" + + IPv4 IPFamily = "4" + IPv6 IPFamily = "6" +) + +// IsDualStackIPs returns true if: +// - all elements of ips are valid +// - at least one IP from each family (v4 and v6) is present +func IsDualStackIPs(ips []net.IP) (bool, error) { + v4Found := false + v6Found := false + for i, ip := range ips { + switch IPFamilyOf(ip) { + case IPv4: + v4Found = true + case IPv6: + v6Found = true + default: + return false, fmt.Errorf("invalid IP[%d]: %v", i, ip) + } + } + + return (v4Found && v6Found), nil +} + +// IsDualStackIPStrings returns true if: +// - all elements of ips can be parsed as IPs +// - at least one IP from each family (v4 and v6) is present +func IsDualStackIPStrings(ips []string) (bool, error) { + parsedIPs := make([]net.IP, 0, len(ips)) + for i, ip := range ips { + parsedIP := ParseIPSloppy(ip) + if parsedIP == nil { + return false, fmt.Errorf("invalid IP[%d]: %v", i, ip) + } + parsedIPs = append(parsedIPs, parsedIP) + } + return IsDualStackIPs(parsedIPs) +} + +// IsDualStackCIDRs returns true if: +// - all elements of cidrs are non-nil +// - at least one CIDR from each family (v4 and v6) is present +func IsDualStackCIDRs(cidrs []*net.IPNet) (bool, error) { + v4Found := false + v6Found := false + for i, cidr := range cidrs { + switch IPFamilyOfCIDR(cidr) { + case IPv4: + v4Found = true + case IPv6: + v6Found = true + default: + return false, fmt.Errorf("invalid CIDR[%d]: %v", i, cidr) + } + } + + return (v4Found && v6Found), nil +} + +// IsDualStackCIDRStrings returns if +// - all elements of cidrs can be parsed as CIDRs +// - at least one CIDR from each family (v4 and v6) is present +func IsDualStackCIDRStrings(cidrs []string) (bool, error) { + parsedCIDRs, err := ParseCIDRs(cidrs) + if err != nil { + return false, err + } + return IsDualStackCIDRs(parsedCIDRs) +} + +// IPFamilyOf returns the IP family of ip, or IPFamilyUnknown if it is invalid. +func IPFamilyOf(ip net.IP) IPFamily { + switch { + case ip.To4() != nil: + return IPv4 + case ip.To16() != nil: + return IPv6 + default: + return IPFamilyUnknown + } +} + +// IPFamilyOfString returns the IP family of ip, or IPFamilyUnknown if ip cannot +// be parsed as an IP. +func IPFamilyOfString(ip string) IPFamily { + return IPFamilyOf(ParseIPSloppy(ip)) +} + +// IPFamilyOfCIDR returns the IP family of cidr. +func IPFamilyOfCIDR(cidr *net.IPNet) IPFamily { + if cidr == nil { + return IPFamilyUnknown + } + return IPFamilyOf(cidr.IP) +} + +// IPFamilyOfCIDRString returns the IP family of cidr. +func IPFamilyOfCIDRString(cidr string) IPFamily { + ip, _, _ := ParseCIDRSloppy(cidr) + return IPFamilyOf(ip) +} + +// IsIPv6 returns true if netIP is IPv6 (and false if it is IPv4, nil, or invalid). +func IsIPv6(netIP net.IP) bool { + return IPFamilyOf(netIP) == IPv6 +} + +// IsIPv6String returns true if ip contains a single IPv6 address and nothing else. It +// returns false if ip is an empty string, an IPv4 address, or anything else that is not a +// single IPv6 address. +func IsIPv6String(ip string) bool { + return IPFamilyOfString(ip) == IPv6 +} + +// IsIPv6CIDR returns true if a cidr is a valid IPv6 CIDR. It returns false if cidr is +// nil or an IPv4 CIDR. Its behavior is not defined if cidr is invalid. +func IsIPv6CIDR(cidr *net.IPNet) bool { + return IPFamilyOfCIDR(cidr) == IPv6 +} + +// IsIPv6CIDRString returns true if cidr contains a single IPv6 CIDR and nothing else. It +// returns false if cidr is an empty string, an IPv4 CIDR, or anything else that is not a +// single valid IPv6 CIDR. +func IsIPv6CIDRString(cidr string) bool { + return IPFamilyOfCIDRString(cidr) == IPv6 +} + +// IsIPv4 returns true if netIP is IPv4 (and false if it is IPv6, nil, or invalid). +func IsIPv4(netIP net.IP) bool { + return IPFamilyOf(netIP) == IPv4 +} + +// IsIPv4String returns true if ip contains a single IPv4 address and nothing else. It +// returns false if ip is an empty string, an IPv6 address, or anything else that is not a +// single IPv4 address. +func IsIPv4String(ip string) bool { + return IPFamilyOfString(ip) == IPv4 +} + +// IsIPv4CIDR returns true if cidr is a valid IPv4 CIDR. It returns false if cidr is nil +// or an IPv6 CIDR. Its behavior is not defined if cidr is invalid. +func IsIPv4CIDR(cidr *net.IPNet) bool { + return IPFamilyOfCIDR(cidr) == IPv4 +} + +// IsIPv4CIDRString returns true if cidr contains a single IPv4 CIDR and nothing else. It +// returns false if cidr is an empty string, an IPv6 CIDR, or anything else that is not a +// single valid IPv4 CIDR. +func IsIPv4CIDRString(cidr string) bool { + return IPFamilyOfCIDRString(cidr) == IPv4 +} diff --git a/vendor/k8s.io/utils/net/net.go b/vendor/k8s.io/utils/net/net.go index b7c08e2e00..704c1f232a 100644 --- a/vendor/k8s.io/utils/net/net.go +++ b/vendor/k8s.io/utils/net/net.go @@ -29,138 +29,16 @@ import ( // order is maintained func ParseCIDRs(cidrsString []string) ([]*net.IPNet, error) { cidrs := make([]*net.IPNet, 0, len(cidrsString)) - for _, cidrString := range cidrsString { + for i, cidrString := range cidrsString { _, cidr, err := ParseCIDRSloppy(cidrString) if err != nil { - return nil, fmt.Errorf("failed to parse cidr value:%q with error:%v", cidrString, err) + return nil, fmt.Errorf("invalid CIDR[%d]: %v (%v)", i, cidr, err) } cidrs = append(cidrs, cidr) } return cidrs, nil } -// IsDualStackIPs returns if a slice of ips is: -// - all are valid ips -// - at least one ip from each family (v4 or v6) -func IsDualStackIPs(ips []net.IP) (bool, error) { - v4Found := false - v6Found := false - for _, ip := range ips { - if ip == nil { - return false, fmt.Errorf("ip %v is invalid", ip) - } - - if v4Found && v6Found { - continue - } - - if IsIPv6(ip) { - v6Found = true - continue - } - - v4Found = true - } - - return (v4Found && v6Found), nil -} - -// IsDualStackIPStrings returns if -// - all are valid ips -// - at least one ip from each family (v4 or v6) -func IsDualStackIPStrings(ips []string) (bool, error) { - parsedIPs := make([]net.IP, 0, len(ips)) - for _, ip := range ips { - parsedIP := ParseIPSloppy(ip) - parsedIPs = append(parsedIPs, parsedIP) - } - return IsDualStackIPs(parsedIPs) -} - -// IsDualStackCIDRs returns if -// - all are valid cidrs -// - at least one cidr from each family (v4 or v6) -func IsDualStackCIDRs(cidrs []*net.IPNet) (bool, error) { - v4Found := false - v6Found := false - for _, cidr := range cidrs { - if cidr == nil { - return false, fmt.Errorf("cidr %v is invalid", cidr) - } - - if v4Found && v6Found { - continue - } - - if IsIPv6(cidr.IP) { - v6Found = true - continue - } - v4Found = true - } - - return v4Found && v6Found, nil -} - -// IsDualStackCIDRStrings returns if -// - all are valid cidrs -// - at least one cidr from each family (v4 or v6) -func IsDualStackCIDRStrings(cidrs []string) (bool, error) { - parsedCIDRs, err := ParseCIDRs(cidrs) - if err != nil { - return false, err - } - return IsDualStackCIDRs(parsedCIDRs) -} - -// IsIPv6 returns if netIP is IPv6. -func IsIPv6(netIP net.IP) bool { - return netIP != nil && netIP.To4() == nil -} - -// IsIPv6String returns if ip is IPv6. -func IsIPv6String(ip string) bool { - netIP := ParseIPSloppy(ip) - return IsIPv6(netIP) -} - -// IsIPv6CIDRString returns if cidr is IPv6. -// This assumes cidr is a valid CIDR. -func IsIPv6CIDRString(cidr string) bool { - ip, _, _ := ParseCIDRSloppy(cidr) - return IsIPv6(ip) -} - -// IsIPv6CIDR returns if a cidr is ipv6 -func IsIPv6CIDR(cidr *net.IPNet) bool { - ip := cidr.IP - return IsIPv6(ip) -} - -// IsIPv4 returns if netIP is IPv4. -func IsIPv4(netIP net.IP) bool { - return netIP != nil && netIP.To4() != nil -} - -// IsIPv4String returns if ip is IPv4. -func IsIPv4String(ip string) bool { - netIP := ParseIPSloppy(ip) - return IsIPv4(netIP) -} - -// IsIPv4CIDR returns if a cidr is ipv4 -func IsIPv4CIDR(cidr *net.IPNet) bool { - ip := cidr.IP - return IsIPv4(ip) -} - -// IsIPv4CIDRString returns if cidr is IPv4. -// This assumes cidr is a valid CIDR. -func IsIPv4CIDRString(cidr string) bool { - ip, _, _ := ParseCIDRSloppy(cidr) - return IsIPv4(ip) -} - // ParsePort parses a string representing an IP port. If the string is not a // valid port number, this returns an error. func ParsePort(port string, allowZero bool) (int, error) { diff --git a/vendor/k8s.io/utils/net/port.go b/vendor/k8s.io/utils/net/port.go index 7ac04f0dc9..c6a53fa02b 100644 --- a/vendor/k8s.io/utils/net/port.go +++ b/vendor/k8s.io/utils/net/port.go @@ -23,15 +23,6 @@ import ( "strings" ) -// IPFamily refers to a specific family if not empty, i.e. "4" or "6". -type IPFamily string - -// Constants for valid IPFamilys: -const ( - IPv4 IPFamily = "4" - IPv6 = "6" -) - // Protocol is a network protocol support by LocalPort. type Protocol string @@ -67,7 +58,7 @@ func NewLocalPort(desc, ip string, ipFamily IPFamily, port int, protocol Protoco if protocol != TCP && protocol != UDP { return nil, fmt.Errorf("Unsupported protocol %s", protocol) } - if ipFamily != "" && ipFamily != "4" && ipFamily != "6" { + if ipFamily != IPFamilyUnknown && ipFamily != IPv4 && ipFamily != IPv6 { return nil, fmt.Errorf("Invalid IP family %s", ipFamily) } if ip != "" { @@ -75,9 +66,10 @@ func NewLocalPort(desc, ip string, ipFamily IPFamily, port int, protocol Protoco if parsedIP == nil { return nil, fmt.Errorf("invalid ip address %s", ip) } - asIPv4 := parsedIP.To4() - if asIPv4 == nil && ipFamily == IPv4 || asIPv4 != nil && ipFamily == IPv6 { - return nil, fmt.Errorf("ip address and family mismatch %s, %s", ip, ipFamily) + if ipFamily != IPFamilyUnknown { + if IPFamily(parsedIP) != ipFamily { + return nil, fmt.Errorf("ip address and family mismatch %s, %s", ip, ipFamily) + } } } return &LocalPort{Description: desc, IP: ip, IPFamily: ipFamily, Port: port, Protocol: protocol}, nil diff --git a/vendor/k8s.io/utils/pointer/pointer.go b/vendor/k8s.io/utils/pointer/pointer.go index 638e27d419..b673a64257 100644 --- a/vendor/k8s.io/utils/pointer/pointer.go +++ b/vendor/k8s.io/utils/pointer/pointer.go @@ -14,12 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Deprecated: Use functions in k8s.io/utils/ptr instead: ptr.To to obtain +// a pointer, ptr.Deref to dereference a pointer, ptr.Equal to compare +// dereferenced pointers. package pointer import ( - "fmt" - "reflect" "time" + + "k8s.io/utils/ptr" ) // AllPtrFieldsNil tests whether all pointer fields in a struct are nil. This is useful when, @@ -28,363 +31,219 @@ import ( // // This function is only valid for structs and pointers to structs. Any other // type will cause a panic. Passing a typed nil pointer will return true. -func AllPtrFieldsNil(obj interface{}) bool { - v := reflect.ValueOf(obj) - if !v.IsValid() { - panic(fmt.Sprintf("reflect.ValueOf() produced a non-valid Value for %#v", obj)) - } - if v.Kind() == reflect.Ptr { - if v.IsNil() { - return true - } - v = v.Elem() - } - for i := 0; i < v.NumField(); i++ { - if v.Field(i).Kind() == reflect.Ptr && !v.Field(i).IsNil() { - return false - } - } - return true -} - -// Int returns a pointer to an int -func Int(i int) *int { - return &i -} +// +// Deprecated: Use ptr.AllPtrFieldsNil instead. +var AllPtrFieldsNil = ptr.AllPtrFieldsNil + +// Int returns a pointer to an int. +var Int = ptr.To[int] // IntPtr is a function variable referring to Int. -// Deprecated: Use Int instead. +// +// Deprecated: Use ptr.To instead. var IntPtr = Int // for back-compat // IntDeref dereferences the int ptr and returns it if not nil, or else // returns def. -func IntDeref(ptr *int, def int) int { - if ptr != nil { - return *ptr - } - return def -} +var IntDeref = ptr.Deref[int] // IntPtrDerefOr is a function variable referring to IntDeref. -// Deprecated: Use IntDeref instead. +// +// Deprecated: Use ptr.Deref instead. var IntPtrDerefOr = IntDeref // for back-compat // Int32 returns a pointer to an int32. -func Int32(i int32) *int32 { - return &i -} +var Int32 = ptr.To[int32] // Int32Ptr is a function variable referring to Int32. -// Deprecated: Use Int32 instead. +// +// Deprecated: Use ptr.To instead. var Int32Ptr = Int32 // for back-compat // Int32Deref dereferences the int32 ptr and returns it if not nil, or else // returns def. -func Int32Deref(ptr *int32, def int32) int32 { - if ptr != nil { - return *ptr - } - return def -} +var Int32Deref = ptr.Deref[int32] // Int32PtrDerefOr is a function variable referring to Int32Deref. -// Deprecated: Use Int32Deref instead. +// +// Deprecated: Use ptr.Deref instead. var Int32PtrDerefOr = Int32Deref // for back-compat // Int32Equal returns true if both arguments are nil or both arguments // dereference to the same value. -func Int32Equal(a, b *int32) bool { - if (a == nil) != (b == nil) { - return false - } - if a == nil { - return true - } - return *a == *b -} +var Int32Equal = ptr.Equal[int32] // Uint returns a pointer to an uint -func Uint(i uint) *uint { - return &i -} +var Uint = ptr.To[uint] // UintPtr is a function variable referring to Uint. -// Deprecated: Use Uint instead. +// +// Deprecated: Use ptr.To instead. var UintPtr = Uint // for back-compat // UintDeref dereferences the uint ptr and returns it if not nil, or else // returns def. -func UintDeref(ptr *uint, def uint) uint { - if ptr != nil { - return *ptr - } - return def -} +var UintDeref = ptr.Deref[uint] // UintPtrDerefOr is a function variable referring to UintDeref. -// Deprecated: Use UintDeref instead. +// +// Deprecated: Use ptr.Deref instead. var UintPtrDerefOr = UintDeref // for back-compat // Uint32 returns a pointer to an uint32. -func Uint32(i uint32) *uint32 { - return &i -} +var Uint32 = ptr.To[uint32] // Uint32Ptr is a function variable referring to Uint32. -// Deprecated: Use Uint32 instead. +// +// Deprecated: Use ptr.To instead. var Uint32Ptr = Uint32 // for back-compat // Uint32Deref dereferences the uint32 ptr and returns it if not nil, or else // returns def. -func Uint32Deref(ptr *uint32, def uint32) uint32 { - if ptr != nil { - return *ptr - } - return def -} +var Uint32Deref = ptr.Deref[uint32] // Uint32PtrDerefOr is a function variable referring to Uint32Deref. -// Deprecated: Use Uint32Deref instead. +// +// Deprecated: Use ptr.Deref instead. var Uint32PtrDerefOr = Uint32Deref // for back-compat // Uint32Equal returns true if both arguments are nil or both arguments // dereference to the same value. -func Uint32Equal(a, b *uint32) bool { - if (a == nil) != (b == nil) { - return false - } - if a == nil { - return true - } - return *a == *b -} +var Uint32Equal = ptr.Equal[uint32] // Int64 returns a pointer to an int64. -func Int64(i int64) *int64 { - return &i -} +var Int64 = ptr.To[int64] // Int64Ptr is a function variable referring to Int64. -// Deprecated: Use Int64 instead. +// +// Deprecated: Use ptr.To instead. var Int64Ptr = Int64 // for back-compat // Int64Deref dereferences the int64 ptr and returns it if not nil, or else // returns def. -func Int64Deref(ptr *int64, def int64) int64 { - if ptr != nil { - return *ptr - } - return def -} +var Int64Deref = ptr.Deref[int64] // Int64PtrDerefOr is a function variable referring to Int64Deref. -// Deprecated: Use Int64Deref instead. +// +// Deprecated: Use ptr.Deref instead. var Int64PtrDerefOr = Int64Deref // for back-compat // Int64Equal returns true if both arguments are nil or both arguments // dereference to the same value. -func Int64Equal(a, b *int64) bool { - if (a == nil) != (b == nil) { - return false - } - if a == nil { - return true - } - return *a == *b -} +var Int64Equal = ptr.Equal[int64] // Uint64 returns a pointer to an uint64. -func Uint64(i uint64) *uint64 { - return &i -} +var Uint64 = ptr.To[uint64] // Uint64Ptr is a function variable referring to Uint64. -// Deprecated: Use Uint64 instead. +// +// Deprecated: Use ptr.To instead. var Uint64Ptr = Uint64 // for back-compat // Uint64Deref dereferences the uint64 ptr and returns it if not nil, or else // returns def. -func Uint64Deref(ptr *uint64, def uint64) uint64 { - if ptr != nil { - return *ptr - } - return def -} +var Uint64Deref = ptr.Deref[uint64] // Uint64PtrDerefOr is a function variable referring to Uint64Deref. -// Deprecated: Use Uint64Deref instead. +// +// Deprecated: Use ptr.Deref instead. var Uint64PtrDerefOr = Uint64Deref // for back-compat // Uint64Equal returns true if both arguments are nil or both arguments // dereference to the same value. -func Uint64Equal(a, b *uint64) bool { - if (a == nil) != (b == nil) { - return false - } - if a == nil { - return true - } - return *a == *b -} +var Uint64Equal = ptr.Equal[uint64] // Bool returns a pointer to a bool. -func Bool(b bool) *bool { - return &b -} +var Bool = ptr.To[bool] // BoolPtr is a function variable referring to Bool. -// Deprecated: Use Bool instead. +// +// Deprecated: Use ptr.To instead. var BoolPtr = Bool // for back-compat // BoolDeref dereferences the bool ptr and returns it if not nil, or else // returns def. -func BoolDeref(ptr *bool, def bool) bool { - if ptr != nil { - return *ptr - } - return def -} +var BoolDeref = ptr.Deref[bool] // BoolPtrDerefOr is a function variable referring to BoolDeref. -// Deprecated: Use BoolDeref instead. +// +// Deprecated: Use ptr.Deref instead. var BoolPtrDerefOr = BoolDeref // for back-compat // BoolEqual returns true if both arguments are nil or both arguments // dereference to the same value. -func BoolEqual(a, b *bool) bool { - if (a == nil) != (b == nil) { - return false - } - if a == nil { - return true - } - return *a == *b -} +var BoolEqual = ptr.Equal[bool] // String returns a pointer to a string. -func String(s string) *string { - return &s -} +var String = ptr.To[string] // StringPtr is a function variable referring to String. -// Deprecated: Use String instead. +// +// Deprecated: Use ptr.To instead. var StringPtr = String // for back-compat // StringDeref dereferences the string ptr and returns it if not nil, or else // returns def. -func StringDeref(ptr *string, def string) string { - if ptr != nil { - return *ptr - } - return def -} +var StringDeref = ptr.Deref[string] // StringPtrDerefOr is a function variable referring to StringDeref. -// Deprecated: Use StringDeref instead. +// +// Deprecated: Use ptr.Deref instead. var StringPtrDerefOr = StringDeref // for back-compat // StringEqual returns true if both arguments are nil or both arguments // dereference to the same value. -func StringEqual(a, b *string) bool { - if (a == nil) != (b == nil) { - return false - } - if a == nil { - return true - } - return *a == *b -} +var StringEqual = ptr.Equal[string] // Float32 returns a pointer to a float32. -func Float32(i float32) *float32 { - return &i -} +var Float32 = ptr.To[float32] // Float32Ptr is a function variable referring to Float32. -// Deprecated: Use Float32 instead. +// +// Deprecated: Use ptr.To instead. var Float32Ptr = Float32 // Float32Deref dereferences the float32 ptr and returns it if not nil, or else // returns def. -func Float32Deref(ptr *float32, def float32) float32 { - if ptr != nil { - return *ptr - } - return def -} +var Float32Deref = ptr.Deref[float32] // Float32PtrDerefOr is a function variable referring to Float32Deref. -// Deprecated: Use Float32Deref instead. +// +// Deprecated: Use ptr.Deref instead. var Float32PtrDerefOr = Float32Deref // for back-compat // Float32Equal returns true if both arguments are nil or both arguments // dereference to the same value. -func Float32Equal(a, b *float32) bool { - if (a == nil) != (b == nil) { - return false - } - if a == nil { - return true - } - return *a == *b -} +var Float32Equal = ptr.Equal[float32] // Float64 returns a pointer to a float64. -func Float64(i float64) *float64 { - return &i -} +var Float64 = ptr.To[float64] // Float64Ptr is a function variable referring to Float64. -// Deprecated: Use Float64 instead. +// +// Deprecated: Use ptr.To instead. var Float64Ptr = Float64 // Float64Deref dereferences the float64 ptr and returns it if not nil, or else // returns def. -func Float64Deref(ptr *float64, def float64) float64 { - if ptr != nil { - return *ptr - } - return def -} +var Float64Deref = ptr.Deref[float64] // Float64PtrDerefOr is a function variable referring to Float64Deref. -// Deprecated: Use Float64Deref instead. +// +// Deprecated: Use ptr.Deref instead. var Float64PtrDerefOr = Float64Deref // for back-compat // Float64Equal returns true if both arguments are nil or both arguments // dereference to the same value. -func Float64Equal(a, b *float64) bool { - if (a == nil) != (b == nil) { - return false - } - if a == nil { - return true - } - return *a == *b -} +var Float64Equal = ptr.Equal[float64] // Duration returns a pointer to a time.Duration. -func Duration(d time.Duration) *time.Duration { - return &d -} +var Duration = ptr.To[time.Duration] // DurationDeref dereferences the time.Duration ptr and returns it if not nil, or else // returns def. -func DurationDeref(ptr *time.Duration, def time.Duration) time.Duration { - if ptr != nil { - return *ptr - } - return def -} +var DurationDeref = ptr.Deref[time.Duration] // DurationEqual returns true if both arguments are nil or both arguments // dereference to the same value. -func DurationEqual(a, b *time.Duration) bool { - if (a == nil) != (b == nil) { - return false - } - if a == nil { - return true - } - return *a == *b -} +var DurationEqual = ptr.Equal[time.Duration] diff --git a/vendor/k8s.io/utils/ptr/OWNERS b/vendor/k8s.io/utils/ptr/OWNERS new file mode 100644 index 0000000000..0d6392752a --- /dev/null +++ b/vendor/k8s.io/utils/ptr/OWNERS @@ -0,0 +1,10 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: +- apelisse +- stewart-yu +- thockin +reviewers: +- apelisse +- stewart-yu +- thockin diff --git a/vendor/k8s.io/utils/ptr/README.md b/vendor/k8s.io/utils/ptr/README.md new file mode 100644 index 0000000000..2ca8073dc7 --- /dev/null +++ b/vendor/k8s.io/utils/ptr/README.md @@ -0,0 +1,3 @@ +# Pointer + +This package provides some functions for pointer-based operations. diff --git a/vendor/k8s.io/utils/ptr/ptr.go b/vendor/k8s.io/utils/ptr/ptr.go new file mode 100644 index 0000000000..659ed3b9e2 --- /dev/null +++ b/vendor/k8s.io/utils/ptr/ptr.go @@ -0,0 +1,73 @@ +/* +Copyright 2023 The Kubernetes 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 ptr + +import ( + "fmt" + "reflect" +) + +// AllPtrFieldsNil tests whether all pointer fields in a struct are nil. This is useful when, +// for example, an API struct is handled by plugins which need to distinguish +// "no plugin accepted this spec" from "this spec is empty". +// +// This function is only valid for structs and pointers to structs. Any other +// type will cause a panic. Passing a typed nil pointer will return true. +func AllPtrFieldsNil(obj interface{}) bool { + v := reflect.ValueOf(obj) + if !v.IsValid() { + panic(fmt.Sprintf("reflect.ValueOf() produced a non-valid Value for %#v", obj)) + } + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return true + } + v = v.Elem() + } + for i := 0; i < v.NumField(); i++ { + if v.Field(i).Kind() == reflect.Ptr && !v.Field(i).IsNil() { + return false + } + } + return true +} + +// To returns a pointer to the given value. +func To[T any](v T) *T { + return &v +} + +// Deref dereferences ptr and returns the value it points to if no nil, or else +// returns def. +func Deref[T any](ptr *T, def T) T { + if ptr != nil { + return *ptr + } + return def +} + +// Equal returns true if both arguments are nil or both arguments +// dereference to the same value. +func Equal[T comparable](a, b *T) bool { + if (a == nil) != (b == nil) { + return false + } + if a == nil { + return true + } + return *a == *b +} diff --git a/vendor/k8s.io/utils/trace/trace.go b/vendor/k8s.io/utils/trace/trace.go index 3023d1066e..187eb5d8c5 100644 --- a/vendor/k8s.io/utils/trace/trace.go +++ b/vendor/k8s.io/utils/trace/trace.go @@ -21,6 +21,7 @@ import ( "context" "fmt" "math/rand" + "sync" "time" "k8s.io/klog/v2" @@ -64,6 +65,11 @@ func durationToMilliseconds(timeDuration time.Duration) int64 { } type traceItem interface { + // rLock must be called before invoking time or writeItem. + rLock() + // rUnlock must be called after processing the item is complete. + rUnlock() + // time returns when the trace was recorded as completed. time() time.Time // writeItem outputs the traceItem to the buffer. If stepThreshold is non-nil, only output the @@ -78,6 +84,10 @@ type traceStep struct { fields []Field } +// rLock doesn't need to do anything because traceStep instances are immutable. +func (s traceStep) rLock() {} +func (s traceStep) rUnlock() {} + func (s traceStep) time() time.Time { return s.stepTime } @@ -93,13 +103,24 @@ func (s traceStep) writeItem(b *bytes.Buffer, formatter string, startTime time.T // Trace keeps track of a set of "steps" and allows us to log a specific // step if it took longer than its share of the total allowed time type Trace struct { + // constant fields name string fields []Field - threshold *time.Duration startTime time.Time - endTime *time.Time - traceItems []traceItem parentTrace *Trace + // fields guarded by a lock + lock sync.RWMutex + threshold *time.Duration + endTime *time.Time + traceItems []traceItem +} + +func (t *Trace) rLock() { + t.lock.RLock() +} + +func (t *Trace) rUnlock() { + t.lock.RUnlock() } func (t *Trace) time() time.Time { @@ -138,6 +159,8 @@ func New(name string, fields ...Field) *Trace { // how long it took. The Fields add key value pairs to provide additional details about the trace // step. func (t *Trace) Step(msg string, fields ...Field) { + t.lock.Lock() + defer t.lock.Unlock() if t.traceItems == nil { // traces almost always have less than 6 steps, do this to avoid more than a single allocation t.traceItems = make([]traceItem, 0, 6) @@ -153,7 +176,9 @@ func (t *Trace) Nest(msg string, fields ...Field) *Trace { newTrace := New(msg, fields...) if t != nil { newTrace.parentTrace = t + t.lock.Lock() t.traceItems = append(t.traceItems, newTrace) + t.lock.Unlock() } return newTrace } @@ -163,7 +188,9 @@ func (t *Trace) Nest(msg string, fields ...Field) *Trace { // is logged. func (t *Trace) Log() { endTime := time.Now() + t.lock.Lock() t.endTime = &endTime + t.lock.Unlock() // an explicit logging request should dump all the steps out at the higher level if t.parentTrace == nil { // We don't start logging until Log or LogIfLong is called on the root trace t.logTrace() @@ -178,13 +205,17 @@ func (t *Trace) Log() { // If the Trace is nested it is not immediately logged. Instead, it is logged when the trace it // is nested within is logged. func (t *Trace) LogIfLong(threshold time.Duration) { + t.lock.Lock() t.threshold = &threshold + t.lock.Unlock() t.Log() } // logTopLevelTraces finds all traces in a hierarchy of nested traces that should be logged but do not have any // parents that will be logged, due to threshold limits, and logs them as top level traces. func (t *Trace) logTrace() { + t.lock.RLock() + defer t.lock.RUnlock() if t.durationIsWithinThreshold() { var buffer bytes.Buffer traceNum := rand.Int31() @@ -217,8 +248,10 @@ func (t *Trace) logTrace() { func (t *Trace) writeTraceSteps(b *bytes.Buffer, formatter string, stepThreshold *time.Duration) { lastStepTime := t.startTime for _, stepOrTrace := range t.traceItems { + stepOrTrace.rLock() stepOrTrace.writeItem(b, formatter, lastStepTime, stepThreshold) lastStepTime = stepOrTrace.time() + stepOrTrace.rUnlock() } } @@ -244,9 +277,13 @@ func (t *Trace) calculateStepThreshold() *time.Duration { traceThreshold := *t.threshold for _, s := range t.traceItems { nestedTrace, ok := s.(*Trace) - if ok && nestedTrace.threshold != nil { - traceThreshold = traceThreshold - *nestedTrace.threshold - lenTrace-- + if ok { + nestedTrace.lock.RLock() + if nestedTrace.threshold != nil { + traceThreshold = traceThreshold - *nestedTrace.threshold + lenTrace-- + } + nestedTrace.lock.RUnlock() } } diff --git a/vendor/modules.txt b/vendor/modules.txt index 19ac4ae469..943b9ee85a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1067,7 +1067,7 @@ k8s.io/kube-openapi/pkg/validation/spec # k8s.io/kubectl v0.25.6 ## explicit; go 1.19 k8s.io/kubectl/pkg/scheme -# k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 +# k8s.io/utils v0.0.0-20230726121419-3b25d923346b ## explicit; go 1.18 k8s.io/utils/buffer k8s.io/utils/clock @@ -1076,6 +1076,7 @@ k8s.io/utils/integer k8s.io/utils/internal/third_party/forked/golang/net k8s.io/utils/net k8s.io/utils/pointer +k8s.io/utils/ptr k8s.io/utils/strings/slices k8s.io/utils/trace # knative.dev/pkg v0.0.0-20221123011842-b78020c16606