From d2ea9fa9ada802b367edaa31fd321baddc0bb2fd Mon Sep 17 00:00:00 2001 From: Petr Muller Date: Thu, 30 Apr 2026 18:28:09 +0200 Subject: [PATCH] Allow info and list tests commands to work without KUBECONFIG Split initFrameworkForTests() into initFrameworkDefaults() (runs at startup without KUBECONFIG) and initFrameworkCluster() (deferred to AddBeforeAll, requires KUBECONFIG). This enables workflows that need to discover or list tests without a live cluster connection, such as the openshift-tests "list all-tests" command. Co-Authored-By: Claude Opus 4.6 --- openshift-tests/ccm-aws-tests/main.go | 70 +++++++++++++++------------ 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/openshift-tests/ccm-aws-tests/main.go b/openshift-tests/ccm-aws-tests/main.go index eb9e37892..8ad1472ad 100644 --- a/openshift-tests/ccm-aws-tests/main.go +++ b/openshift-tests/ccm-aws-tests/main.go @@ -14,6 +14,7 @@ import ( "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" kclientset "k8s.io/client-go/kubernetes" + restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/kubernetes/test/e2e/framework" @@ -39,10 +40,9 @@ func main() { Qualifiers: []string{`!labels.exists(l, l == "Serial") && labels.exists(l, l == "Conformance")`}, }) - // Initialize framework for the tests. - if err := initFrameworkForTests(); err != nil { - panic(fmt.Errorf("failed to initialize test framework: %w", err)) - } + // Initialize framework for the tests. Works with or without KUBECONFIG + // so that "info" and "list tests" commands can run without cluster access. + initFrameworkForTests() // Build the extension test specs specs, err := g.BuildExtensionTestSpecsFromOpenShiftGinkgoSuite() @@ -125,16 +125,14 @@ func getRegionFromEnv() string { } // initFrameworkForTests initializes the framework for the tests globally. -func initFrameworkForTests() error { - if len(os.Getenv("KUBECONFIG")) == 0 { - return fmt.Errorf("KUBECONFIG is empty. Set the KUBECONFIG environment variable") - } - - // Initialize framework - required for test discovery - // TODO: - // 1. Fix the provider getting from env (when ote supports aws) - // 2. Build the config from the env, and set the testContext.CloudConfig (if required by the test) - // 3. Move this init to a dedicated function +// When KUBECONFIG is set, it loads the cluster config and sets the host. +// When KUBECONFIG is not set, it uses a placeholder host so that +// AfterReadingAllFlags can run without emitting a klog warning to stdout +// (which would violate the OTE Binary Stdout Contract for info/list commands). +// TODO: +// 1. Fix the provider getting from env (when ote supports aws) +// 2. Build the config from the env, and set the testContext.CloudConfig (if required by the test) +func initFrameworkForTests() { testContext.Provider = "local" // TODO: OTE supports local or skeleton // Set up AWS cloud configuration when environment variables are set. @@ -157,27 +155,37 @@ func initFrameworkForTests() error { testContext.NodeOSDistro = "custom" testContext.MasterOSDistro = "custom" - // Load kube client config and set the host variable for kubectl - testContext.KubeConfig = os.Getenv("KUBECONFIG") - clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - &clientcmd.ClientConfigLoadingRules{ - ExplicitPath: testContext.KubeConfig, - }, - &clientcmd.ConfigOverrides{}, - ) - cfg, err := clientConfig.ClientConfig() - if err != nil { - return fmt.Errorf("failed to get client config: %w", err) + // Load kube client config when available. + if kubeconfig := os.Getenv("KUBECONFIG"); len(kubeconfig) > 0 { + testContext.KubeConfig = kubeconfig + clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + &clientcmd.ClientConfigLoadingRules{ + ExplicitPath: testContext.KubeConfig, + }, + &clientcmd.ConfigOverrides{}, + ) + if cfg, err := clientConfig.ClientConfig(); err == nil { + testContext.Host = cfg.Host + } + } else if _, err := restclient.InClusterConfig(); err != nil { + // No KUBECONFIG and not running in-cluster. Set a placeholder Host so + // AfterReadingAllFlags skips in-cluster config detection, which would + // emit a klog warning through GinkgoWriter to stdout. + testContext.Host = "placeholder" } - testContext.Host = cfg.Host - // After reading all flags, this will configure the test context, and need to be - // called once by framework to avoid re-configuring the test context, and leding - // to issues in Ginkgo phases (PhaseBuildTopLevel, PhaseBuildTree, PhaseRun), - // such as:'cannot clone suite after tree has been built' + // Redirect framework.Output to stderr to preserve the OTE Binary Stdout + // Contract (info/list tests commands must output clean JSON on stdout). + framework.Output = os.Stderr + + // Must be called during startup (before the Ginkgo tree is built) because it + // internally calls ginkgo.PreviewSpecs which clones the suite. framework.AfterReadingAllFlags(testContext) - return nil + // Clear the placeholder so tests don't accidentally use it. + if testContext.Host == "placeholder" { + testContext.Host = "" + } } // initFrameworkForTest initializes the framework for the test instance.