From e17fe5cf0bde22c1da0a8f7d5ded4c9295332db1 Mon Sep 17 00:00:00 2001 From: Tamar-Dinavetsky Date: Sun, 12 Jul 2026 16:53:54 +0300 Subject: [PATCH 1/2] test: add MTA-842 secrets migration test and VerifySecret framework helper --- e2e-tests/framework/test_helpers.go | 31 ++++++ .../tier1/mta_842_secrets_migration_test.go | 104 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 e2e-tests/tests/tier1/mta_842_secrets_migration_test.go diff --git a/e2e-tests/framework/test_helpers.go b/e2e-tests/framework/test_helpers.go index 5019b6a4..841f8fc7 100644 --- a/e2e-tests/framework/test_helpers.go +++ b/e2e-tests/framework/test_helpers.go @@ -1,6 +1,7 @@ package framework import ( + "encoding/json" "fmt" "log" "regexp" @@ -85,3 +86,33 @@ func ValidateClusterRBAC(kubectl KubectlRunner, bindings []ExpectedClusterRoleBi } return nil } + +// Fetches a secret by name from the given namespace and cluster, parses the JSON response, and verifies the type +// field matches the expected value. Returns a descriptive error if the secret cannot be fetched, the JSON cannot +// be parsed, or the type does not match. +func VerifySecret(kubectl KubectlRunner, namespace, secretName, expectedType string) error { + secretJson, err := kubectl.Run("get", "secret", secretName, "-n", namespace, "-o", "json") + if err != nil { + return fmt.Errorf("failed to get secret %s: %w", secretName, err) + } + + var secretObj map[string]any + if err := json.Unmarshal([]byte(secretJson), &secretObj); err != nil { + return fmt.Errorf("failed to parse secret %s JSON: %w", secretName, err) + } + + actualType, ok := secretObj["type"].(string) + if !ok { + return fmt.Errorf("secret %s has no type field", secretName) + } + if actualType != expectedType { + return fmt.Errorf("secret %s: expected type %q but got %q", secretName, expectedType, actualType) + } + data, ok := secretObj["data"].(map[string]any) + if !ok || len(data) == 0 { + return fmt.Errorf("secret %s has missing or empty data field", secretName) + } + + log.Printf("Secret verified: name=%s type=%s\n", secretName, actualType) + return nil +} diff --git a/e2e-tests/tests/tier1/mta_842_secrets_migration_test.go b/e2e-tests/tests/tier1/mta_842_secrets_migration_test.go new file mode 100644 index 00000000..722ed3aa --- /dev/null +++ b/e2e-tests/tests/tier1/mta_842_secrets_migration_test.go @@ -0,0 +1,104 @@ +package e2e + +import ( + "log" + "os" + "os/exec" + "path/filepath" + + "github.com/konveyor/crane/e2e-tests/config" + . "github.com/konveyor/crane/e2e-tests/framework" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Migrate namespace with multiple secret types", func() { + It("[MTA-842] all secret types are exported, transformed, and applied to target cluster", Label("tier1"), func() { + namespace := "crane-secrets-test" + + scenario := NewMigrationScenario( + namespace, + namespace, + config.K8sDeployBin, + config.CraneBin, + config.SourceContext, + config.TargetContext, + ) + kubectlSrc := scenario.KubectlSrc + kubectlTgt := scenario.KubectlTgt + + paths, err := NewScenarioPaths("crane-export-*") + Expect(err).NotTo(HaveOccurred()) + exportOpts := ExportOptions{Namespace: namespace, ExportDir: paths.ExportDir} + transformOpts := TransformOptions{ExportDir: paths.ExportDir, TransformDir: paths.TransformDir} + applyOpts := ApplyOptions{ExportDir: paths.ExportDir, TransformDir: paths.TransformDir, + OutputDir: paths.OutputDir} + DeferCleanup(func() { + By("Cleanup temp directory") + if paths.TempDir != "" { + log.Printf("Removing temp dir: %s\n", paths.TempDir) + if err := os.RemoveAll(paths.TempDir); err != nil { + log.Printf("cleanup: failed to remove temp dir %q: %v", paths.TempDir, err) + } + } + By("Cleanup source namespace and temp dir") + if _, err := kubectlSrc.Run("delete", "namespace", namespace, "--ignore-not-found=true"); err != nil { + log.Printf("cleanup: failed to delete source namespace: %v", err) + } + By("Cleanup target namespace and temp dir") + if _, err := kubectlTgt.Run("delete", "namespace", namespace, "--ignore-not-found=true"); err != nil { + log.Printf("cleanup: failed to delete target namespace: %v", err) + } + }) + + By("Create namespace on source") + Expect(kubectlSrc.CreateNamespace(namespace)).NotTo(HaveOccurred()) + + By("Create Opaque secret on source") + Expect(kubectlSrc.Run("create", "secret", "generic", "opaque-secret", "--from-literal=key=value", "-n", namespace)).Error().NotTo(HaveOccurred()) + + By("Generate TLS certificate and key") + certFile := filepath.Join(paths.TempDir, "tls.crt") + keyFile := filepath.Join(paths.TempDir, "tls.key") + cmd := exec.Command("openssl", "req", "-x509", "-newkey", "rsa:2048", "-keyout", keyFile, "-out", certFile, "-days", "1", "-nodes", "-subj", "/CN=test") + out, err := cmd.CombinedOutput() + Expect(err).NotTo(HaveOccurred(), "openssl output: %s", out) + + By("Create TLS secret on source") + Expect(kubectlSrc.Run("create", "secret", "tls", "tls-secret", "--cert="+certFile, "--key="+keyFile, "-n", namespace)).Error().NotTo(HaveOccurred()) + + By("Create docker-registry secret on source") + Expect(kubectlSrc.Run("create", "secret", "docker-registry", "docker-secret", "--docker-server=quay.io", "--docker-username=user", "--docker-password=pass", "-n", namespace)).Error().NotTo(HaveOccurred()) + + runner := scenario.Crane + runner.WorkDir = paths.TempDir + + By("Run crane export/transform/apply pipeline") + log.Printf("Running crane pipeline for namespace %s\n", namespace) + Expect(RunCranePipelineWithChecks(runner, exportOpts, transformOpts, applyOpts)).NotTo(HaveOccurred()) + log.Printf("Crane pipeline completed for namespace %s\n", namespace) + + By("Verify all secret manifests are present in output directory") + for _, secretName := range []string{"opaque-secret", "tls-secret", "docker-secret"} { + glob := filepath.Join(paths.OutputDir, "resources", namespace, "Secret__*_"+secretName+".yaml") + matches, err := filepath.Glob(glob) + Expect(err).NotTo(HaveOccurred()) + Expect(matches).NotTo(BeEmpty(), "expected manifest for secret "+secretName+" in output directory") + log.Printf("Secret manifest found in output: %s\n", matches) + } + + By("Apply rendered manifests to target") + log.Printf("Applying rendered manifests on target namespace %s from %s\n", namespace, paths.OutputDir) + Expect(ApplyOutputToTarget(kubectlTgt, namespace, paths.OutputDir)).NotTo(HaveOccurred()) + + By("Verify Opaque secret is present on target with correct type") + Expect(VerifySecret(kubectlTgt, namespace, "opaque-secret", "Opaque")).NotTo(HaveOccurred()) + + By("Verify tls secret is present on target with correct type") + Expect(VerifySecret(kubectlTgt, namespace, "tls-secret", "kubernetes.io/tls")).NotTo(HaveOccurred()) + + By("Verify docker secret is present on target with correct type") + Expect(VerifySecret(kubectlTgt, namespace, "docker-secret", "kubernetes.io/dockerconfigjson")).NotTo(HaveOccurred()) + + }) +}) From c3c4b171a9188aa476b262f97527dd0ed01fe5b7 Mon Sep 17 00:00:00 2001 From: Tamar-Dinavetsky Date: Sun, 12 Jul 2026 17:55:05 +0300 Subject: [PATCH 2/2] add MTA-842 secrets migration test and VerifySecret framework helper --- e2e-tests/framework/test_helpers.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e-tests/framework/test_helpers.go b/e2e-tests/framework/test_helpers.go index 841f8fc7..490de75d 100644 --- a/e2e-tests/framework/test_helpers.go +++ b/e2e-tests/framework/test_helpers.go @@ -103,14 +103,14 @@ func VerifySecret(kubectl KubectlRunner, namespace, secretName, expectedType str actualType, ok := secretObj["type"].(string) if !ok { - return fmt.Errorf("secret %s has no type field", secretName) + return fmt.Errorf("secret %s/%s: type field is not a string, got %T", namespace, secretName, secretObj["type"]) } if actualType != expectedType { - return fmt.Errorf("secret %s: expected type %q but got %q", secretName, expectedType, actualType) + return fmt.Errorf("secret %s/%s: expected type %q but got %q", namespace, secretName, expectedType, actualType) } data, ok := secretObj["data"].(map[string]any) if !ok || len(data) == 0 { - return fmt.Errorf("secret %s has missing or empty data field", secretName) + return fmt.Errorf("secret %s/%s: data field is missing or empty, got %T", namespace, secretName, secretObj["data"]) } log.Printf("Secret verified: name=%s type=%s\n", secretName, actualType)