diff --git a/Makefile b/Makefile index 379ccfa..8910d45 100644 --- a/Makefile +++ b/Makefile @@ -270,7 +270,11 @@ unit-test: .PHONY: e2e-test e2e-test: - cd test && go test -count=1 -v ./e2e + go test -count=1 -tags=integration -v -timeout 30m ./test/... + +.PHONY: e2e-test-upgrade +e2e-test-upgrade: + go test -count=1 -tags=upgrade -v -timeout 30m ./test/... # Generate related image FILE := helm-charts/policy-controller-operator/values.yaml diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index 74def1e..9e7e197 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -1,3 +1,5 @@ +//go:build integration + package e2e import ( @@ -12,8 +14,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" - e2e_utils "github.com/securesign/policy-controller-operator/test/e2e/utils" - appsv1 "k8s.io/api/apps/v1" + e2e_utils "github.com/securesign/policy-controller-operator/test/utils" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -75,55 +76,37 @@ var _ = SynchronizedBeforeSuite(func() []byte { ObjectMeta: metav1.ObjectMeta{Name: e2e_utils.InstallNamespace}, })).To(SatisfyAny(Succeed(), MatchError(ContainSubstring("already exists")))) - By("applying the operator bundle: " + policyControllerCRPath) - renderedPolicyController, err := e2e_utils.RenderTemplate(policyControllerCRPath, map[string]string{ + By("applying the operator bundle: " + e2e_utils.PolicyControllerCRPath) + renderedPolicyController, err := e2e_utils.RenderTemplate(e2e_utils.PolicyControllerCRPath, map[string]string{ "NS": e2e_utils.InstallNamespace, }) Expect(err).NotTo(HaveOccurred()) Expect(e2e_utils.ApplyManifest(ctx, k8sClient, renderedPolicyController, "")).To(Succeed()) By("ensuring deployment is ready") - dep := &appsv1.Deployment{} - e2e_utils.ExpectExists(e2e_utils.DeploymentName, e2e_utils.InstallNamespace, dep, k8sClient, ctx) - desired := *dep.Spec.Replicas - Eventually(func(ctx context.Context) int32 { - err := k8sClient.Get(ctx, client.ObjectKey{Namespace: e2e_utils.InstallNamespace, Name: e2e_utils.DeploymentName}, dep) - Expect(err).ToNot(HaveOccurred()) - return dep.Status.ReadyReplicas - }).WithContext(ctx).Should(Equal(desired), "timed out waiting for %d pods to be Ready in Deployment %q", desired, e2e_utils.DeploymentName) + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForDeploymentReady(ctx, k8sClient, e2e_utils.InstallNamespace, e2e_utils.DeploymentName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for Deployment %q to be ready", e2e_utils.DeploymentName) By("injecting CA") injectCA, err = strconv.ParseBool(strings.TrimSpace(e2e_utils.InjectCA())) Expect(err).NotTo(HaveOccurred()) if injectCA { Expect(e2e_utils.InjectCAIntoDeployment(ctx, k8sClient, e2e_utils.DeploymentName, e2e_utils.InstallNamespace)).To(Succeed()) - Eventually(func(ctx context.Context) (bool, error) { - cm := &corev1.ConfigMap{} - err := k8sClient.Get(ctx, client.ObjectKey{Namespace: e2e_utils.InstallNamespace, Name: "trusted-ca-bundle"}, cm) - if err != nil { - return false, err - } - bundle, ok := cm.Data["ca-bundle.crt"] - return ok && len(bundle) > 0, nil - }).WithContext(ctx).Should(BeTrue(), "trusted-ca-bundle never got its ca-bundle.crt") - - dep := &appsv1.Deployment{} - Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: e2e_utils.InstallNamespace, Name: e2e_utils.DeploymentName}, dep)).To(Succeed(), "failed to read Deployment after CA injection") - - desired := *dep.Spec.Replicas - Eventually(func(ctx context.Context) (int32, error) { - if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: e2e_utils.InstallNamespace, Name: e2e_utils.DeploymentName}, dep); err != nil { - return 0, err - } - return dep.Status.ReadyReplicas, nil - }).WithContext(ctx).Should(Equal(desired), "timed out waiting for %d Ready replicas in Deployment %q", desired, e2e_utils.DeploymentName) + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForConfigMapKey(ctx, k8sClient, e2e_utils.InstallNamespace, "trusted-ca-bundle", "ca-bundle.crt") + }).WithContext(ctx).Should(Succeed(), "trusted-ca-bundle never got its ca-bundle.crt") + + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForDeploymentReady(ctx, k8sClient, e2e_utils.InstallNamespace, e2e_utils.DeploymentName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for Deployment %q to be ready after CA injection", e2e_utils.DeploymentName) } By("verifying all required resources are created") e2e_utils.ExpectRequiredResources(ctx, k8sClient) By("asserting admission webhook behaviour") - renderedPolicyController, err = e2e_utils.RenderTemplate(policyControllerCRPath, map[string]string{ + renderedPolicyController, err = e2e_utils.RenderTemplate(e2e_utils.PolicyControllerCRPath, map[string]string{ "NS": "default", }) Expect(err).NotTo(HaveOccurred()) diff --git a/test/e2e/policy_controller_e2e_test.go b/test/e2e/policy_controller_e2e_test.go index 239e2df..f8e1c9c 100644 --- a/test/e2e/policy_controller_e2e_test.go +++ b/test/e2e/policy_controller_e2e_test.go @@ -1,3 +1,5 @@ +//go:build integration + package e2e import ( @@ -6,35 +8,25 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - e2e_utils "github.com/securesign/policy-controller-operator/test/e2e/utils" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" + e2e_utils "github.com/securesign/policy-controller-operator/test/utils" "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/client" ) const ( - policyControllerCRPath = "custom_resources/policy_controller/common_policy_controller.yaml.tpl" - trustRootCommonCrPath = "custom_resources/trust_roots/common_trust_root.yaml.tpl" - clusterimagepolicyCommonCrPath = "custom_resources/cluster_image_policies/common_cluster_image_policy.yaml.tpl" - commonTestNS = "pco-e2e" - commonTestImageEnv = "COMMON_TEST_IMAGE" - commonTrustRootName = "common-install-trust-root" - commonCIPName = "common-install-cluster-image-policy" - - trustRootBYOKCrPath = "custom_resources/trust_roots/byok_trust_root.yaml.tpl" - clusterimagepolicyBYOKCrPath = "custom_resources/cluster_image_policies/common_cluster_image_policy.yaml.tpl" - byokTestNS = "pco-e2e-byok" - byokTestImageEnv = "BYOK_TEST_IMAGE" - byokTrustRootName = "byok-install-trust-root" - byokCIPName = "byok-install-cluster-image-policy" - - trustRootSTUFCrPath = "custom_resources/trust_roots/stuf_trust_root.yaml.tpl" - clusterimagepolicySTUFCrPath = "custom_resources/cluster_image_policies/common_cluster_image_policy.yaml.tpl" - stufTestNS = "pco-e2e-stuf" - stufTestImageEnv = "STUF_TEST_IMAGE" - stufTrustRootName = "serialized-tuf-install-trust-root" - stufCIPName = "serialized-tuf-install-cluster-image-policy" + commonTestNS = "pco-e2e" + commonTestImageEnv = "COMMON_TEST_IMAGE" + commonTrustRootName = "common-install-trust-root" + commonCIPName = "common-install-cluster-image-policy" + + byokTestNS = "pco-e2e-byok" + byokTestImageEnv = "BYOK_TEST_IMAGE" + byokTrustRootName = "byok-install-trust-root" + byokCIPName = "byok-install-cluster-image-policy" + + stufTestNS = "pco-e2e-stuf" + stufTestImageEnv = "STUF_TEST_IMAGE" + stufTrustRootName = "serialized-tuf-install-trust-root" + stufCIPName = "serialized-tuf-install-cluster-image-policy" ) var ( @@ -64,21 +56,16 @@ var _ = Describe("policy-controller-operator common installation", Ordered, Seri }) It("ensuring deployment is ready", func(ctx SpecContext) { - dep := &appsv1.Deployment{} - e2e_utils.ExpectExists(e2e_utils.DeploymentName, e2e_utils.InstallNamespace, dep, k8sClient, ctx) - desired := *dep.Spec.Replicas - Eventually(func(ctx context.Context) int32 { - err := k8sClient.Get(ctx, client.ObjectKey{Namespace: e2e_utils.InstallNamespace, Name: e2e_utils.DeploymentName}, dep) - Expect(err).ToNot(HaveOccurred()) - return dep.Status.ReadyReplicas - }).WithContext(ctx).Should(Equal(desired), "timed out waiting for %d pods to be Ready in Deployment %q", desired, e2e_utils.DeploymentName) + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForDeploymentReady(ctx, k8sClient, e2e_utils.InstallNamespace, e2e_utils.DeploymentName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for Deployment %q to be ready", e2e_utils.DeploymentName) }) It("creates a TrustRoot and adds it to the sigstore-keys ConfigMap", func(ctx SpecContext) { tufroot, err := e2e_utils.ResolveTufRoot(ctx) Expect(err).NotTo(HaveOccurred()) - commonRenderedTrustRoot, err := e2e_utils.RenderTemplate(trustRootCommonCrPath, map[string]string{ + commonRenderedTrustRoot, err := e2e_utils.RenderTemplate(e2e_utils.TrustRootCommonCrPath, map[string]string{ "TRUST_ROOT_NAME": commonTrustRootName, "TUFMirror": e2e_utils.TufUrl(), "TUFRoot": e2e_utils.Base64EncodeString(tufroot), @@ -86,21 +73,13 @@ var _ = Describe("policy-controller-operator common installation", Ordered, Seri Expect(err).NotTo(HaveOccurred()) Expect(e2e_utils.ApplyManifest(ctx, k8sClient, commonRenderedTrustRoot, "")).To(Succeed()) - Eventually(func(ctx SpecContext) (string, error) { - cm := &corev1.ConfigMap{} - if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: e2e_utils.InstallNamespace, Name: "config-sigstore-keys"}, cm); err != nil { - return "", err - } - val, ok := cm.Data[commonTrustRootName] - if !ok { - return "", fmt.Errorf("key not present yet") - } - return val, nil - }).WithContext(ctx).ShouldNot(BeEmpty(), "timed out waiting for ConfigMap 'config-sigstore-keys' to have the %s key", commonTrustRootName) + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForConfigMapKey(ctx, k8sClient, e2e_utils.InstallNamespace, "config-sigstore-keys", commonTrustRootName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for ConfigMap 'config-sigstore-keys' to have the %s key", commonTrustRootName) }) It("creates a Cluster image policy and adds it to the config-image-policies ConfigMap", func(ctx SpecContext) { - commonRenderedClusteImagePolicy, err := e2e_utils.RenderTemplate(clusterimagepolicyCommonCrPath, map[string]string{ + commonRenderedClusteImagePolicy, err := e2e_utils.RenderTemplate(e2e_utils.ClusterimagepolicyCommonCrPath, map[string]string{ "FULCIO_URL": e2e_utils.FulcioUrl(), "REKOR_URL": e2e_utils.RekorUrl(), "OIDC_ISSUER_URL": e2e_utils.OidcIssuerUrl(), @@ -113,27 +92,19 @@ var _ = Describe("policy-controller-operator common installation", Ordered, Seri Expect(err).NotTo(HaveOccurred()) Expect(e2e_utils.ApplyManifest(ctx, k8sClient, commonRenderedClusteImagePolicy, "")).To(Succeed()) - Eventually(func(ctx SpecContext) (string, error) { - cm := &corev1.ConfigMap{} - if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: e2e_utils.InstallNamespace, Name: "config-image-policies"}, cm); err != nil { - return "", err - } - val, ok := cm.Data[commonCIPName] - if !ok { - return "", fmt.Errorf("key not present yet") - } - return val, nil - }).WithContext(ctx).ShouldNot(BeEmpty(), "timed out waiting for ConfigMap 'config-image-policies' to have the %s key", commonCIPName) + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForConfigMapKey(ctx, k8sClient, e2e_utils.InstallNamespace, "config-image-policies", commonCIPName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for ConfigMap 'config-image-policies' to have the %s key", commonCIPName) }) It("verifies policy controller behavour", func(ctx SpecContext) { - e2e_utils.Verify(ctx, k8sClient, commonTestNS, commonTestImage) + e2e_utils.Verify(ctx, k8sClient, commonTestNS, commonTestImage, true) }) It("creates a TrustRoot and adds it to the sigstore-keys ConfigMap", func(ctx SpecContext) { trustedrootValues, err := e2e_utils.ParseTufRoot(ctx) Expect(err).NotTo(HaveOccurred()) - byokRenderedTrustRoot, err := e2e_utils.RenderTemplate(trustRootBYOKCrPath, map[string]string{ + byokRenderedTrustRoot, err := e2e_utils.RenderTemplate(e2e_utils.TrustRootBYOKCrPath, map[string]string{ "TRUST_ROOT_NAME": byokTrustRootName, "FULCIO_ORG_NAME": trustedrootValues.FulcioOrgName, "FULCIO_COMMON_NAME": trustedrootValues.FulcioCommonName, @@ -153,21 +124,13 @@ var _ = Describe("policy-controller-operator common installation", Ordered, Seri Expect(err).NotTo(HaveOccurred()) Expect(e2e_utils.ApplyManifest(ctx, k8sClient, byokRenderedTrustRoot, "")).To(Succeed()) - Eventually(func(ctx SpecContext) (string, error) { - cm := &corev1.ConfigMap{} - if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: e2e_utils.InstallNamespace, Name: "config-sigstore-keys"}, cm); err != nil { - return "", err - } - val, ok := cm.Data[byokTrustRootName] - if !ok { - return "", fmt.Errorf("key not present yet") - } - return val, nil - }).WithContext(ctx).ShouldNot(BeEmpty(), "timed out waiting for ConfigMap 'config-sigstore-keys' to have the %s key", byokTrustRootName) + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForConfigMapKey(ctx, k8sClient, e2e_utils.InstallNamespace, "config-sigstore-keys", byokTrustRootName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for ConfigMap 'config-sigstore-keys' to have the %s key", byokTrustRootName) }) It("creates a Cluster image policy and adds it to the config-image-policies ConfigMap", func(ctx SpecContext) { - byokRenderedClusteImagePolicy, err := e2e_utils.RenderTemplate(clusterimagepolicyBYOKCrPath, map[string]string{ + byokRenderedClusteImagePolicy, err := e2e_utils.RenderTemplate(e2e_utils.ClusterimagepolicyBYOKCrPath, map[string]string{ "FULCIO_URL": e2e_utils.FulcioUrl(), "REKOR_URL": e2e_utils.RekorUrl(), "OIDC_ISSUER_URL": e2e_utils.OidcIssuerUrl(), @@ -180,21 +143,13 @@ var _ = Describe("policy-controller-operator common installation", Ordered, Seri Expect(err).NotTo(HaveOccurred()) Expect(e2e_utils.ApplyManifest(ctx, k8sClient, byokRenderedClusteImagePolicy, "")).To(Succeed()) - Eventually(func(ctx SpecContext) (string, error) { - cm := &corev1.ConfigMap{} - if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: e2e_utils.InstallNamespace, Name: "config-image-policies"}, cm); err != nil { - return "", err - } - val, ok := cm.Data[byokCIPName] - if !ok { - return "", fmt.Errorf("key not present yet") - } - return val, nil - }).WithContext(ctx).ShouldNot(BeEmpty(), "timed out waiting for ConfigMap 'config-image-policies' to have the %s key", byokCIPName) + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForConfigMapKey(ctx, k8sClient, e2e_utils.InstallNamespace, "config-image-policies", byokCIPName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for ConfigMap 'config-image-policies' to have the %s key", byokCIPName) }) It("verifies policy controller behavour", func(ctx SpecContext) { - e2e_utils.Verify(ctx, k8sClient, byokTestNS, byokImage) + e2e_utils.Verify(ctx, k8sClient, byokTestNS, byokImage, true) }) It("creates a TrustRoot and adds it to the sigstore-keys ConfigMap", func(ctx SpecContext) { @@ -204,7 +159,7 @@ var _ = Describe("policy-controller-operator common installation", Ordered, Seri serializedRepo, err := e2e_utils.TufMirrorFS(ctx) Expect(err).NotTo(HaveOccurred()) - stufRenderedTrustRoot, err := e2e_utils.RenderTemplate(trustRootSTUFCrPath, map[string]string{ + stufRenderedTrustRoot, err := e2e_utils.RenderTemplate(e2e_utils.TrustRootSTUFCrPath, map[string]string{ "TRUST_ROOT_NAME": stufTrustRootName, "TUFRoot": e2e_utils.Base64EncodeString(tufroot), "REPOSITORY": e2e_utils.Base64EncodeString(serializedRepo), @@ -212,22 +167,14 @@ var _ = Describe("policy-controller-operator common installation", Ordered, Seri Expect(err).NotTo(HaveOccurred()) Expect(e2e_utils.ApplyManifest(ctx, k8sClient, stufRenderedTrustRoot, "")).To(Succeed()) - Eventually(func(ctx SpecContext) (string, error) { - cm := &corev1.ConfigMap{} - if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: e2e_utils.InstallNamespace, Name: "config-sigstore-keys"}, cm); err != nil { - return "", err - } - val, ok := cm.Data[stufTrustRootName] - if !ok { - return "", fmt.Errorf("key not present yet") - } - return val, nil - }).WithContext(ctx).ShouldNot(BeEmpty(), "timed out waiting for ConfigMap 'config-sigstore-keys' to have the %s key", stufTrustRootName) + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForConfigMapKey(ctx, k8sClient, e2e_utils.InstallNamespace, "config-sigstore-keys", stufTrustRootName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for ConfigMap 'config-sigstore-keys' to have the %s key", stufTrustRootName) }) It("creates a Cluster image policy and adds it to the config-image-policies ConfigMap", func(ctx SpecContext) { - stufRenderedClusteImagePolicy, err := e2e_utils.RenderTemplate(clusterimagepolicySTUFCrPath, map[string]string{ + stufRenderedClusteImagePolicy, err := e2e_utils.RenderTemplate(e2e_utils.ClusterimagepolicySTUFCrPath, map[string]string{ "FULCIO_URL": e2e_utils.FulcioUrl(), "REKOR_URL": e2e_utils.RekorUrl(), "OIDC_ISSUER_URL": e2e_utils.OidcIssuerUrl(), @@ -240,20 +187,12 @@ var _ = Describe("policy-controller-operator common installation", Ordered, Seri Expect(err).NotTo(HaveOccurred()) Expect(e2e_utils.ApplyManifest(ctx, k8sClient, stufRenderedClusteImagePolicy, "")).To(Succeed()) - Eventually(func(ctx SpecContext) (string, error) { - cm := &corev1.ConfigMap{} - if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: e2e_utils.InstallNamespace, Name: "config-image-policies"}, cm); err != nil { - return "", err - } - val, ok := cm.Data[stufCIPName] - if !ok { - return "", fmt.Errorf("key not present yet") - } - return val, nil - }).WithContext(ctx).ShouldNot(BeEmpty(), "timed out waiting for ConfigMap 'config-image-policies' to have the %s key", stufCIPName) + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForConfigMapKey(ctx, k8sClient, e2e_utils.InstallNamespace, "config-image-policies", stufCIPName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for ConfigMap 'config-image-policies' to have the %s key", stufCIPName) }) It("verifies policy controller behavour", func(ctx SpecContext) { - e2e_utils.Verify(ctx, k8sClient, stufTestNS, stufTestImage) + e2e_utils.Verify(ctx, k8sClient, stufTestNS, stufTestImage, true) }) }) diff --git a/test/e2e/update_e2e_test.go b/test/e2e/update_e2e_test.go index 9a9f41d..48f8a29 100644 --- a/test/e2e/update_e2e_test.go +++ b/test/e2e/update_e2e_test.go @@ -1,3 +1,5 @@ +//go:build integration + package e2e import ( @@ -6,8 +8,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - e2e_utils "github.com/securesign/policy-controller-operator/test/e2e/utils" - appsv1 "k8s.io/api/apps/v1" + e2e_utils "github.com/securesign/policy-controller-operator/test/utils" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -28,14 +29,9 @@ var _ = Describe("policy-controller-operator update reconciliation", Ordered, Se }) It("ensuring deployment is ready", func(ctx SpecContext) { - dep := &appsv1.Deployment{} - e2e_utils.ExpectExists(e2e_utils.DeploymentName, e2e_utils.InstallNamespace, dep, k8sClient, ctx) - desired := *dep.Spec.Replicas - Eventually(func(ctx context.Context) int32 { - err := k8sClient.Get(ctx, client.ObjectKey{Namespace: e2e_utils.InstallNamespace, Name: e2e_utils.DeploymentName}, dep) - Expect(err).ToNot(HaveOccurred()) - return dep.Status.ReadyReplicas - }).WithContext(ctx).Should(Equal(desired), "timed out waiting for %d pods to be Ready in Deployment %q", desired, e2e_utils.DeploymentName) + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForDeploymentReady(ctx, k8sClient, e2e_utils.InstallNamespace, e2e_utils.DeploymentName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for Deployment %q to be ready", e2e_utils.DeploymentName) }) It("reconciles PolicyController", func(ctx SpecContext) { @@ -65,7 +61,7 @@ var _ = Describe("policy-controller-operator update reconciliation", Ordered, Se tufroot, err := e2e_utils.ResolveTufRoot(ctx) Expect(err).NotTo(HaveOccurred()) - renderedTrustRoot, err := e2e_utils.RenderTemplate(trustRootCommonCrPath, map[string]string{ + renderedTrustRoot, err := e2e_utils.RenderTemplate(e2e_utils.TrustRootCommonCrPath, map[string]string{ "TRUST_ROOT_NAME": updateTrustRootName, "TUFMirror": e2e_utils.TufUrl(), "TUFRoot": e2e_utils.Base64EncodeString(tufroot), @@ -73,17 +69,9 @@ var _ = Describe("policy-controller-operator update reconciliation", Ordered, Se Expect(err).NotTo(HaveOccurred()) Expect(e2e_utils.ApplyManifest(ctx, k8sClient, renderedTrustRoot, "")).To(Succeed()) - Eventually(func(ctx SpecContext) (string, error) { - cm := &corev1.ConfigMap{} - if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: e2e_utils.InstallNamespace, Name: "config-sigstore-keys"}, cm); err != nil { - return "", err - } - val, ok := cm.Data[updateTrustRootName] - if !ok { - return "", fmt.Errorf("key not present yet") - } - return val, nil - }).WithContext(ctx).ShouldNot(BeEmpty(), "timed out waiting for ConfigMap 'config-sigstore-keys' to have the %s key", updateTrustRootName) + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForConfigMapKey(ctx, k8sClient, e2e_utils.InstallNamespace, "config-sigstore-keys", updateTrustRootName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for ConfigMap 'config-sigstore-keys' to have the %s key", updateTrustRootName) var trustRootGeneration int64 Eventually(func(ctx SpecContext) (int64, error) { @@ -137,7 +125,7 @@ var _ = Describe("policy-controller-operator update reconciliation", Ordered, Se }) It("reconciles ClusterImagePolicy", func(ctx SpecContext) { - commonRenderedClusteImagePolicy, err := e2e_utils.RenderTemplate(clusterimagepolicyCommonCrPath, map[string]string{ + commonRenderedClusteImagePolicy, err := e2e_utils.RenderTemplate(e2e_utils.ClusterimagepolicyCommonCrPath, map[string]string{ "FULCIO_URL": e2e_utils.FulcioUrl(), "REKOR_URL": e2e_utils.RekorUrl(), "OIDC_ISSUER_URL": e2e_utils.OidcIssuerUrl(), @@ -150,17 +138,9 @@ var _ = Describe("policy-controller-operator update reconciliation", Ordered, Se Expect(err).NotTo(HaveOccurred()) Expect(e2e_utils.ApplyManifest(ctx, k8sClient, commonRenderedClusteImagePolicy, "")).To(Succeed()) - Eventually(func(ctx SpecContext) (string, error) { - cm := &corev1.ConfigMap{} - if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: e2e_utils.InstallNamespace, Name: "config-image-policies"}, cm); err != nil { - return "", err - } - val, ok := cm.Data[updatedClusterImagePolicyName] - if !ok { - return "", fmt.Errorf("key not present yet") - } - return val, nil - }).WithContext(ctx).ShouldNot(BeEmpty(), "timed out waiting for ConfigMap 'config-image-policies' to have the %s key", updatedClusterImagePolicyName) + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForConfigMapKey(ctx, k8sClient, e2e_utils.InstallNamespace, "config-image-policies", updatedClusterImagePolicyName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for ConfigMap 'config-image-policies' to have the %s key", updatedClusterImagePolicyName) var cipGeneration int64 Eventually(func(ctx SpecContext) (int64, error) { @@ -182,7 +162,7 @@ var _ = Describe("policy-controller-operator update reconciliation", Ordered, Se }).WithContext(ctx).Should(BeNumerically(">", int64(0))) updatedSubject := fmt.Sprintf("%s-updated", e2e_utils.OidcIssuerSubject()) - updatedClusterImagePolicy, err := e2e_utils.RenderTemplate(clusterimagepolicyCommonCrPath, map[string]string{ + updatedClusterImagePolicy, err := e2e_utils.RenderTemplate(e2e_utils.ClusterimagepolicyCommonCrPath, map[string]string{ "FULCIO_URL": e2e_utils.FulcioUrl(), "REKOR_URL": e2e_utils.RekorUrl(), "OIDC_ISSUER_URL": e2e_utils.OidcIssuerUrl(), diff --git a/test/e2e_upgrade/e2e_suite_upgrade_test.go b/test/e2e_upgrade/e2e_suite_upgrade_test.go new file mode 100644 index 0000000..2f7b5c7 --- /dev/null +++ b/test/e2e_upgrade/e2e_suite_upgrade_test.go @@ -0,0 +1,88 @@ +//go:build upgrade + +package e2e + +import ( + "context" + "fmt" + "os" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/format" + e2e_utils "github.com/securesign/policy-controller-operator/test/utils" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/clientcmd" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/e2e-framework/klient/conf" +) + +var ( + k8sClient client.Client + ctx context.Context + scheme = runtime.NewScheme() +) + +const ( + fromChannelVar = "UPGRADE_FROM_CHANNEL" + toChannelVar = "UPGRADE_TO_CHANNEL" + channelDefault = "stable" + + fromOperatorIndexImageEnv = "UPGRADE_FROM_OPERATOR_INDEX_IMAGE" + toOperatorIndexImageEnv = "UPGRADE_TO_OPERATOR_INDEX_IMAGE" + defaultOperatorIndexImage = "registry.redhat.io/redhat/redhat-operator-index:v4.19" +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) +} + +func TestE2e(t *testing.T) { + RegisterFailHandler(Fail) + log.SetLogger(GinkgoLogr) + SetDefaultEventuallyTimeout(10 * time.Minute) + EnforceDefaultTimeoutsWhenUsingContexts() + RunSpecs(t, "Policy Controller Upgrade E2E Suite") + + format.MaxLength = 0 +} + +var _ = SynchronizedBeforeSuite(func() []byte { + kubeconfig := conf.ResolveKubeConfigFile() + data, err := os.ReadFile(kubeconfig) + Expect(err).NotTo(HaveOccurred()) + return data +}, func(data []byte) { + restCfg, err := clientcmd.RESTConfigFromKubeConfig(data) + Expect(err).NotTo(HaveOccurred()) + + k8sClient, err = client.New(restCfg, client.Options{Scheme: scheme}) + Expect(err).NotTo(HaveOccurred()) + + ctx = context.Background() + + fmt.Println(">>> Running upgrade tests with the following parameters:") + fmt.Printf(" %-24s %s\n", "From Channel:", e2e_utils.EnvOrDefault(fromChannelVar, channelDefault)) + fmt.Printf(" %-24s %s\n", "To Channel:", e2e_utils.EnvOrDefault(toChannelVar, channelDefault)) + fmt.Printf(" %-24s %s\n", "Package:", e2e_utils.PackageName) + fmt.Printf(" %-24s %s\n", "From index image:", e2e_utils.EnvOrDefault(fromOperatorIndexImageEnv, defaultOperatorIndexImage)) + + toIndexImage, ok := os.LookupEnv(toOperatorIndexImageEnv) + Expect(ok).To(BeTrue(), "required env var %s must be set to trigger an upgrade", toOperatorIndexImageEnv) + Expect(toIndexImage).NotTo(BeEmpty(), "required env var %s must be non-empty to trigger an upgrade", toOperatorIndexImageEnv) + fmt.Printf(" %-24s %s\n", "To index image:", toIndexImage) + + fmt.Printf(" %-22s %s\n", "RHTAS Install Namespace:", e2e_utils.RhtasInstallNamespace()) + fmt.Printf(" %-24s %s\n", "TUF URL:", e2e_utils.TufUrl()) + fmt.Printf(" %-24s %s\n", "TSA URL:", e2e_utils.TsaUrl()) + fmt.Printf(" %-24s %s\n", "Rekor URL:", e2e_utils.RekorUrl()) + fmt.Printf(" %-24s %s\n", "Fulcio URL:", e2e_utils.FulcioUrl()) + fmt.Printf(" %-24s %s\n", "OIDC Issuer URL:", e2e_utils.OidcIssuerUrl()) + fmt.Printf(" %-24s %s\n", "OIDC Issuer Subject:", e2e_utils.OidcIssuerSubject()) + fmt.Printf(" %-24s %s\n", "Inject CA:", e2e_utils.InjectCA()) +}) diff --git a/test/e2e_upgrade/upgrade_test.go b/test/e2e_upgrade/upgrade_test.go new file mode 100644 index 0000000..dbbe16a --- /dev/null +++ b/test/e2e_upgrade/upgrade_test.go @@ -0,0 +1,319 @@ +//go:build upgrade + +package e2e + +import ( + "context" + "fmt" + "strconv" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + e2e_utils "github.com/securesign/policy-controller-operator/test/utils" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const ( + operatorInstallNS = "policy-controller-operator" + operatorSubName = "policy-controller-operator" + + upgradeTestNS = "pco-upgrade-e2e" + upgradeTestImageEnv = "UPGRADE_TEST_IMAGE" + postUpgradeTestENV = "POST_UPGRADE_TEST_IMAGE" + upgradetrustRootName = "upgrade-install-trust-root" + upgradeCIPName = "upgrade-install-cluster-image-policy" + + upgradeOperatorGroupName = "pco-upgrade-operator-group" + + catalogSourceNamespace = "openshift-marketplace" + upgradeFromCatalogSourceName = "pco-upgrade-catalog-from" + upgradeToCatalogSourceName = "pco-upgrade-catalog-to" +) + +var ( + upgradeTestImage string + postUpgradeTestImage string + injectCA bool + + csvBefore string + csvAfter string + + operatorDeploymentsBefore []string + operatorDeploymentsAfter []string +) + +var _ = Describe("Operator upgrade", Ordered, func() { + AfterAll(func(ctx SpecContext) { + installedCSV, err := e2e_utils.GetCSVName(ctx, k8sClient, operatorInstallNS, operatorSubName) + Expect(err).NotTo(HaveOccurred()) + Expect(installedCSV).NotTo(BeEmpty()) + + Expect(e2e_utils.DeleteResource(ctx, k8sClient, schema.GroupVersionKind{Group: "policy.sigstore.dev", Version: "v1beta1", Kind: "ClusterImagePolicy"}, upgradeCIPName, "")).To(Succeed()) + Expect(e2e_utils.DeleteResource(ctx, k8sClient, schema.GroupVersionKind{Group: "policy.sigstore.dev", Version: "v1alpha1", Kind: "TrustRoot"}, upgradetrustRootName, "")).To(Succeed()) + Expect(e2e_utils.DeleteResource(ctx, k8sClient, schema.GroupVersionKind{Group: "rhtas.charts.redhat.com", Version: "v1alpha1", Kind: "PolicyController"}, "policycontroller-sample", e2e_utils.InstallNamespace)).To(Succeed()) + Expect(e2e_utils.DeleteResource(ctx, k8sClient, schema.GroupVersionKind{Group: "operators.coreos.com", Version: "v1alpha1", Kind: "CatalogSource"}, upgradeToCatalogSourceName, catalogSourceNamespace)).To(Succeed()) + Expect(e2e_utils.DeleteResource(ctx, k8sClient, schema.GroupVersionKind{Group: "operators.coreos.com", Version: "v1alpha1", Kind: "CatalogSource"}, upgradeFromCatalogSourceName, catalogSourceNamespace)).To(Succeed()) + Expect(e2e_utils.DeleteResource(ctx, k8sClient, schema.GroupVersionKind{Group: "operators.coreos.com", Version: "v1alpha1", Kind: "Subscription"}, operatorSubName, operatorInstallNS)).To(Succeed()) + Expect(e2e_utils.DeleteResource(ctx, k8sClient, schema.GroupVersionKind{Group: "operators.coreos.com", Version: "v1alpha1", Kind: "ClusterServiceVersion"}, installedCSV, operatorInstallNS)).To(Succeed()) + Expect(e2e_utils.DeleteResource(ctx, k8sClient, schema.GroupVersionKind{Group: "operators.coreos.com", Version: "v1", Kind: "OperatorGroup"}, upgradeOperatorGroupName, operatorInstallNS)).To(Succeed()) + Expect(e2e_utils.DeleteResource(ctx, k8sClient, schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}, e2e_utils.InstallNamespace, "")) + }) + + BeforeAll(func(ctx SpecContext) { + upgradeTestImage = e2e_utils.PrepareImage(ctx, upgradeTestImageEnv) + postUpgradeTestImage = e2e_utils.PrepareImage(ctx, postUpgradeTestENV) + + parsedInjectCA, err := strconv.ParseBool(strings.TrimSpace(e2e_utils.InjectCA())) + Expect(err).NotTo(HaveOccurred()) + injectCA = parsedInjectCA + + Expect(k8sClient.Create(ctx, &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: e2e_utils.InstallNamespace}, + })).To(SatisfyAny(Succeed(), MatchError(ContainSubstring("already exists")))) + }) + + It("creates an OperatorGroup and a CatalogSource", func(ctx SpecContext) { + By("ensuring an OperatorGroup exists in " + operatorInstallNS) + renderedOperatorGroup, err := e2e_utils.RenderTemplate(e2e_utils.OperatorGroupPath, map[string]string{ + "NAME": upgradeOperatorGroupName, + "NS": operatorInstallNS, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(e2e_utils.ApplyManifest(ctx, k8sClient, renderedOperatorGroup, "")).To(Succeed()) + + By("creating a CatalogSource in " + catalogSourceNamespace) + renderedCatalogSource, err := e2e_utils.RenderTemplate(e2e_utils.CatalogSourcePath, map[string]string{ + "NAME": upgradeFromCatalogSourceName, + "NS": catalogSourceNamespace, + "INDEX_IMAGE": e2e_utils.EnvOrDefault(fromOperatorIndexImageEnv, defaultOperatorIndexImage), + }) + Expect(err).NotTo(HaveOccurred()) + Expect(e2e_utils.ApplyManifest(ctx, k8sClient, renderedCatalogSource, "")).To(Succeed()) + + By("waiting for the CatalogSource to become READY") + Eventually(func(ctx context.Context) (string, error) { + catalogSource, err := e2e_utils.GetCatalogSource(ctx, k8sClient, catalogSourceNamespace, upgradeFromCatalogSourceName) + if err != nil { + return "", err + } + return e2e_utils.GetCatalogSourceLastObservedState(catalogSource) + }).WithContext(ctx).Should(Equal("READY"), "timed out waiting for CatalogSource %s/%s to be READY", catalogSourceNamespace, upgradeFromCatalogSourceName) + }) + + It("creates a Subscription for PCO", func(ctx SpecContext) { + renderedSubscription, err := e2e_utils.RenderTemplate(e2e_utils.SubscriptionPath, map[string]string{ + "NAME": operatorSubName, + "NS": operatorInstallNS, + "CHANNEL": e2e_utils.EnvOrDefault(fromChannelVar, channelDefault), + "SOURCE": upgradeFromCatalogSourceName, + "SOURCE_NAMESPACE": catalogSourceNamespace, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(e2e_utils.ApplyManifest(ctx, k8sClient, renderedSubscription, "")).To(Succeed()) + + By("waiting for Subscription to report status.installedCSV") + Eventually(func(ctx context.Context) (string, error) { + val, err := e2e_utils.GetCSVName(ctx, k8sClient, operatorInstallNS, operatorSubName) + if err != nil { + return "", err + } + csvBefore = val + return val, nil + }).WithContext(ctx).ShouldNot(BeEmpty(), "timed out waiting for Subscription %s/%s to have status.installedCSV", operatorInstallNS, operatorSubName) + Expect(csvBefore).NotTo(BeEmpty()) + + By("waiting for installed CSV to be Succeeded") + Eventually(func(ctx context.Context) (string, error) { + csv, err := e2e_utils.GetCSV(ctx, k8sClient, operatorInstallNS, csvBefore) + if err != nil { + return "", err + } + return e2e_utils.GetCSVPhase(csv) + }).WithContext(ctx).Should(Equal("Succeeded"), "timed out waiting for CSV %s/%s to be Succeeded", operatorInstallNS, csvBefore) + + csv, err := e2e_utils.GetCSV(ctx, k8sClient, operatorInstallNS, csvBefore) + Expect(err).NotTo(HaveOccurred()) + operatorDeploymentsBefore, err = e2e_utils.GetCSVDeploymentNames(csv) + Expect(err).NotTo(HaveOccurred()) + Expect(operatorDeploymentsBefore).NotTo(BeEmpty()) + + By("waiting for operator deployment to be Ready: " + strings.Join(operatorDeploymentsBefore, ", ")) + for _, deployName := range operatorDeploymentsBefore { + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForDeploymentReady(ctx, k8sClient, operatorInstallNS, deployName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for Deployment %q to be ready", deployName) + } + }) + + It("creates the policy controller resource", func(ctx SpecContext) { + By("applying the operator bundle: " + e2e_utils.PolicyControllerCRPath) + renderedPolicyController, err := e2e_utils.RenderTemplate(e2e_utils.PolicyControllerCRPath, map[string]string{ + "NS": e2e_utils.InstallNamespace, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(e2e_utils.ApplyManifest(ctx, k8sClient, renderedPolicyController, "")).To(Succeed()) + + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForDeploymentReady(ctx, k8sClient, e2e_utils.InstallNamespace, e2e_utils.DeploymentName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for Deployment %q to be ready", e2e_utils.DeploymentName) + }) + + It("injects CA into the policy-controller deployment", func(ctx SpecContext) { + if !injectCA { + return + } + + By("injecting CA") + Expect(e2e_utils.InjectCAIntoDeployment(ctx, k8sClient, e2e_utils.DeploymentName, e2e_utils.InstallNamespace)).To(Succeed()) + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForConfigMapKey(ctx, k8sClient, e2e_utils.InstallNamespace, "trusted-ca-bundle", "ca-bundle.crt") + }).WithContext(ctx).Should(Succeed(), "trusted-ca-bundle never got its ca-bundle.crt") + + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForDeploymentReady(ctx, k8sClient, e2e_utils.InstallNamespace, e2e_utils.DeploymentName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for Deployment %q to be ready after CA injection", e2e_utils.DeploymentName) + }) + + It("creates a TrustRoot and adds it to the sigstore-keys ConfigMap", func(ctx SpecContext) { + tufroot, err := e2e_utils.ResolveTufRoot(ctx) + Expect(err).NotTo(HaveOccurred()) + + upgradeRenderedTrustRoot, err := e2e_utils.RenderTemplate(e2e_utils.TrustRootCommonCrPath, map[string]string{ + "TRUST_ROOT_NAME": upgradetrustRootName, + "TUFMirror": e2e_utils.TufUrl(), + "TUFRoot": e2e_utils.Base64EncodeString(tufroot), + }) + Expect(err).NotTo(HaveOccurred()) + Expect(e2e_utils.ApplyManifest(ctx, k8sClient, upgradeRenderedTrustRoot, "")).To(Succeed()) + + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForConfigMapKey(ctx, k8sClient, e2e_utils.InstallNamespace, "config-sigstore-keys", upgradetrustRootName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for ConfigMap 'config-sigstore-keys' to have the %s key", upgradetrustRootName) + }) + + It("creates a Cluster image policy and adds it to the config-image-policies ConfigMap", func(ctx SpecContext) { + upgradeRenderedClusterImagePolicy, err := e2e_utils.RenderTemplate(e2e_utils.ClusterimagepolicyCommonCrPath, map[string]string{ + "FULCIO_URL": e2e_utils.FulcioUrl(), + "REKOR_URL": e2e_utils.RekorUrl(), + "OIDC_ISSUER_URL": e2e_utils.OidcIssuerUrl(), + "OIDC_ISSUER_SUBJECT": e2e_utils.OidcIssuerSubject(), + "TEST_IMAGE": upgradeTestImage, + "TEST_IMAGE_PREFIX": e2e_utils.ImageRepoPrefix(upgradeTestImage), + "TRUST_ROOT_REF": upgradetrustRootName, + "CIP_NAME": upgradeCIPName, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(e2e_utils.ApplyManifest(ctx, k8sClient, upgradeRenderedClusterImagePolicy, "")).To(Succeed()) + + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForConfigMapKey(ctx, k8sClient, e2e_utils.InstallNamespace, "config-image-policies", upgradeCIPName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for ConfigMap 'config-image-policies' to have the %s key", upgradeCIPName) + }) + + It("verifies policy controller behavour", func(ctx SpecContext) { + e2e_utils.Verify(ctx, k8sClient, upgradeTestNS, upgradeTestImage, true) + }) + + It("upgrades the PCO", func(ctx SpecContext) { + By("creating a CatalogSource") + renderedCatalogSource, err := e2e_utils.RenderTemplate(e2e_utils.CatalogSourcePath, map[string]string{ + "NAME": upgradeToCatalogSourceName, + "NS": catalogSourceNamespace, + "INDEX_IMAGE": e2e_utils.EnvOrDefault(toOperatorIndexImageEnv, ""), + }) + Expect(err).NotTo(HaveOccurred()) + Expect(e2e_utils.ApplyManifest(ctx, k8sClient, renderedCatalogSource, "")).To(Succeed()) + + Eventually(func(ctx context.Context) (string, error) { + catalogSource, err := e2e_utils.GetCatalogSource(ctx, k8sClient, catalogSourceNamespace, upgradeToCatalogSourceName) + if err != nil { + return "", err + } + return e2e_utils.GetCatalogSourceLastObservedState(catalogSource) + }).WithContext(ctx).Should(Equal("READY"), "timed out waiting for CatalogSource %s/%s to be READY", catalogSourceNamespace, upgradeToCatalogSourceName) + + By("updating the Subscription") + Eventually(func(ctx context.Context) error { + return e2e_utils.UpdateSubscriptionSourceAndChannel(ctx, k8sClient, operatorInstallNS, operatorSubName, + upgradeToCatalogSourceName, e2e_utils.EnvOrDefault(toChannelVar, channelDefault)) + }).WithContext(ctx).Should(Succeed(), "timed out updating Subscription %s/%s", operatorInstallNS, operatorSubName) + + By("waiting for Subscription to report a new Succeeded installedCSV") + Eventually(func(ctx context.Context) error { + val, err := e2e_utils.GetCSVName(ctx, k8sClient, operatorInstallNS, operatorSubName) + if err != nil { + return err + } + if val == csvBefore { + return fmt.Errorf("installedCSV has not changed yet (still %q)", csvBefore) + } + + csv, err := e2e_utils.GetCSV(ctx, k8sClient, operatorInstallNS, val) + if err != nil { + return err + } + phase, err := e2e_utils.GetCSVPhase(csv) + if err != nil { + return err + } + if phase != "Succeeded" { + return fmt.Errorf("CSV %q phase is %q", val, phase) + } + + csvAfter = val + operatorDeploymentsAfter, err = e2e_utils.GetCSVDeploymentNames(csv) + if err != nil { + return err + } + if len(operatorDeploymentsAfter) == 0 { + return fmt.Errorf("no deployments found in CSV %q", val) + } + return nil + }).WithContext(ctx).Should(Succeed(), "timed out waiting for upgraded CSV to differ from %q and be Succeeded", csvBefore) + Expect(csvAfter).NotTo(Equal(csvBefore)) + + By("waiting for operator deployment to be Ready after upgrade: " + strings.Join(operatorDeploymentsAfter, ", ")) + for _, deployName := range operatorDeploymentsAfter { + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForDeploymentReady(ctx, k8sClient, operatorInstallNS, deployName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for Deployment %q to be ready after upgrade", deployName) + } + }) + + It("verifies policy controller behavour after upgrade", func(ctx SpecContext) { + if injectCA { + By("re-injecting CA after upgrade") + Expect(e2e_utils.InjectCAIntoDeployment(ctx, k8sClient, e2e_utils.DeploymentName, e2e_utils.InstallNamespace)).To(Succeed()) + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForDeploymentReady(ctx, k8sClient, e2e_utils.InstallNamespace, e2e_utils.DeploymentName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for Deployment %q to be ready after CA injection", e2e_utils.DeploymentName) + } + + By("allowing already signed images through") + e2e_utils.Verify(ctx, k8sClient, upgradeTestNS, upgradeTestImage, false) + + By("testing a new image") + upgradeRenderedClusterImagePolicy, err := e2e_utils.RenderTemplate(e2e_utils.ClusterimagepolicyCommonCrPath, map[string]string{ + "FULCIO_URL": e2e_utils.FulcioUrl(), + "REKOR_URL": e2e_utils.RekorUrl(), + "OIDC_ISSUER_URL": e2e_utils.OidcIssuerUrl(), + "OIDC_ISSUER_SUBJECT": e2e_utils.OidcIssuerSubject(), + "TEST_IMAGE": postUpgradeTestImage, + "TEST_IMAGE_PREFIX": e2e_utils.ImageRepoPrefix(postUpgradeTestImage), + "TRUST_ROOT_REF": upgradetrustRootName, + "CIP_NAME": upgradeCIPName, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(e2e_utils.ApplyManifest(ctx, k8sClient, upgradeRenderedClusterImagePolicy, "")).To(Succeed()) + + Eventually(func(ctx context.Context) error { + return e2e_utils.WaitForConfigMapKey(ctx, k8sClient, e2e_utils.InstallNamespace, "config-image-policies", upgradeCIPName) + }).WithContext(ctx).Should(Succeed(), "timed out waiting for ConfigMap 'config-image-policies' to have the %s key", upgradeCIPName) + + By("rejecting unsigned images") + e2e_utils.Verify(ctx, k8sClient, upgradeTestNS, postUpgradeTestImage, true) + }) +}) diff --git a/test/e2e/utils/command.go b/test/utils/command.go similarity index 100% rename from test/e2e/utils/command.go rename to test/utils/command.go diff --git a/test/e2e/utils/common.go b/test/utils/common.go similarity index 55% rename from test/e2e/utils/common.go rename to test/utils/common.go index f42694a..0878a3c 100644 --- a/test/e2e/utils/common.go +++ b/test/utils/common.go @@ -11,11 +11,26 @@ import ( "text/template" . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/controller-runtime/pkg/client" ) const ( + SubscriptionPath = "../utils/custom_resources/subscription/subscription.yaml.tpl" + CatalogSourcePath = "../utils/custom_resources/catalog_source/catalog_source.yaml.tpl" + OperatorGroupPath = "../utils/custom_resources/operator_group/operator_group.yaml.tpl" + PolicyControllerCRPath = "../utils/custom_resources/policy_controller/common_policy_controller.yaml.tpl" + TrustRootCommonCrPath = "../utils/custom_resources/trust_roots/common_trust_root.yaml.tpl" + ClusterimagepolicyCommonCrPath = "../utils/custom_resources/cluster_image_policies/common_cluster_image_policy.yaml.tpl" + + TrustRootBYOKCrPath = "../utils/custom_resources/trust_roots/byok_trust_root.yaml.tpl" + ClusterimagepolicyBYOKCrPath = "../utils/custom_resources/cluster_image_policies/common_cluster_image_policy.yaml.tpl" + + TrustRootSTUFCrPath = "../utils/custom_resources/trust_roots/stuf_trust_root.yaml.tpl" + ClusterimagepolicySTUFCrPath = "../utils/custom_resources/cluster_image_policies/common_cluster_image_policy.yaml.tpl" + InstallNamespace = "policy-controller-operator" + PackageName = "policy-controller-operator" DeploymentName = "policycontroller-sample-policy-controller-webhook" ValidatingWebhookName = "policy.rhtas.com" MutatingWebhookName = "policy.rhtas.com" @@ -63,3 +78,17 @@ func nindent(n int, s string) string { func Base64EncodeString(src []byte) string { return base64.StdEncoding.EncodeToString(src) } + +func GetNestedString(obj map[string]any, path ...string) (string, error) { + val, found, err := unstructured.NestedString(obj, path...) + if err != nil { + return "", err + } + if !found { + return "", fmt.Errorf("%s not present yet", strings.Join(path, ".")) + } + if val == "" { + return "", fmt.Errorf("%s is empty", strings.Join(path, ".")) + } + return val, nil +} diff --git a/test/e2e/utils/cosign.go b/test/utils/cosign.go similarity index 100% rename from test/e2e/utils/cosign.go rename to test/utils/cosign.go diff --git a/test/utils/custom_resources/catalog_source/catalog_source.yaml.tpl b/test/utils/custom_resources/catalog_source/catalog_source.yaml.tpl new file mode 100644 index 0000000..53436c6 --- /dev/null +++ b/test/utils/custom_resources/catalog_source/catalog_source.yaml.tpl @@ -0,0 +1,10 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: CatalogSource +metadata: + name: {{ .NAME }} + namespace: {{ .NS }} +spec: + sourceType: grpc + image: {{ .INDEX_IMAGE }} + displayName: PCO Upgrade Test Catalog + publisher: policy-controller-operator-e2e diff --git a/test/e2e/custom_resources/cluster_image_policies/common_cluster_image_policy.yaml.tpl b/test/utils/custom_resources/cluster_image_policies/common_cluster_image_policy.yaml.tpl similarity index 100% rename from test/e2e/custom_resources/cluster_image_policies/common_cluster_image_policy.yaml.tpl rename to test/utils/custom_resources/cluster_image_policies/common_cluster_image_policy.yaml.tpl diff --git a/test/utils/custom_resources/operator_group/operator_group.yaml.tpl b/test/utils/custom_resources/operator_group/operator_group.yaml.tpl new file mode 100644 index 0000000..df7e3bc --- /dev/null +++ b/test/utils/custom_resources/operator_group/operator_group.yaml.tpl @@ -0,0 +1,7 @@ +apiVersion: operators.coreos.com/v1 +kind: OperatorGroup +metadata: + name: {{ .NAME }} + namespace: {{ .NS }} +spec: + targetNamespaces: [] diff --git a/test/e2e/custom_resources/policy_controller/common_policy_controller.yaml.tpl b/test/utils/custom_resources/policy_controller/common_policy_controller.yaml.tpl similarity index 100% rename from test/e2e/custom_resources/policy_controller/common_policy_controller.yaml.tpl rename to test/utils/custom_resources/policy_controller/common_policy_controller.yaml.tpl diff --git a/test/utils/custom_resources/subscription/subscription.yaml.tpl b/test/utils/custom_resources/subscription/subscription.yaml.tpl new file mode 100644 index 0000000..711b339 --- /dev/null +++ b/test/utils/custom_resources/subscription/subscription.yaml.tpl @@ -0,0 +1,11 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: Subscription +metadata: + name: {{ .NAME }} + namespace: {{ .NS }} +spec: + channel: {{ .CHANNEL }} + installPlanApproval: Automatic + name: policy-controller-operator + source: {{ .SOURCE }} + sourceNamespace: {{ .SOURCE_NAMESPACE }} diff --git a/test/e2e/custom_resources/trust_roots/byok_trust_root.yaml.tpl b/test/utils/custom_resources/trust_roots/byok_trust_root.yaml.tpl similarity index 100% rename from test/e2e/custom_resources/trust_roots/byok_trust_root.yaml.tpl rename to test/utils/custom_resources/trust_roots/byok_trust_root.yaml.tpl diff --git a/test/e2e/custom_resources/trust_roots/common_trust_root.yaml.tpl b/test/utils/custom_resources/trust_roots/common_trust_root.yaml.tpl similarity index 100% rename from test/e2e/custom_resources/trust_roots/common_trust_root.yaml.tpl rename to test/utils/custom_resources/trust_roots/common_trust_root.yaml.tpl diff --git a/test/e2e/custom_resources/trust_roots/stuf_trust_root.yaml.tpl b/test/utils/custom_resources/trust_roots/stuf_trust_root.yaml.tpl similarity index 100% rename from test/e2e/custom_resources/trust_roots/stuf_trust_root.yaml.tpl rename to test/utils/custom_resources/trust_roots/stuf_trust_root.yaml.tpl diff --git a/test/e2e/utils/image.go b/test/utils/image.go similarity index 95% rename from test/e2e/utils/image.go rename to test/utils/image.go index 5e4875c..48ddd81 100644 --- a/test/e2e/utils/image.go +++ b/test/utils/image.go @@ -1,10 +1,10 @@ package e2e_utils import ( - "context" - "fmt" - "os" - "strings" + "context" + "fmt" + "os" + "strings" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/random" diff --git a/test/e2e/utils/kubernetes.go b/test/utils/kubernetes.go similarity index 76% rename from test/e2e/utils/kubernetes.go rename to test/utils/kubernetes.go index e8045ff..cc32f4c 100644 --- a/test/e2e/utils/kubernetes.go +++ b/test/utils/kubernetes.go @@ -25,6 +25,12 @@ const ( injectCA = "INJECT_CA" ) +var ( + GVKCatalogSource = schema.GroupVersionKind{Group: "operators.coreos.com", Version: "v1alpha1", Kind: "CatalogSource"} + GVKSubscription = schema.GroupVersionKind{Group: "operators.coreos.com", Version: "v1alpha1", Kind: "Subscription"} + GVKCSV = schema.GroupVersionKind{Group: "operators.coreos.com", Version: "v1alpha1", Kind: "ClusterServiceVersion"} +) + func InjectCA() string { return EnvOrDefault(injectCA, defaultInjectCA) } @@ -494,3 +500,112 @@ func CreateTestCronJob(ctx context.Context, k8sClient client.Client, ns, testIma } return k8sClient.Create(ctx, cronJob) } + +func WaitForDeploymentReady(ctx context.Context, c client.Client, ns, name string) error { + dep := &appsv1.Deployment{} + if err := c.Get(ctx, client.ObjectKey{Namespace: ns, Name: name}, dep); err != nil { + return err + } + desired := int32(1) + if dep.Spec.Replicas != nil { + desired = *dep.Spec.Replicas + } + if dep.Status.ReadyReplicas != desired { + return fmt.Errorf("ready %d/%d", dep.Status.ReadyReplicas, desired) + } + return nil +} + +func WaitForConfigMapKey(ctx context.Context, c client.Client, ns, name, key string) error { + cm := &corev1.ConfigMap{} + if err := c.Get(ctx, client.ObjectKey{Namespace: ns, Name: name}, cm); err != nil { + return err + } + val, ok := cm.Data[key] + if !ok || val == "" { + return fmt.Errorf("key not present yet") + } + return nil +} + +func GetUnstructured(ctx context.Context, c client.Client, gvk schema.GroupVersionKind, namespace, name string) (*unstructured.Unstructured, error) { + obj := &unstructured.Unstructured{} + obj.SetGroupVersionKind(gvk) + + if err := c.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, obj); err != nil { + return nil, err + } + return obj, nil +} + +func GetCatalogSource(ctx context.Context, c client.Client, ns, name string) (*unstructured.Unstructured, error) { + return GetUnstructured(ctx, c, GVKCatalogSource, ns, name) +} +func GetSubscription(ctx context.Context, c client.Client, ns, name string) (*unstructured.Unstructured, error) { + return GetUnstructured(ctx, c, GVKSubscription, ns, name) +} +func GetCSV(ctx context.Context, c client.Client, ns, name string) (*unstructured.Unstructured, error) { + return GetUnstructured(ctx, c, GVKCSV, ns, name) +} + +func GetCatalogSourceLastObservedState(cs *unstructured.Unstructured) (string, error) { + return GetNestedString(cs.Object, "status", "connectionState", "lastObservedState") +} + +func GetCSVPhase(csv *unstructured.Unstructured) (string, error) { + return GetNestedString(csv.Object, "status", "phase") +} + +func GetCSVName(ctx context.Context, c client.Client, ns, subName string) (string, error) { + sub, err := GetUnstructured(ctx, c, GVKSubscription, ns, subName) + if err != nil { + return "", err + } + val, err := GetNestedString(sub.Object, "status", "installedCSV") + if err != nil { + return "", fmt.Errorf("Subscription %s/%s: %w", ns, subName, err) + } + return val, nil +} + +func GetCSVDeploymentNames(csv *unstructured.Unstructured) ([]string, error) { + deployments, found, err := unstructured.NestedSlice(csv.Object, "spec", "install", "spec", "deployments") + if err != nil { + return nil, err + } + if !found || len(deployments) == 0 { + return nil, fmt.Errorf("spec.install.spec.deployments not present or empty") + } + + names := make([]string, 0, len(deployments)) + for i, raw := range deployments { + m, ok := raw.(map[string]any) + if !ok { + return nil, fmt.Errorf("unexpected type for deployments[%d]: %T", i, raw) + } + nameAny, ok := m["name"] + name, _ := nameAny.(string) + if !ok || name == "" { + return nil, fmt.Errorf("missing deployments[%d].name", i) + } + names = append(names, name) + } + return names, nil +} + +func UpdateSubscriptionSourceAndChannel(ctx context.Context, c client.Client, ns, name, source, channel string) error { + sub, err := GetSubscription(ctx, c, ns, name) + if err != nil { + return err + } + + orig := sub.DeepCopy() + if err := unstructured.SetNestedField(sub.Object, source, "spec", "source"); err != nil { + return err + } + if err := unstructured.SetNestedField(sub.Object, channel, "spec", "channel"); err != nil { + return err + } + + return c.Patch(ctx, sub, client.MergeFrom(orig)) +} diff --git a/test/e2e/utils/oidc.go b/test/utils/oidc.go similarity index 100% rename from test/e2e/utils/oidc.go rename to test/utils/oidc.go diff --git a/test/e2e/utils/rhtas.go b/test/utils/rhtas.go similarity index 100% rename from test/e2e/utils/rhtas.go rename to test/utils/rhtas.go diff --git a/test/e2e/utils/support.go b/test/utils/support.go similarity index 90% rename from test/e2e/utils/support.go rename to test/utils/support.go index 95a5bd6..ebed188 100644 --- a/test/e2e/utils/support.go +++ b/test/utils/support.go @@ -13,7 +13,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -func Verify(ctx SpecContext, k8sClient client.Client, namespace, testImage string) { +func Verify(ctx SpecContext, k8sClient client.Client, namespace, testImage string, assertRejectAndSign bool) { By("creating a test namespace") Expect(CreateTestNamespace(ctx, k8sClient, namespace)).NotTo(HaveOccurred()) @@ -91,16 +91,22 @@ func Verify(ctx SpecContext, k8sClient client.Client, namespace, testImage strin } } - assertReject("rejecting workload creation with unsigned image") - VerifyByCosign(ctx, testImage) - assertReject("still rejecting workload creation when only image is signed") - AttachProvenance(ctx, testImage) - assertReject("still rejecting workload creation when only image is signed") - AttachSBOM(ctx, testImage) + if assertRejectAndSign { + assertReject("rejecting workload creation with unsigned image") + VerifyByCosign(ctx, testImage) + assertReject("still rejecting workload creation when only image is signed") + AttachProvenance(ctx, testImage) + assertReject("still rejecting workload creation when only image is signed") + AttachSBOM(ctx, testImage) - for _, workload := range workloads { - By("allowing workload creation when image, provenance, and SBOM are all present") - Expect(workload.create(ctx)).To(Succeed()) + for _, workload := range workloads { + By("allowing workload creation when image, provenance, and SBOM are all present") + Expect(workload.create(ctx)).To(Succeed()) + } + } else { + for _, workload := range workloads { + Expect(workload.create(ctx)).To(Succeed()) + } } By("cleaning up test resources")