diff --git a/pkg/controller/build/build_controller_test.go b/pkg/controller/build/build_controller_test.go index e463d6df8..afb977c45 100644 --- a/pkg/controller/build/build_controller_test.go +++ b/pkg/controller/build/build_controller_test.go @@ -103,9 +103,8 @@ var _ = Describe("Reconcile Build", func() { statusWriter.UpdateCalls(statusCall) _, err := reconciler.Reconcile(request) - Expect(err).To(HaveOccurred()) + Expect(err).To(BeNil()) Expect(statusWriter.UpdateCallCount()).To(Equal(1)) - Expect(err.Error()).To(ContainSubstring("secret non-existing does not exist")) }) It("succeeds when the secret exists", func() { @@ -166,9 +165,8 @@ var _ = Describe("Reconcile Build", func() { statusWriter.UpdateCalls(statusCall) _, err := reconciler.Reconcile(request) - Expect(err).To(HaveOccurred()) + Expect(err).To(BeNil()) Expect(statusWriter.UpdateCallCount()).To(Equal(1)) - Expect(err.Error()).To(ContainSubstring("secret non-existing does not exist")) }) It("succeeds when the secret exists", func() { @@ -225,9 +223,8 @@ var _ = Describe("Reconcile Build", func() { statusWriter.UpdateCalls(statusCall) _, err := reconciler.Reconcile(request) - Expect(err).To(HaveOccurred()) + Expect(err).To(BeNil()) Expect(statusWriter.UpdateCallCount()).To(Equal(1)) - Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("secret %s does not exist", registrySecret))) }) It("succeed when the secret exists", func() { @@ -270,9 +267,8 @@ var _ = Describe("Reconcile Build", func() { statusWriter.UpdateCalls(statusCall) _, err := reconciler.Reconcile(request) - Expect(err).To(HaveOccurred()) + Expect(err).To(BeNil()) Expect(statusWriter.UpdateCallCount()).To(Equal(1)) - Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("there are no secrets in namespace %s", namespace))) }) }) @@ -300,11 +296,8 @@ var _ = Describe("Reconcile Build", func() { }) _, err := reconciler.Reconcile(request) - Expect(err).To(HaveOccurred()) + Expect(err).To(BeNil()) Expect(statusWriter.UpdateCallCount()).To(Equal(1)) - Expect(err.Error()).To(ContainSubstring("do not exist")) - Expect(err.Error()).To(ContainSubstring("non-existing-source")) - Expect(err.Error()).To(ContainSubstring("non-existing-output")) }) }) diff --git a/test/build_samples.go b/test/build_samples.go index 5d74eb95a..44b7193c2 100644 --- a/test/build_samples.go +++ b/test/build_samples.go @@ -111,6 +111,80 @@ spec: name: fake-secret ` +// BuildWithOutputRefSecret defines a Build with a +// referenced secret under spec.output +const BuildWithOutputRefSecret = ` +apiVersion: build.dev/v1alpha1 +kind: Build +spec: + source: + url: "https://github.com/sbose78/taxi" + strategy: + kind: ClusterBuildStrategy + output: + image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app + credentials: + name: output-secret + timeout: 5s +` + +// BuildWithSourceRefSecret defines a Build with a +// referenced secret under spec.source +const BuildWithSourceRefSecret = ` +apiVersion: build.dev/v1alpha1 +kind: Build +spec: + source: + url: "https://github.com/sbose78/taxi" + credentials: + name: 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: build.dev/v1alpha1 +kind: Build +spec: + source: + url: "https://github.com/sbose78/taxi" + builder: + image: heroku/buildpacks:18 + credentials: + name: builder-secret + 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: build.dev/v1alpha1 +kind: Build +spec: + source: + url: "https://github.com/sbose78/taxi" + credentials: + name: source-secret + builder: + image: heroku/buildpacks:18 + credentials: + name: builder-secret + 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 = ` diff --git a/test/catalog.go b/test/catalog.go index 16df6890b..c7f722584 100644 --- a/test/catalog.go +++ b/test/catalog.go @@ -30,6 +30,27 @@ import ( // 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, + }, + } +} + // BuildWithClusterBuildStrategy gives you an specific Build CRD func (c *Catalog) BuildWithClusterBuildStrategy(name string, ns string, strategyName string, secretName string) *build.Build { buildStrategy := build.ClusterBuildStrategyKind @@ -176,7 +197,7 @@ func (c *Catalog) FakeSecretList() corev1.SecretList { } } -// FakeSecretListInNamespace to support test +// FakeNoSecretListInNamespace returns an empty secret list func (c *Catalog) FakeNoSecretListInNamespace() corev1.SecretList { return corev1.SecretList{ Items: []corev1.Secret{}, diff --git a/test/integration/build_to_secrets_test.go b/test/integration/build_to_secrets_test.go new file mode 100644 index 000000000..d8f5567d7 --- /dev/null +++ b/test/integration/build_to_secrets_test.go @@ -0,0 +1,515 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package integration_test + +import ( + "fmt" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" + "github.com/shipwright-io/build/test" + corev1 "k8s.io/api/core/v1" +) + +var _ = Describe("Integration tests Build and referenced Secrets", func() { + + var ( + cbsObject *v1alpha1.ClusterBuildStrategy + buildObject *v1alpha1.Build + ) + // Load the ClusterBuildStrategies before each test case + BeforeEach(func() { + cbsObject, err = tb.Catalog.LoadCBSWithName(STRATEGY+tb.Namespace, []byte(test.ClusterBuildStrategySingleStep)) + Expect(err).To(BeNil()) + + err = tb.CreateClusterBuildStrategy(cbsObject) + Expect(err).To(BeNil()) + }) + + // Delete the ClusterBuildStrategies after each test case + AfterEach(func() { + err := tb.DeleteClusterBuildStrategy(cbsObject.Name) + Expect(err).To(BeNil()) + }) + + Context("when a build reference a secret with annotations for the spec output", func() { + It("should validate the Build after secret deletion", func() { + + // populate Build related vars + buildName := BUILD + tb.Namespace + buildObject, err = tb.Catalog.LoadBuildWithNameAndStrategy( + buildName, + STRATEGY+tb.Namespace, + []byte(test.BuildWithOutputRefSecret), + ) + Expect(err).To(BeNil()) + + sampleSecret := tb.Catalog.SecretWithAnnotation(buildObject.Spec.Output.SecretRef.Name, buildObject.Namespace) + + Expect(tb.CreateSecret(sampleSecret)).To(BeNil()) + + Expect(tb.CreateBuild(buildObject)).To(BeNil()) + + // wait until the Build finish the validation + buildObject, err := tb.GetBuildTillValidation(buildName) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionTrue)) + Expect(buildObject.Status.Reason).To(Equal("Succeeded")) + + // delete a secret + Expect(tb.DeleteSecret(buildObject.Spec.Output.SecretRef.Name)).To(BeNil()) + + // assert that the validation happened one more time + buildObject, err = tb.GetBuildTillRegistration(buildName, corev1.ConditionFalse) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionFalse)) + Expect(buildObject.Status.Reason).To(Equal(fmt.Sprintf("secret %s does not exist", buildObject.Spec.Output.SecretRef.Name))) + + }) + + It("should validate when a missing secret is recreated", func() { + // populate Build related vars + buildName := BUILD + tb.Namespace + buildObject, err = tb.Catalog.LoadBuildWithNameAndStrategy( + buildName, + STRATEGY+tb.Namespace, + []byte(test.BuildCBSMinimalWithFakeSecret), + ) + Expect(err).To(BeNil()) + + Expect(tb.CreateBuild(buildObject)).To(BeNil()) + + // wait until the Build finish the validation + buildObject, err := tb.GetBuildTillValidation(buildName) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionFalse)) + Expect(buildObject.Status.Reason).To(Equal(fmt.Sprintf("secret %s does not exist", buildObject.Spec.Output.SecretRef.Name))) + + sampleSecret := tb.Catalog.SecretWithAnnotation(buildObject.Spec.Output.SecretRef.Name, buildObject.Namespace) + + // generate resources + Expect(tb.CreateSecret(sampleSecret)).To(BeNil()) + + // assert that the validation happened one more time + buildObject, err = tb.GetBuildTillRegistration(buildName, corev1.ConditionTrue) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionTrue)) + Expect(buildObject.Status.Reason).To(Equal("Succeeded")) + }) + }) + + Context("when a build reference a secret without annotations for the spec output", func() { + It("should not validate the Build after a secret deletion", func() { + + // populate Build related vars + buildName := BUILD + tb.Namespace + buildObject, err = tb.Catalog.LoadBuildWithNameAndStrategy( + buildName, + STRATEGY+tb.Namespace, + []byte(test.BuildWithOutputRefSecret), + ) + Expect(err).To(BeNil()) + + sampleSecret := tb.Catalog.SecretWithoutAnnotation(buildObject.Spec.Output.SecretRef.Name, buildObject.Namespace) + + // generate resources + Expect(tb.CreateSecret(sampleSecret)).To(BeNil()) + Expect(tb.CreateBuild(buildObject)).To(BeNil()) + + // wait until the Build finish the validation + buildObject, err := tb.GetBuildTillValidation(buildName) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionTrue)) + Expect(buildObject.Status.Reason).To(Equal("Succeeded")) + + // delete a secret + Expect(tb.DeleteSecret(buildObject.Spec.Output.SecretRef.Name)).To(BeNil()) + + // assert that the validation happened one more time + buildObject, err = tb.GetBuild(buildName) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionTrue)) + }) + + It("should not validate when a missing secret is recreated without annotation", func() { + // populate Build related vars + buildName := BUILD + tb.Namespace + buildObject, err = tb.Catalog.LoadBuildWithNameAndStrategy( + buildName, + STRATEGY+tb.Namespace, + []byte(test.BuildCBSMinimalWithFakeSecret), + ) + Expect(err).To(BeNil()) + + Expect(tb.CreateBuild(buildObject)).To(BeNil()) + + // wait until the Build finish the validation + buildObject, err := tb.GetBuildTillValidation(buildName) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionFalse)) + Expect(buildObject.Status.Reason).To(Equal(fmt.Sprintf("secret %s does not exist", buildObject.Spec.Output.SecretRef.Name))) + + sampleSecret := tb.Catalog.SecretWithoutAnnotation(buildObject.Spec.Output.SecretRef.Name, buildObject.Namespace) + + // generate resources + Expect(tb.CreateSecret(sampleSecret)).To(BeNil()) + + // // assert that the validation happened one more time + buildObject, err = tb.GetBuildTillRegistration(buildName, corev1.ConditionFalse) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionFalse)) + Expect(buildObject.Status.Reason).To(Equal(fmt.Sprintf("secret %s does not exist", buildObject.Spec.Output.SecretRef.Name))) + + }) + + It("should validate when a missing secret is recreated with annotation", func() { + // populate Build related vars + buildName := BUILD + tb.Namespace + buildObject, err = tb.Catalog.LoadBuildWithNameAndStrategy( + buildName, + STRATEGY+tb.Namespace, + []byte(test.BuildCBSMinimalWithFakeSecret), + ) + Expect(err).To(BeNil()) + + Expect(tb.CreateBuild(buildObject)).To(BeNil()) + + // wait until the Build finish the validation + buildObject, err := tb.GetBuildTillValidation(buildName) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionFalse)) + Expect(buildObject.Status.Reason).To(Equal(fmt.Sprintf("secret %s does not exist", "fake-secret"))) + + sampleSecret := tb.Catalog.SecretWithoutAnnotation(buildObject.Spec.Output.SecretRef.Name, buildObject.Namespace) + + // generate resources + Expect(tb.CreateSecret(sampleSecret)).To(BeNil()) + // validate build status again + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionFalse)) + Expect(buildObject.Status.Reason).To(Equal(fmt.Sprintf("secret %s does not exist", "fake-secret"))) + + // we modify the annotation so automatic delete does not take place + data := []byte(fmt.Sprintf(`{"metadata":{"annotations":{"%s":"true"}}}`, v1alpha1.AnnotationBuildRefSecret)) + + _, err = tb.PatchSecret(buildObject.Spec.Output.SecretRef.Name, data) + Expect(err).To(BeNil()) + + // // assert that the validation happened one more time + buildObject, err = tb.GetBuildTillRegistration(buildName, corev1.ConditionTrue) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionTrue)) + Expect(buildObject.Status.Reason).To(Equal("Succeeded")) + + }) + }) + + Context("when a build reference a secret with annotations for the spec source", func() { + It("should validate the Build after secret deletion", func() { + + // populate Build related vars + buildName := BUILD + tb.Namespace + buildObject, err = tb.Catalog.LoadBuildWithNameAndStrategy( + buildName, + STRATEGY+tb.Namespace, + []byte(test.BuildWithSourceRefSecret), + ) + Expect(err).To(BeNil()) + + sampleSecret := tb.Catalog.SecretWithAnnotation(buildObject.Spec.Source.SecretRef.Name, buildObject.Namespace) + + Expect(tb.CreateSecret(sampleSecret)).To(BeNil()) + + Expect(tb.CreateBuild(buildObject)).To(BeNil()) + + // wait until the Build finish the validation + buildObject, err := tb.GetBuildTillValidation(buildName) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionTrue)) + Expect(buildObject.Status.Reason).To(Equal("Succeeded")) + + // delete a secret + Expect(tb.DeleteSecret(buildObject.Spec.Source.SecretRef.Name)).To(BeNil()) + + // assert that the validation happened one more time + buildObject, err = tb.GetBuildTillRegistration(buildName, corev1.ConditionFalse) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionFalse)) + Expect(buildObject.Status.Reason).To(Equal(fmt.Sprintf("secret %s does not exist", buildObject.Spec.Source.SecretRef.Name))) + + }) + + It("should validate when a missing secret is recreated", func() { + // populate Build related vars + buildName := BUILD + tb.Namespace + buildObject, err = tb.Catalog.LoadBuildWithNameAndStrategy( + buildName, + STRATEGY+tb.Namespace, + []byte(test.BuildWithSourceRefSecret), + ) + Expect(err).To(BeNil()) + + Expect(tb.CreateBuild(buildObject)).To(BeNil()) + + // wait until the Build finish the validation + buildObject, err := tb.GetBuildTillValidation(buildName) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionFalse)) + Expect(buildObject.Status.Reason).To(Equal(fmt.Sprintf("secret %s does not exist", buildObject.Spec.Source.SecretRef.Name))) + + sampleSecret := tb.Catalog.SecretWithAnnotation(buildObject.Spec.Source.SecretRef.Name, buildObject.Namespace) + + // generate resources + Expect(tb.CreateSecret(sampleSecret)).To(BeNil()) + + // assert that the validation happened one more time + buildObject, err = tb.GetBuildTillRegistration(buildName, corev1.ConditionTrue) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionTrue)) + Expect(buildObject.Status.Reason).To(Equal("Succeeded")) + }) + }) + + Context("when a build reference a secret with annotations for the spec builder", func() { + It("should validate the Build after secret deletion", func() { + + // populate Build related vars + buildName := BUILD + tb.Namespace + buildObject, err = tb.Catalog.LoadBuildWithNameAndStrategy( + buildName, + STRATEGY+tb.Namespace, + []byte(test.BuildWithBuilderRefSecret), + ) + Expect(err).To(BeNil()) + + sampleSecret := tb.Catalog.SecretWithAnnotation(buildObject.Spec.BuilderImage.SecretRef.Name, buildObject.Namespace) + + Expect(tb.CreateSecret(sampleSecret)).To(BeNil()) + + Expect(tb.CreateBuild(buildObject)).To(BeNil()) + + // wait until the Build finish the validation + buildObject, err := tb.GetBuildTillValidation(buildName) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionTrue)) + Expect(buildObject.Status.Reason).To(Equal("Succeeded")) + + // delete a secret + Expect(tb.DeleteSecret(buildObject.Spec.BuilderImage.SecretRef.Name)).To(BeNil()) + + // assert that the validation happened one more time + buildObject, err = tb.GetBuildTillRegistration(buildName, corev1.ConditionFalse) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionFalse)) + Expect(buildObject.Status.Reason).To(Equal(fmt.Sprintf("secret %s does not exist", buildObject.Spec.BuilderImage.SecretRef.Name))) + + }) + + It("should validate when a missing secret is recreated", func() { + // populate Build related vars + buildName := BUILD + tb.Namespace + buildObject, err = tb.Catalog.LoadBuildWithNameAndStrategy( + buildName, + STRATEGY+tb.Namespace, + []byte(test.BuildWithBuilderRefSecret), + ) + Expect(err).To(BeNil()) + + Expect(tb.CreateBuild(buildObject)).To(BeNil()) + + // wait until the Build finish the validation + buildObject, err := tb.GetBuildTillValidation(buildName) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionFalse)) + Expect(buildObject.Status.Reason).To(Equal(fmt.Sprintf("secret %s does not exist", buildObject.Spec.BuilderImage.SecretRef.Name))) + + sampleSecret := tb.Catalog.SecretWithAnnotation(buildObject.Spec.BuilderImage.SecretRef.Name, buildObject.Namespace) + + // generate resources + Expect(tb.CreateSecret(sampleSecret)).To(BeNil()) + + // assert that the validation happened one more time + buildObject, err = tb.GetBuildTillRegistration(buildName, corev1.ConditionTrue) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionTrue)) + Expect(buildObject.Status.Reason).To(Equal("Succeeded")) + }) + }) + + Context("when a build reference multiple secrets with annotations for a build instance", func() { + It("should validate the Build after secret deletion", func() { + + // populate Build related vars + buildName := BUILD + tb.Namespace + buildObject, err = tb.Catalog.LoadBuildWithNameAndStrategy( + buildName, + STRATEGY+tb.Namespace, + []byte(test.BuildWithMultipleRefSecrets), + ) + Expect(err).To(BeNil()) + + specSourceSecret := tb.Catalog.SecretWithAnnotation(buildObject.Spec.Source.SecretRef.Name, buildObject.Namespace) + specBuilderSecret := tb.Catalog.SecretWithAnnotation(buildObject.Spec.BuilderImage.SecretRef.Name, buildObject.Namespace) + + Expect(tb.CreateSecret(specSourceSecret)).To(BeNil()) + Expect(tb.CreateSecret(specBuilderSecret)).To(BeNil()) + + Expect(tb.CreateBuild(buildObject)).To(BeNil()) + + // wait until the Build finish the validation + buildObject, err := tb.GetBuildTillValidation(buildName) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionTrue)) + Expect(buildObject.Status.Reason).To(Equal("Succeeded")) + + // delete a secret + Expect(tb.DeleteSecret(specSourceSecret.Name)).To(BeNil()) + Expect(tb.DeleteSecret(specBuilderSecret.Name)).To(BeNil()) + + buildObject, err = tb.GetBuildTillReasonContainsSubstring(buildName, "do not exist") + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionFalse)) + Expect(buildObject.Status.Reason).To(ContainSubstring(specSourceSecret.Name)) + Expect(buildObject.Status.Reason).To(ContainSubstring(specBuilderSecret.Name)) + + }) + + It("should validate when a missing secret is recreated", func() { + // populate Build related vars + buildName := BUILD + tb.Namespace + buildObject, err = tb.Catalog.LoadBuildWithNameAndStrategy( + buildName, + STRATEGY+tb.Namespace, + []byte(test.BuildWithMultipleRefSecrets), + ) + Expect(err).To(BeNil()) + + Expect(tb.CreateBuild(buildObject)).To(BeNil()) + + // wait until the Build finish the validation + buildObject, err := tb.GetBuildTillValidation(buildName) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionFalse)) + Expect(buildObject.Status.Reason).To(ContainSubstring("do not exist")) + Expect(buildObject.Status.Reason).To(ContainSubstring(buildObject.Spec.Source.SecretRef.Name)) + Expect(buildObject.Status.Reason).To(ContainSubstring(buildObject.Spec.BuilderImage.SecretRef.Name)) + + specSourceSecret := tb.Catalog.SecretWithAnnotation(buildObject.Spec.Source.SecretRef.Name, buildObject.Namespace) + specBuilderSecret := tb.Catalog.SecretWithAnnotation(buildObject.Spec.BuilderImage.SecretRef.Name, buildObject.Namespace) + + // generate resources + Expect(tb.CreateSecret(specSourceSecret)).To(BeNil()) + Expect(tb.CreateSecret(specBuilderSecret)).To(BeNil()) + + // assert that the validation happened one more time + buildObject, err = tb.GetBuildTillRegistration(buildName, corev1.ConditionTrue) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionTrue)) + Expect(buildObject.Status.Reason).To(Equal("Succeeded")) + }) + }) + Context("when multiple builds reference a secret with annotations for the spec.source", func() { + It("should validate the Builds after secret deletion", func() { + + // populate Build related vars + firstBuildName := BUILD + tb.Namespace + firstBuildObject, err := tb.Catalog.LoadBuildWithNameAndStrategy( + firstBuildName, + STRATEGY+tb.Namespace, + []byte(test.BuildWithSourceRefSecret), + ) + Expect(err).To(BeNil()) + + // populate Build related vars + secondBuildName := BUILD + tb.Namespace + "extra-build" + secondBuildObject, err := tb.Catalog.LoadBuildWithNameAndStrategy( + secondBuildName, + STRATEGY+tb.Namespace, + []byte(test.BuildWithSourceRefSecret), + ) + Expect(err).To(BeNil()) + + specSourceSecret := tb.Catalog.SecretWithAnnotation(firstBuildObject.Spec.Source.SecretRef.Name, firstBuildObject.Namespace) + + Expect(tb.CreateSecret(specSourceSecret)).To(BeNil()) + + Expect(tb.CreateBuild(firstBuildObject)).To(BeNil()) + Expect(tb.CreateBuild(secondBuildObject)).To(BeNil()) + + // wait until the Build finish the validation + o, err := tb.GetBuildTillValidation(firstBuildName) + Expect(err).To(BeNil()) + Expect(o.Status.Registered).To(Equal(corev1.ConditionTrue)) + Expect(o.Status.Reason).To(Equal("Succeeded")) + + o, err = tb.GetBuildTillValidation(secondBuildName) + Expect(err).To(BeNil()) + Expect(o.Status.Registered).To(Equal(corev1.ConditionTrue)) + Expect(o.Status.Reason).To(Equal("Succeeded")) + + // delete a secret + Expect(tb.DeleteSecret(specSourceSecret.Name)).To(BeNil()) + + // assert that the validation happened one more time + o, err = tb.GetBuildTillRegistration(firstBuildName, corev1.ConditionFalse) + Expect(err).To(BeNil()) + Expect(o.Status.Registered).To(Equal(corev1.ConditionFalse)) + Expect(o.Status.Reason).To(Equal(fmt.Sprintf("secret %s does not exist", firstBuildObject.Spec.Source.SecretRef.Name))) + + // assert that the validation happened one more time + o, err = tb.GetBuildTillRegistration(secondBuildName, corev1.ConditionFalse) + Expect(err).To(BeNil()) + Expect(o.Status.Registered).To(Equal(corev1.ConditionFalse)) + Expect(o.Status.Reason).To(Equal(fmt.Sprintf("secret %s does not exist", secondBuildObject.Spec.Source.SecretRef.Name))) + }) + It("should validate the Builds when a missing secret is recreated", func() { + // populate Build related vars + firstBuildName := BUILD + tb.Namespace + firstBuildObject, err := tb.Catalog.LoadBuildWithNameAndStrategy( + firstBuildName, + STRATEGY+tb.Namespace, + []byte(test.BuildWithSourceRefSecret), + ) + Expect(err).To(BeNil()) + + // populate Build related vars + secondBuildName := BUILD + tb.Namespace + "extra-build" + secondBuildObject, err := tb.Catalog.LoadBuildWithNameAndStrategy( + secondBuildName, + STRATEGY+tb.Namespace, + []byte(test.BuildWithSourceRefSecret), + ) + Expect(err).To(BeNil()) + + Expect(tb.CreateBuild(firstBuildObject)).To(BeNil()) + Expect(tb.CreateBuild(secondBuildObject)).To(BeNil()) + + // wait until the Builds finish the validation + buildObject, err := tb.GetBuildTillValidation(firstBuildName) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionFalse)) + + buildObject, err = tb.GetBuildTillValidation(secondBuildName) + Expect(err).To(BeNil()) + Expect(buildObject.Status.Registered).To(Equal(corev1.ConditionFalse)) + + specSourceSecret := tb.Catalog.SecretWithAnnotation(firstBuildObject.Spec.Source.SecretRef.Name, firstBuildObject.Namespace) + + // generate resources + Expect(tb.CreateSecret(specSourceSecret)).To(BeNil()) + + // assert that the validation happened one more time + o, err := tb.GetBuildTillRegistration(firstBuildName, corev1.ConditionTrue) + Expect(err).To(BeNil()) + Expect(o.Status.Registered).To(Equal(corev1.ConditionTrue)) + Expect(o.Status.Reason).To(Equal("Succeeded")) + + o, err = tb.GetBuildTillRegistration(secondBuildName, corev1.ConditionTrue) + Expect(err).To(BeNil()) + Expect(o.Status.Registered).To(Equal(corev1.ConditionTrue)) + Expect(o.Status.Reason).To(Equal("Succeeded")) + }) + }) +}) diff --git a/test/integration/utils/builds.go b/test/integration/utils/builds.go index 08ba9fa60..2b22d449f 100644 --- a/test/integration/utils/builds.go +++ b/test/integration/utils/builds.go @@ -6,9 +6,13 @@ 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/v1alpha1" ) @@ -54,3 +58,99 @@ func (t *TestBuild) PatchBuildWithPatchType(buildName string, data []byte, pt ty } 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) (*v1alpha1.Build, error) { + + var ( + pollBuildTillRegistration = func() (bool, error) { + + bInterface := t.BuildClientSet.BuildV1alpha1().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 != "" { + return true, nil + } + + return false, nil + } + ) + + brInterface := t.BuildClientSet.BuildV1alpha1().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{}) +} + +// 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) (*v1alpha1.Build, error) { + + var ( + pollBuildTillRegistration = func() (bool, error) { + + bInterface := t.BuildClientSet.BuildV1alpha1().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 == condition { + return true, nil + } + + return false, nil + } + ) + + brInterface := t.BuildClientSet.BuildV1alpha1().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{}) +} + +// GetBuildTillReasonContainsSubstring polls until a Build reason 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) GetBuildTillReasonContainsSubstring(name string, partOfReason string) (*v1alpha1.Build, error) { + + var ( + pollBuildTillReasonContainsSubString = func() (bool, error) { + + bInterface := t.BuildClientSet.BuildV1alpha1().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.Reason, partOfReason) { + return true, nil + } + + return false, nil + } + ) + + brInterface := t.BuildClientSet.BuildV1alpha1().Builds(t.Namespace) + + if err := wait.PollImmediate(t.Interval, t.TimeOut, pollBuildTillReasonContainsSubString); err != nil { + return nil, err + } + + return brInterface.Get(context.TODO(), name, metav1.GetOptions{}) +} diff --git a/test/integration/utils/secrets.go b/test/integration/utils/secrets.go index 5821aadab..9bd8016b9 100644 --- a/test/integration/utils/secrets.go +++ b/test/integration/utils/secrets.go @@ -9,16 +9,42 @@ import ( 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(ns string, secret *corev1.Secret) error { - client := t.Clientset.CoreV1().Secrets(ns) +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 +}