From 8dc8a16a95768998ff8fd899d43ee2c18c460a2e Mon Sep 17 00:00:00 2001 From: mliptak0 Date: Tue, 23 Jun 2026 10:08:51 +0200 Subject: [PATCH 1/2] HYPERFLEET-1108 - feat: Introduce run ID for E2E test labeling and adapter resource tracking - Add E2E_RUN_ID environment variable support for labeling test resources across cluster and nodepool adapters - Implement GenerateRunID() with automatic UUID v7 fallback for local development and validation for Kubernetes label constraints - Update DeployAdapter() to inject run ID into Helm chart resources and adapter environment - Update adapter shell scripts to validate and propagate E2E_RUN_ID with length and format checks - Add run ID parameter (e2eRunId) to all adapter task configurations to enable resource tracking by test run - Update adapter task resource templates to include e2e.hyperfleet.io/run-id labels - Add --e2e-run-id CLI flag to test command for explicit run ID specification - Fix deployment YAML trailing newline issue Co-authored-by: Cursor --- cmd/hyperfleet-e2e/test/cmd.go | 12 +++++ deploy-scripts/deploy-clm.sh | 7 +++ deploy-scripts/lib/adapter.sh | 14 ++++++ pkg/e2e/suite.go | 14 +++++- pkg/helper/adapter.go | 20 ++++++-- pkg/helper/helper.go | 1 + pkg/helper/suite.go | 48 +++++++++++++++++++ .../cl-crash/adapter-task-config.yaml | 5 ++ .../cl-deployment/adapter-task-config.yaml | 6 ++- .../adapter-task-resource-deployment.yaml | 3 +- .../adapter-task-config.yaml | 9 ++-- .../cl-job/adapter-task-config.yaml | 6 ++- .../cl-job/adapter-task-resource-job.yaml | 4 ++ .../adapter-task-config.yaml | 8 +++- .../cl-m-wrong-ds/adapter-task-config.yaml | 8 +++- .../cl-m-wrong-nest/adapter-task-config.yaml | 8 +++- .../cl-maestro/adapter-task-config.yaml | 5 ++ .../adapter-task-resource-manifestwork.yaml | 3 ++ .../cl-namespace/adapter-task-config.yaml | 9 ++-- .../adapter-task-config.yaml | 9 ++-- .../cl-stuck/adapter-task-config.yaml | 5 ++ .../np-configmap/adapter-task-config.yaml | 4 ++ .../adapter-task-resource-configmap.yaml | 1 + 23 files changed, 177 insertions(+), 32 deletions(-) diff --git a/cmd/hyperfleet-e2e/test/cmd.go b/cmd/hyperfleet-e2e/test/cmd.go index 8d5f0e9..3415201 100644 --- a/cmd/hyperfleet-e2e/test/cmd.go +++ b/cmd/hyperfleet-e2e/test/cmd.go @@ -30,6 +30,7 @@ var args struct { junitReport string dryRun bool flakeAttempts int + e2eRunID string } func init() { @@ -48,6 +49,8 @@ func init() { "List matching specs without executing them") pfs.IntVar(&args.flakeAttempts, "flake-attempts", 1, "Number of attempts for flaky tests (1 = no retries, 3 = up to 2 retries)") + pfs.StringVar(&args.e2eRunID, "e2e-run-id", "", + "E2E run ID for labeling test resources (overrides E2E_RUN_ID env var; defaults to auto-generated UUID)") } func run(cmd *cobra.Command, argv []string) { @@ -88,6 +91,15 @@ func run(cmd *cobra.Command, argv []string) { os.Exit(1) } + // If --e2e-run-id was provided, inject it into the environment so + // GetE2ETestRunID() picks it up (env var takes precedence over auto-generation). + if args.e2eRunID != "" { + if err := os.Setenv("E2E_RUN_ID", args.e2eRunID); err != nil { + log.Printf("Failed to set E2E_RUN_ID: %v\n", err) + os.Exit(1) + } + } + e2e.SetSuiteConfig(cfg) exitCode := e2e.RunTests(cmd.Context()) diff --git a/deploy-scripts/deploy-clm.sh b/deploy-scripts/deploy-clm.sh index c338447..ad905f4 100755 --- a/deploy-scripts/deploy-clm.sh +++ b/deploy-scripts/deploy-clm.sh @@ -42,6 +42,7 @@ fi ACTION="${ACTION:-}" NAMESPACE="${NAMESPACE:-}" +export E2E_RUN_ID="${E2E_RUN_ID:-}" DRY_RUN="${DRY_RUN:-false}" VERBOSE="${VERBOSE:-false}" @@ -141,6 +142,8 @@ REQUIRED FLAGS: OPTIONAL FLAGS: --namespace Kubernetes namespace (default from .env: ${NAMESPACE}) + --e2e-run-id E2E run ID for resource labeling + Set automatically in prow CI via E2E_RUN_ID env var # Component Selection --skip-api Skip API installation @@ -256,6 +259,10 @@ parse_arguments() { NAMESPACE="$2" shift 2 ;; + --e2e-run-id) + export E2E_RUN_ID="$2" + shift 2 + ;; --skip-api) INSTALL_API=false shift diff --git a/deploy-scripts/lib/adapter.sh b/deploy-scripts/lib/adapter.sh index 6bd092d..5437476 100755 --- a/deploy-scripts/lib/adapter.sh +++ b/deploy-scripts/lib/adapter.sh @@ -167,6 +167,16 @@ install_adapter_instance() { return 1 fi + local e2e_run_id="${E2E_RUN_ID:-}" + if [[ -z "${e2e_run_id}" ]]; then + log_error "E2E_RUN_ID is not set. Pass --e2e-run-id to deploy-clm.sh or set the E2E_RUN_ID environment variable." + return 1 + fi + if (( ${#e2e_run_id} > 63 )); then + log_error "E2E_RUN_ID '${e2e_run_id}' is ${#e2e_run_id} characters, exceeds the 63-character Kubernetes label value limit." + return 1 + fi + if [[ "${DRY_RUN}" == "true" ]]; then log_info "[DRY-RUN] Would install adapter with:" log_info " Release name: ${release_name}" @@ -177,6 +187,7 @@ install_adapter_instance() { log_info " Subscription ID: ${subscription_id}" log_info " Topic: ${topic}" log_info " Dead Letter Topic: ${dead_letter_topic}" + log_info " E2E Run ID: ${e2e_run_id}" return 0 fi @@ -202,6 +213,9 @@ install_adapter_instance() { --set "broker.googlepubsub.subscriptionId=${subscription_id}" --set "broker.googlepubsub.topic=${topic}" --set "broker.googlepubsub.deadLetterTopic=${dead_letter_topic}" + --set "env[0].name=E2E_RUN_ID" + --set "env[0].value=${e2e_run_id}" + --set "labels.e2e\\.hyperfleet\\.io/run-id=${e2e_run_id}" --labels "adapter-resource-type=${resource_type},adapter-name=${adapter_name}" ) diff --git a/pkg/e2e/suite.go b/pkg/e2e/suite.go index 9c5aca4..120ffe8 100644 --- a/pkg/e2e/suite.go +++ b/pkg/e2e/suite.go @@ -38,10 +38,20 @@ var _ = ginkgo.BeforeSuite(func() { cfg.Display() - logger.Info("starting hyperfleet-e2e test suite - each test creates temporary resources") + // Generate unique run ID for this test suite execution + runID, err := helper.GetE2ETestRunID() + if err != nil { + log.Fatalf("Failed to generate run ID: %v", err) + } + helper.SetRunID(runID) + + logger.Info("starting hyperfleet-e2e test suite", + "run_id", runID, + "message", "each test creates temporary resources") }) var _ = ginkgo.AfterSuite(func() { + runID := helper.GetRunID() helper.ClearSuiteConfig() - logger.Info("test suite completed") + logger.Info("test suite completed", "run_id", runID) }) diff --git a/pkg/helper/adapter.go b/pkg/helper/adapter.go index a7a4b88..d2d5dcf 100644 --- a/pkg/helper/adapter.go +++ b/pkg/helper/adapter.go @@ -17,14 +17,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// generateRandomString generates a random alphanumeric string of the specified length -func generateRandomString(length int) string { +// randomAlphanumeric returns a random lowercase alphanumeric string of the given length. +func randomAlphanumeric(length int) string { const charset = "abcdefghijklmnopqrstuvwxyz0123456789" b := make([]byte, length) for i := range b { n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) if err != nil { - // Fallback: use current time nanoseconds for basic randomness b[i] = charset[(time.Now().UnixNano()+int64(i))%int64(len(charset))] } else { b[i] = charset[n.Int64()] @@ -191,6 +190,19 @@ func (h *Helper) DeployAdapter(ctx context.Context, opts AdapterDeploymentOption "--set", fmt.Sprintf("fullnameOverride=%s", releaseName), ) + // The run ID (h.RunID) is passed to Helm via --set labels.e2e\.hyperfleet\.io/run-id= to label deployed adapter + // and injected into the adapter container as env.E2E_RUN_ID so the adapter can read it via its task config. + if h.RunID != "" { + helmArgs = append(helmArgs, + "--set", fmt.Sprintf("labels.e2e\\.hyperfleet\\.io/run-id=%s", h.RunID), + "--set", "env[0].name=E2E_RUN_ID", + "--set", fmt.Sprintf("env[0].value=%s", h.RunID), + ) + logger.Info("injecting run ID into Helm chart", + "run_id", h.RunID, + "release_name", releaseName) + } + // Override image pull policy if set (e.g. IfNotPresent for local kind clusters) if policy := os.Getenv("IMAGE_PULL_POLICY"); policy != "" { helmArgs = append(helmArgs, "--set", fmt.Sprintf("image.pullPolicy=%s", policy)) @@ -323,7 +335,7 @@ func (h *Helper) cleanupClusterScopedResources(ctx context.Context, releaseName // outputDir is configured via OUTPUT_DIR env var or config file (defaults to "output") func (h *Helper) saveDiagnosticLogs(ctx context.Context, adapterName, releaseName, namespace string) { // Generate output directory with adapter name and random suffix - randomSuffix := generateRandomString(4) + randomSuffix := randomAlphanumeric(4) outputDir := filepath.Join(h.Cfg.OutputDir, fmt.Sprintf("%s-%s", adapterName, randomSuffix)) // Create output directory diff --git a/pkg/helper/helper.go b/pkg/helper/helper.go index 53b04f8..ad25290 100644 --- a/pkg/helper/helper.go +++ b/pkg/helper/helper.go @@ -23,6 +23,7 @@ type Helper struct { Client *client.HyperFleetClient K8sClient *k8sclient.Client MaestroClient *maestro.Client + RunID string } // TestDataPath resolves a relative path within the testdata directory diff --git a/pkg/helper/suite.go b/pkg/helper/suite.go index 0e61b14..b2565fc 100644 --- a/pkg/helper/suite.go +++ b/pkg/helper/suite.go @@ -1,9 +1,13 @@ package helper import ( + "fmt" "log" + "os" + "regexp" "sync" + "github.com/google/uuid" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" k8sclient "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client/kubernetes" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/config" @@ -13,6 +17,7 @@ var ( // suiteConfig is loaded once in cmd layer before tests start suiteConfig *config.Config configMutex sync.RWMutex + runID string ) // SetSuiteConfig sets the global suite configuration for the test suite @@ -36,6 +41,48 @@ func ClearSuiteConfig() { suiteConfig = nil } +// SetRunID sets the global run ID for the test suite +func SetRunID(id string) { + configMutex.Lock() + defer configMutex.Unlock() + runID = id +} + +// GetRunID returns the global run ID for the test suite +func GetRunID() string { + configMutex.RLock() + defer configMutex.RUnlock() + return runID +} + +// maxRunIDLength is the maximum allowed length for a run ID. +// Kubernetes label values are limited to 63 characters. +const maxRunIDLength = 63 + +// labelValueRegex matches valid Kubernetes label values per +// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set +var labelValueRegex = regexp.MustCompile(`^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$`) + +// GetE2ETestRunID returns the run identifier for this E2E test suite execution. +// It prefers the E2E_RUN_ID environment variable (set by CI/prow to the namespace name), +// and falls back to a generated UUID v7 when running locally. +func GetE2ETestRunID() (string, error) { + if id := os.Getenv("E2E_RUN_ID"); id != "" { + if len(id) > maxRunIDLength { + return "", fmt.Errorf("E2E_RUN_ID %q is %d characters, exceeds the %d-character Kubernetes label value limit", id, len(id), maxRunIDLength) + } + if !labelValueRegex.MatchString(id) { + return "", fmt.Errorf("E2E_RUN_ID %q contains characters invalid for a Kubernetes label value", id) + } + return id, nil + } + id, err := uuid.NewV7() + if err != nil { + return "", fmt.Errorf("failed to generate run ID: %w", err) + } + return id.String(), nil +} + // New creates a helper instance for testing // Creates a new helper per test func New() *Helper { @@ -67,6 +114,7 @@ func newHelper(cfg *config.Config) (*Helper, error) { Cfg: cfg, Client: cl, K8sClient: k8sClient, + RunID: GetRunID(), // MaestroClient is initialized lazily via GetMaestroClient() to avoid // unnecessary K8s API calls in test suites that don't use Maestro }, nil diff --git a/testdata/adapter-configs/cl-crash/adapter-task-config.yaml b/testdata/adapter-configs/cl-crash/adapter-task-config.yaml index 846ac7c..dac6331 100644 --- a/testdata/adapter-configs/cl-crash/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-crash/adapter-task-config.yaml @@ -6,6 +6,10 @@ params: source: "event.id" type: "string" required: true + - name: "e2eRunId" + source: "env.E2E_RUN_ID" + type: "string" + required: true preconditions: - name: "clusterStatus" @@ -49,6 +53,7 @@ resources: labels: hyperfleet.io/cluster-id: "{{ .clusterId }}" hyperfleet.io/cluster-name: "{{ .clusterName }}" + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" annotations: hyperfleet.io/generation: "{{ .generationSpec }}" discovery: diff --git a/testdata/adapter-configs/cl-deployment/adapter-task-config.yaml b/testdata/adapter-configs/cl-deployment/adapter-task-config.yaml index 406ae4e..01ea6a5 100644 --- a/testdata/adapter-configs/cl-deployment/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-deployment/adapter-task-config.yaml @@ -6,6 +6,10 @@ params: source: "event.id" type: "string" required: true + - name: "e2eRunId" + source: "env.E2E_RUN_ID" + type: "string" + required: true # Preconditions with valid operators and CEL expressions preconditions: @@ -35,7 +39,6 @@ preconditions: ? status.conditions.filter(c, c.type == "Reconciled")[0].last_transition_time : now() )).getSeconds() > 300 - - name: "clusterAdapterStatus" api_call: method: "GET" @@ -46,7 +49,6 @@ preconditions: capture: - name: "clusterJobStatus" field: "{.items[?(@.adapter=='cl-job')].conditions[?(@.type=='Available')].status}" - - name: "validationCheck" expression: | is_deleting || (clusterJobStatus == "True" && (clusterNotReconciled || clusterReconciledTTL)) diff --git a/testdata/adapter-configs/cl-deployment/adapter-task-resource-deployment.yaml b/testdata/adapter-configs/cl-deployment/adapter-task-resource-deployment.yaml index 6f66b37..1b54184 100644 --- a/testdata/adapter-configs/cl-deployment/adapter-task-resource-deployment.yaml +++ b/testdata/adapter-configs/cl-deployment/adapter-task-resource-deployment.yaml @@ -7,6 +7,7 @@ metadata: labels: hyperfleet.io/cluster-id: "{{ .clusterId }}" hyperfleet.io/resource-type: "deployment" + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" annotations: hyperfleet.io/generation: "{{ .generationSpec }}" spec: @@ -20,10 +21,10 @@ spec: labels: app: test hyperfleet.io/cluster-id: "{{ .clusterId }}" + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" spec: containers: - name: test image: nginx:latest ports: - containerPort: 80 - diff --git a/testdata/adapter-configs/cl-invalid-resource/adapter-task-config.yaml b/testdata/adapter-configs/cl-invalid-resource/adapter-task-config.yaml index 7309264..718f123 100644 --- a/testdata/adapter-configs/cl-invalid-resource/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-invalid-resource/adapter-task-config.yaml @@ -6,11 +6,10 @@ params: source: "event.id" type: "string" required: true - - name: "testRunId" - source: "env.TEST_RUN_ID" + - name: "e2eRunId" + source: "env.E2E_RUN_ID" type: "string" - required: false - default: "TEST_RUN_ID" + required: true - name: "ci" source: "env.CI" type: "string" @@ -62,7 +61,7 @@ resources: labels: hyperfleet.io/cluster-id: "{{ .clusterId }}" hyperfleet.io/cluster-name: "{{ .clusterName }}" - e2e.hyperfleet.io/test-run-id: "{{ .testRunId }}" + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" e2e.hyperfleet.io/ci: "{{ .ci }}" e2e.hyperfleet.io/managed-by: "test-framework" annotations: diff --git a/testdata/adapter-configs/cl-job/adapter-task-config.yaml b/testdata/adapter-configs/cl-job/adapter-task-config.yaml index a59aac1..651296c 100644 --- a/testdata/adapter-configs/cl-job/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-job/adapter-task-config.yaml @@ -6,6 +6,10 @@ params: source: "event.id" type: "string" required: true + - name: "e2eRunId" + source: "env.E2E_RUN_ID" + type: "string" + required: true # Preconditions with valid operators and CEL expressions preconditions: @@ -35,7 +39,6 @@ preconditions: ? status.conditions.filter(c, c.type == "Reconciled")[0].last_transition_time : now() )).getSeconds() > 300 - - name: "clusterAdapterStatus" api_call: method: "GET" @@ -46,7 +49,6 @@ preconditions: capture: - name: "clusterNamespaceStatus" field: "{.items[?(@.adapter=='cl-namespace')].data.namespace.status}" - - name: "validationCheck" expression: | is_deleting || (clusterNamespaceStatus == "Active" && (clusterNotReconciled || clusterReconciledTTL)) diff --git a/testdata/adapter-configs/cl-job/adapter-task-resource-job.yaml b/testdata/adapter-configs/cl-job/adapter-task-resource-job.yaml index bf61887..7a5ad01 100644 --- a/testdata/adapter-configs/cl-job/adapter-task-resource-job.yaml +++ b/testdata/adapter-configs/cl-job/adapter-task-resource-job.yaml @@ -8,11 +8,15 @@ metadata: hyperfleet.io/cluster-id: "{{ .clusterId }}" hyperfleet.io/resource-type: "job" app: test-job + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" annotations: hyperfleet.io/generation: "{{ .generationSpec }}" spec: backoffLimit: 0 template: + metadata: + labels: + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" spec: restartPolicy: Never containers: diff --git a/testdata/adapter-configs/cl-m-unreg-consumer/adapter-task-config.yaml b/testdata/adapter-configs/cl-m-unreg-consumer/adapter-task-config.yaml index 3c89ad6..8f522d1 100644 --- a/testdata/adapter-configs/cl-m-unreg-consumer/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-m-unreg-consumer/adapter-task-config.yaml @@ -17,6 +17,11 @@ params: source: "env.NAMESPACE" type: "string" + - name: "e2eRunId" + source: "env.E2E_RUN_ID" + type: "string" + required: true + # Preconditions with valid operators and CEL expressions preconditions: @@ -42,13 +47,11 @@ preconditions: - name: "placementClusterName" expression: "\"unregistered-consumer\"" # Points to non-existent consumer to test apply failure - # Structured conditions with valid operators conditions: - field: "reconciledConditionStatus" operator: "equals" value: "False" - - name: "validationCheck" # Valid CEL expression expression: | @@ -79,6 +82,7 @@ resources: hyperfleet.io/component: "infrastructure" hyperfleet.io/generation: "{{ .generation }}" hyperfleet.io/resource-group: "cluster-setup" + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" # Maestro-specific labels maestro.io/source-id: "{{ .adapter.name }}" diff --git a/testdata/adapter-configs/cl-m-wrong-ds/adapter-task-config.yaml b/testdata/adapter-configs/cl-m-wrong-ds/adapter-task-config.yaml index 791ffb3..7a6c48d 100644 --- a/testdata/adapter-configs/cl-m-wrong-ds/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-m-wrong-ds/adapter-task-config.yaml @@ -17,6 +17,11 @@ params: source: "env.NAMESPACE" type: "string" + - name: "e2eRunId" + source: "env.E2E_RUN_ID" + type: "string" + required: true + # Preconditions with valid operators and CEL expressions preconditions: @@ -42,13 +47,11 @@ preconditions: - name: "placementClusterName" expression: "\"cluster1\"" # TBC coming from placement adapter - # Structured conditions with valid operators conditions: - field: "reconciledConditionStatus" operator: "equals" value: "False" - - name: "validationCheck" # Valid CEL expression expression: | @@ -79,6 +82,7 @@ resources: hyperfleet.io/component: "infrastructure" hyperfleet.io/generation: "{{ .generation }}" hyperfleet.io/resource-group: "cluster-setup" + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" # Maestro-specific labels maestro.io/source-id: "{{ .adapter.name }}" diff --git a/testdata/adapter-configs/cl-m-wrong-nest/adapter-task-config.yaml b/testdata/adapter-configs/cl-m-wrong-nest/adapter-task-config.yaml index 43540c4..2207b93 100644 --- a/testdata/adapter-configs/cl-m-wrong-nest/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-m-wrong-nest/adapter-task-config.yaml @@ -17,6 +17,11 @@ params: source: "env.NAMESPACE" type: "string" + - name: "e2eRunId" + source: "env.E2E_RUN_ID" + type: "string" + required: true + # Preconditions with valid operators and CEL expressions preconditions: @@ -42,13 +47,11 @@ preconditions: - name: "placementClusterName" expression: "\"cluster1\"" # TBC coming from placement adapter - # Structured conditions with valid operators conditions: - field: "reconciledConditionStatus" operator: "equals" value: "False" - - name: "validationCheck" # Valid CEL expression expression: | @@ -79,6 +82,7 @@ resources: hyperfleet.io/component: "infrastructure" hyperfleet.io/generation: "{{ .generation }}" hyperfleet.io/resource-group: "cluster-setup" + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" # Maestro-specific labels maestro.io/source-id: "{{ .adapter.name }}" diff --git a/testdata/adapter-configs/cl-maestro/adapter-task-config.yaml b/testdata/adapter-configs/cl-maestro/adapter-task-config.yaml index 8978adf..99128df 100644 --- a/testdata/adapter-configs/cl-maestro/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-maestro/adapter-task-config.yaml @@ -17,6 +17,11 @@ params: source: "env.NAMESPACE" type: "string" + - name: "e2eRunId" + source: "env.E2E_RUN_ID" + type: "string" + required: true + # Preconditions with valid operators and CEL expressions preconditions: - name: "clusterStatus" diff --git a/testdata/adapter-configs/cl-maestro/adapter-task-resource-manifestwork.yaml b/testdata/adapter-configs/cl-maestro/adapter-task-resource-manifestwork.yaml index 87dd7a8..a2187a0 100644 --- a/testdata/adapter-configs/cl-maestro/adapter-task-resource-manifestwork.yaml +++ b/testdata/adapter-configs/cl-maestro/adapter-task-resource-manifestwork.yaml @@ -13,6 +13,7 @@ metadata: hyperfleet.io/component: "infrastructure" hyperfleet.io/generation: "{{ .generation }}" hyperfleet.io/resource-group: "cluster-setup" + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" # Maestro-specific labels maestro.io/source-id: "{{ .adapter.name }}" @@ -65,6 +66,7 @@ spec: app.kubernetes.io/instance: "{{ .adapter.name }}" app.kubernetes.io/name: cl-maestro app.kubernetes.io/transport: maestro + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" annotations: hyperfleet.io/generation: "{{ .generation }}" - apiVersion: v1 @@ -91,6 +93,7 @@ spec: app.kubernetes.io/name: cl-maestro app.kubernetes.io/version: 1.0.0 app.kubernetes.io/transport: maestro + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" annotations: hyperfleet.io/generation: "{{ .generation }}" diff --git a/testdata/adapter-configs/cl-namespace/adapter-task-config.yaml b/testdata/adapter-configs/cl-namespace/adapter-task-config.yaml index fc519bd..845bc42 100644 --- a/testdata/adapter-configs/cl-namespace/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-namespace/adapter-task-config.yaml @@ -6,11 +6,10 @@ params: source: "event.id" type: "string" required: true - - name: "testRunId" - source: "env.TEST_RUN_ID" + - name: "e2eRunId" + source: "env.E2E_RUN_ID" type: "string" - required: false - default: "TEST_RUN_ID" + required: true - name: "ci" source: "env.CI" type: "string" @@ -64,7 +63,7 @@ resources: labels: hyperfleet.io/cluster-id: "{{ .clusterId }}" hyperfleet.io/cluster-name: "{{ .clusterName }}" - e2e.hyperfleet.io/test-run-id: "{{ .testRunId }}" + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" e2e.hyperfleet.io/ci: "{{ .ci }}" e2e.hyperfleet.io/managed-by: "test-framework" annotations: diff --git a/testdata/adapter-configs/cl-precondition-error/adapter-task-config.yaml b/testdata/adapter-configs/cl-precondition-error/adapter-task-config.yaml index bca42fc..ec6b667 100644 --- a/testdata/adapter-configs/cl-precondition-error/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-precondition-error/adapter-task-config.yaml @@ -8,11 +8,10 @@ params: source: "event.id" type: "string" required: true - - name: "testRunId" - source: "env.TEST_RUN_ID" + - name: "e2eRunId" + source: "env.E2E_RUN_ID" type: "string" - required: false - default: "TEST_RUN_ID" + required: true - name: "ci" source: "env.CI" type: "string" @@ -44,7 +43,7 @@ resources: name: "{{ .clusterId }}" labels: hyperfleet.io/cluster-id: "{{ .clusterId }}" - e2e.hyperfleet.io/test-run-id: "{{ .testRunId }}" + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" e2e.hyperfleet.io/ci: "{{ .ci }}" e2e.hyperfleet.io/managed-by: "test-framework" discovery: diff --git a/testdata/adapter-configs/cl-stuck/adapter-task-config.yaml b/testdata/adapter-configs/cl-stuck/adapter-task-config.yaml index 2492028..25100eb 100644 --- a/testdata/adapter-configs/cl-stuck/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-stuck/adapter-task-config.yaml @@ -6,6 +6,10 @@ params: source: "event.id" type: "string" required: true + - name: "e2eRunId" + source: "env.E2E_RUN_ID" + type: "string" + required: true preconditions: - name: "clusterStatus" @@ -51,6 +55,7 @@ resources: labels: hyperfleet.io/cluster-id: "{{ .clusterId }}" hyperfleet.io/cluster-name: "{{ .clusterName }}" + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" annotations: hyperfleet.io/generation: "{{ .generationSpec }}" discovery: diff --git a/testdata/adapter-configs/np-configmap/adapter-task-config.yaml b/testdata/adapter-configs/np-configmap/adapter-task-config.yaml index 0e56379..4107239 100644 --- a/testdata/adapter-configs/np-configmap/adapter-task-config.yaml +++ b/testdata/adapter-configs/np-configmap/adapter-task-config.yaml @@ -10,6 +10,10 @@ params: source: "event.id" type: "string" required: true + - name: "e2eRunId" + source: "env.E2E_RUN_ID" + type: "string" + required: true # Preconditions with valid operators and CEL expressions preconditions: diff --git a/testdata/adapter-configs/np-configmap/adapter-task-resource-configmap.yaml b/testdata/adapter-configs/np-configmap/adapter-task-resource-configmap.yaml index 750344b..a64ae61 100644 --- a/testdata/adapter-configs/np-configmap/adapter-task-resource-configmap.yaml +++ b/testdata/adapter-configs/np-configmap/adapter-task-resource-configmap.yaml @@ -9,6 +9,7 @@ metadata: hyperfleet.io/nodepool-id: "{{ .nodepoolId }}" hyperfleet.io/nodepool-name: "{{ .nodepoolName }}" hyperfleet.io/resource-type: "configmap" + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" annotations: hyperfleet.io/generation: "{{ .generationSpec }}" data: From c60e71ef488d74382e13dac677eb327bca31aa57 Mon Sep 17 00:00:00 2001 From: mliptak0 Date: Wed, 24 Jun 2026 14:15:39 +0200 Subject: [PATCH 2/2] HYPERFLEET-1108 - feat: Make e2e-run-id optional in task configs, add flag for passing run id --- cmd/hyperfleet-e2e/test/cmd.go | 4 +-- pkg/e2e/suite.go | 15 ++++++---- pkg/helper/suite.go | 29 +++++++++---------- .../cl-crash/adapter-task-config.yaml | 2 +- .../cl-deployment/adapter-task-config.yaml | 2 +- .../adapter-task-config.yaml | 2 +- .../cl-job/adapter-task-config.yaml | 2 +- .../adapter-task-config.yaml | 4 ++- .../cl-m-wrong-ds/adapter-task-config.yaml | 4 ++- .../cl-m-wrong-nest/adapter-task-config.yaml | 4 ++- .../cl-maestro/adapter-task-config.yaml | 2 +- .../cl-namespace/adapter-task-config.yaml | 2 +- .../adapter-task-config.yaml | 2 +- .../cl-stuck/adapter-task-config.yaml | 2 +- .../np-configmap/adapter-task-config.yaml | 2 +- 15 files changed, 44 insertions(+), 34 deletions(-) diff --git a/cmd/hyperfleet-e2e/test/cmd.go b/cmd/hyperfleet-e2e/test/cmd.go index 3415201..4b94f69 100644 --- a/cmd/hyperfleet-e2e/test/cmd.go +++ b/cmd/hyperfleet-e2e/test/cmd.go @@ -50,7 +50,7 @@ func init() { pfs.IntVar(&args.flakeAttempts, "flake-attempts", 1, "Number of attempts for flaky tests (1 = no retries, 3 = up to 2 retries)") pfs.StringVar(&args.e2eRunID, "e2e-run-id", "", - "E2E run ID for labeling test resources (overrides E2E_RUN_ID env var; defaults to auto-generated UUID)") + "E2E run ID for labeling test resources (overrides E2E_RUN_ID env var; optional)") } func run(cmd *cobra.Command, argv []string) { @@ -92,7 +92,7 @@ func run(cmd *cobra.Command, argv []string) { } // If --e2e-run-id was provided, inject it into the environment so - // GetE2ETestRunID() picks it up (env var takes precedence over auto-generation). + // GetE2ETestRunID() picks it up. if args.e2eRunID != "" { if err := os.Setenv("E2E_RUN_ID", args.e2eRunID); err != nil { log.Printf("Failed to set E2E_RUN_ID: %v\n", err) diff --git a/pkg/e2e/suite.go b/pkg/e2e/suite.go index 120ffe8..dbca1fc 100644 --- a/pkg/e2e/suite.go +++ b/pkg/e2e/suite.go @@ -38,16 +38,21 @@ var _ = ginkgo.BeforeSuite(func() { cfg.Display() - // Generate unique run ID for this test suite execution + // Get run ID (optional - empty string if not set) runID, err := helper.GetE2ETestRunID() if err != nil { - log.Fatalf("Failed to generate run ID: %v", err) + log.Fatalf("Failed to get run ID: %v", err) } helper.SetRunID(runID) - logger.Info("starting hyperfleet-e2e test suite", - "run_id", runID, - "message", "each test creates temporary resources") + if runID != "" { + logger.Info("starting hyperfleet-e2e test suite", + "run_id", runID, + "message", "each test creates temporary resources") + } else { + logger.Info("starting hyperfleet-e2e test suite", + "message", "each test creates temporary resources (no run-id set)") + } }) var _ = ginkgo.AfterSuite(func() { diff --git a/pkg/helper/suite.go b/pkg/helper/suite.go index b2565fc..b2f2fcd 100644 --- a/pkg/helper/suite.go +++ b/pkg/helper/suite.go @@ -7,7 +7,6 @@ import ( "regexp" "sync" - "github.com/google/uuid" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client" k8sclient "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client/kubernetes" "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/config" @@ -64,23 +63,23 @@ const maxRunIDLength = 63 var labelValueRegex = regexp.MustCompile(`^[a-zA-Z0-9]([-_.a-zA-Z0-9]*[a-zA-Z0-9])?$`) // GetE2ETestRunID returns the run identifier for this E2E test suite execution. -// It prefers the E2E_RUN_ID environment variable (set by CI/prow to the namespace name), -// and falls back to a generated UUID v7 when running locally. +// It reads the E2E_RUN_ID environment variable (set by CI/prow to the namespace name). +// Returns empty string if E2E_RUN_ID is not set (run-id is optional). func GetE2ETestRunID() (string, error) { - if id := os.Getenv("E2E_RUN_ID"); id != "" { - if len(id) > maxRunIDLength { - return "", fmt.Errorf("E2E_RUN_ID %q is %d characters, exceeds the %d-character Kubernetes label value limit", id, len(id), maxRunIDLength) - } - if !labelValueRegex.MatchString(id) { - return "", fmt.Errorf("E2E_RUN_ID %q contains characters invalid for a Kubernetes label value", id) - } - return id, nil + id := os.Getenv("E2E_RUN_ID") + if id == "" { + // Run ID is optional - return empty string + return "", nil } - id, err := uuid.NewV7() - if err != nil { - return "", fmt.Errorf("failed to generate run ID: %w", err) + + // Validate run ID format and length + if len(id) > maxRunIDLength { + return "", fmt.Errorf("E2E_RUN_ID %q is %d characters, exceeds the %d-character Kubernetes label value limit", id, len(id), maxRunIDLength) + } + if !labelValueRegex.MatchString(id) { + return "", fmt.Errorf("E2E_RUN_ID %q contains characters invalid for a Kubernetes label value", id) } - return id.String(), nil + return id, nil } // New creates a helper instance for testing diff --git a/testdata/adapter-configs/cl-crash/adapter-task-config.yaml b/testdata/adapter-configs/cl-crash/adapter-task-config.yaml index dac6331..1e67634 100644 --- a/testdata/adapter-configs/cl-crash/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-crash/adapter-task-config.yaml @@ -9,7 +9,7 @@ params: - name: "e2eRunId" source: "env.E2E_RUN_ID" type: "string" - required: true + required: false preconditions: - name: "clusterStatus" diff --git a/testdata/adapter-configs/cl-deployment/adapter-task-config.yaml b/testdata/adapter-configs/cl-deployment/adapter-task-config.yaml index 01ea6a5..124c7cb 100644 --- a/testdata/adapter-configs/cl-deployment/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-deployment/adapter-task-config.yaml @@ -9,7 +9,7 @@ params: - name: "e2eRunId" source: "env.E2E_RUN_ID" type: "string" - required: true + required: false # Preconditions with valid operators and CEL expressions preconditions: diff --git a/testdata/adapter-configs/cl-invalid-resource/adapter-task-config.yaml b/testdata/adapter-configs/cl-invalid-resource/adapter-task-config.yaml index 718f123..170adc5 100644 --- a/testdata/adapter-configs/cl-invalid-resource/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-invalid-resource/adapter-task-config.yaml @@ -9,7 +9,7 @@ params: - name: "e2eRunId" source: "env.E2E_RUN_ID" type: "string" - required: true + required: false - name: "ci" source: "env.CI" type: "string" diff --git a/testdata/adapter-configs/cl-job/adapter-task-config.yaml b/testdata/adapter-configs/cl-job/adapter-task-config.yaml index 651296c..216b05c 100644 --- a/testdata/adapter-configs/cl-job/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-job/adapter-task-config.yaml @@ -9,7 +9,7 @@ params: - name: "e2eRunId" source: "env.E2E_RUN_ID" type: "string" - required: true + required: false # Preconditions with valid operators and CEL expressions preconditions: diff --git a/testdata/adapter-configs/cl-m-unreg-consumer/adapter-task-config.yaml b/testdata/adapter-configs/cl-m-unreg-consumer/adapter-task-config.yaml index 8f522d1..32aa32c 100644 --- a/testdata/adapter-configs/cl-m-unreg-consumer/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-m-unreg-consumer/adapter-task-config.yaml @@ -20,7 +20,7 @@ params: - name: "e2eRunId" source: "env.E2E_RUN_ID" type: "string" - required: true + required: false # Preconditions with valid operators and CEL expressions @@ -132,6 +132,7 @@ resources: app.kubernetes.io/instance: "{{ .adapter.name }}" app.kubernetes.io/name: cl-maestro app.kubernetes.io/transport: maestro + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" annotations: hyperfleet.io/generation: "{{ .generation }}" - apiVersion: v1 @@ -148,6 +149,7 @@ resources: app.kubernetes.io/name: cl-maestro app.kubernetes.io/version: 1.0.0 app.kubernetes.io/transport: maestro + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" annotations: hyperfleet.io/generation: "{{ .generation }}" diff --git a/testdata/adapter-configs/cl-m-wrong-ds/adapter-task-config.yaml b/testdata/adapter-configs/cl-m-wrong-ds/adapter-task-config.yaml index 7a6c48d..d7b4e4a 100644 --- a/testdata/adapter-configs/cl-m-wrong-ds/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-m-wrong-ds/adapter-task-config.yaml @@ -20,7 +20,7 @@ params: - name: "e2eRunId" source: "env.E2E_RUN_ID" type: "string" - required: true + required: false # Preconditions with valid operators and CEL expressions @@ -132,6 +132,7 @@ resources: app.kubernetes.io/instance: "{{ .adapter.name }}" app.kubernetes.io/name: cl-maestro app.kubernetes.io/transport: maestro + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" annotations: hyperfleet.io/generation: "{{ .generation }}" - apiVersion: v1 @@ -148,6 +149,7 @@ resources: app.kubernetes.io/name: cl-maestro app.kubernetes.io/version: 1.0.0 app.kubernetes.io/transport: maestro + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" annotations: hyperfleet.io/generation: "{{ .generation }}" diff --git a/testdata/adapter-configs/cl-m-wrong-nest/adapter-task-config.yaml b/testdata/adapter-configs/cl-m-wrong-nest/adapter-task-config.yaml index 2207b93..cad944b 100644 --- a/testdata/adapter-configs/cl-m-wrong-nest/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-m-wrong-nest/adapter-task-config.yaml @@ -20,7 +20,7 @@ params: - name: "e2eRunId" source: "env.E2E_RUN_ID" type: "string" - required: true + required: false # Preconditions with valid operators and CEL expressions @@ -132,6 +132,7 @@ resources: app.kubernetes.io/instance: "{{ .adapter.name }}" app.kubernetes.io/name: cl-maestro app.kubernetes.io/transport: maestro + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" annotations: hyperfleet.io/generation: "{{ .generation }}" - apiVersion: v1 @@ -148,6 +149,7 @@ resources: app.kubernetes.io/name: cl-maestro app.kubernetes.io/version: 1.0.0 app.kubernetes.io/transport: maestro + e2e.hyperfleet.io/run-id: "{{ .e2eRunId }}" annotations: hyperfleet.io/generation: "{{ .generation }}" diff --git a/testdata/adapter-configs/cl-maestro/adapter-task-config.yaml b/testdata/adapter-configs/cl-maestro/adapter-task-config.yaml index 99128df..e664c06 100644 --- a/testdata/adapter-configs/cl-maestro/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-maestro/adapter-task-config.yaml @@ -20,7 +20,7 @@ params: - name: "e2eRunId" source: "env.E2E_RUN_ID" type: "string" - required: true + required: false # Preconditions with valid operators and CEL expressions preconditions: diff --git a/testdata/adapter-configs/cl-namespace/adapter-task-config.yaml b/testdata/adapter-configs/cl-namespace/adapter-task-config.yaml index 845bc42..107a049 100644 --- a/testdata/adapter-configs/cl-namespace/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-namespace/adapter-task-config.yaml @@ -9,7 +9,7 @@ params: - name: "e2eRunId" source: "env.E2E_RUN_ID" type: "string" - required: true + required: false - name: "ci" source: "env.CI" type: "string" diff --git a/testdata/adapter-configs/cl-precondition-error/adapter-task-config.yaml b/testdata/adapter-configs/cl-precondition-error/adapter-task-config.yaml index ec6b667..9c25d67 100644 --- a/testdata/adapter-configs/cl-precondition-error/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-precondition-error/adapter-task-config.yaml @@ -11,7 +11,7 @@ params: - name: "e2eRunId" source: "env.E2E_RUN_ID" type: "string" - required: true + required: false - name: "ci" source: "env.CI" type: "string" diff --git a/testdata/adapter-configs/cl-stuck/adapter-task-config.yaml b/testdata/adapter-configs/cl-stuck/adapter-task-config.yaml index 25100eb..71addb4 100644 --- a/testdata/adapter-configs/cl-stuck/adapter-task-config.yaml +++ b/testdata/adapter-configs/cl-stuck/adapter-task-config.yaml @@ -9,7 +9,7 @@ params: - name: "e2eRunId" source: "env.E2E_RUN_ID" type: "string" - required: true + required: false preconditions: - name: "clusterStatus" diff --git a/testdata/adapter-configs/np-configmap/adapter-task-config.yaml b/testdata/adapter-configs/np-configmap/adapter-task-config.yaml index 4107239..65f6315 100644 --- a/testdata/adapter-configs/np-configmap/adapter-task-config.yaml +++ b/testdata/adapter-configs/np-configmap/adapter-task-config.yaml @@ -13,7 +13,7 @@ params: - name: "e2eRunId" source: "env.E2E_RUN_ID" type: "string" - required: true + required: false # Preconditions with valid operators and CEL expressions preconditions: