Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions test/e2e/util/external_oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ func (config *ExtOIDCConfig) GetAuthenticationConfig() *configv1.AuthenticationS
},
},
OIDCClients: []configv1.OIDCClientConfig{
{
ClientID: config.CliClientID,
ComponentName: "cli",
ComponentNamespace: "openshift-console",
ExtraScopes: []string{"email"},
},
{
ClientID: config.ConsoleClientID,
ClientSecret: configv1.SecretNameReference{
Expand Down Expand Up @@ -154,7 +160,7 @@ func (config *ExtOIDCConfig) GetAuthenticationConfig() *configv1.AuthenticationS
}

// ValidateAuthenticationSpec validates the external OIDC configuration and the expected HostedCluster authentication configuration before running the test
func ValidateAuthenticationSpec(t *testing.T, ctx context.Context, client crclient.Client, hostedCluster *hyperv1.HostedCluster, config *ExtOIDCConfig) {
func ValidateAuthenticationSpec(t testing.TB, ctx context.Context, client crclient.Client, hostedCluster *hyperv1.HostedCluster, config *ExtOIDCConfig) {
g := NewWithT(t)

// check auth config
Expand Down Expand Up @@ -201,7 +207,7 @@ func ValidateAuthenticationSpec(t *testing.T, ctx context.Context, client crclie
}

// IsExternalOIDCCluster checks if the cluster is using external OIDC.
func IsExternalOIDCCluster(t *testing.T, ctx context.Context, clientCfg *rest.Config) (bool, error) {
func IsExternalOIDCCluster(t testing.TB, ctx context.Context, clientCfg *rest.Config) (bool, error) {
configv1Client, err := configv1typedclient.NewForConfig(clientCfg)
if err != nil {
return false, err
Expand All @@ -215,7 +221,7 @@ func IsExternalOIDCCluster(t *testing.T, ctx context.Context, clientCfg *rest.Co
}

// ChangeClientForKeycloakExtOIDC changes the guest client using a keycloak user config
func ChangeClientForKeycloakExtOIDC(t *testing.T, ctx context.Context, clientCfg *rest.Config, authConfig *ExtOIDCConfig) crclient.Client {
func ChangeClientForKeycloakExtOIDC(t testing.TB, ctx context.Context, clientCfg *rest.Config, authConfig *ExtOIDCConfig) crclient.Client {
g := NewWithT(t)
newConfig := ChangeUserForKeycloakExtOIDC(t, ctx, clientCfg, authConfig)
client, err := crclient.New(newConfig, crclient.Options{Scheme: scheme})
Expand All @@ -224,7 +230,7 @@ func ChangeClientForKeycloakExtOIDC(t *testing.T, ctx context.Context, clientCfg
}

// ChangeUserForKeycloakExtOIDC changes the user of current CLI session for a Keycloak external OIDC cluster
func ChangeUserForKeycloakExtOIDC(t *testing.T, ctx context.Context, clientCfg *rest.Config, authConfig *ExtOIDCConfig) *rest.Config {
func ChangeUserForKeycloakExtOIDC(t testing.TB, ctx context.Context, clientCfg *rest.Config, authConfig *ExtOIDCConfig) *rest.Config {
g := NewWithT(t)
g.Expect(authConfig).NotTo(BeNil())
g.Expect(authConfig.ExternalOIDCProvider).Should(Equal(ProviderKeycloak))
Expand Down
120 changes: 77 additions & 43 deletions test/e2e/v2/cmd/create-guests/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,14 @@ import (
"sync"
"time"

routev1 "github.com/openshift/api/route/v1"

configv1 "github.com/openshift/api/config/v1"
hyperv1 "github.com/openshift/hypershift/api/hypershift/v1beta1"
"github.com/openshift/hypershift/test/e2e/v2/lifecycle"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
Expand All @@ -53,6 +57,9 @@ var scheme = runtime.NewScheme()

func init() {
utilruntime.Must(hyperv1.AddToScheme(scheme))
utilruntime.Must(corev1.AddToScheme(scheme))
utilruntime.Must(appsv1.AddToScheme(scheme))
utilruntime.Must(routev1.AddToScheme(scheme))
}

const defaultNamespace = "clusters"
Expand Down Expand Up @@ -134,6 +141,16 @@ func run(ctx context.Context, cfg envConfig) error {
clusterNames[spec.OutputFile] = name
}

// Phase 0: Platform-specific pre-create hooks (e.g., deploy OIDC providers).
log.Println("Phase 0: Running platform pre-create hooks")
mgmtClientPre, err := newMgmtClient()
if err != nil {
return fmt.Errorf("creating management cluster client for pre-create: %w", err)
}
if err := cfg.platform.PreCreate(ctx, mgmtClientPre, cfg.namespace); err != nil {
return fmt.Errorf("platform pre-create hook: %w", err)
}

// Phase 1: Create all clusters in parallel.
log.Printf("Phase 1: Creating %d clusters in parallel", len(named))
createErrors := createClustersParallel(ctx, cfg, named)
Expand Down Expand Up @@ -176,8 +193,15 @@ func run(ctx context.Context, cfg envConfig) error {
}
}

// Phase 4: Watch for version rollout completion on all clusters.
log.Println("Phase 4: Waiting for version rollout completion on all clusters")
// Phase 4: Platform-specific post-available hooks (e.g., waiting for
// day-2 config transitions now that control plane components exist).
log.Println("Phase 4: Running platform post-available hooks")
if err := cfg.platform.PostAvailable(ctx, mgmtClient, cfg.namespace, clusterNames); err != nil {
return fmt.Errorf("platform post-available hook: %w", err)
}

// Phase 5: Watch for version rollout completion on all clusters.
log.Println("Phase 5: Waiting for version rollout completion on all clusters")
rolloutErrors := waitForVersionRollout(ctx, mgmtClient, cfg, named)
anyRolloutFailed := false
for _, ns := range named {
Expand All @@ -191,8 +215,15 @@ func run(ctx context.Context, cfg envConfig) error {
}
}

// Phase 5: Write cluster names to SHARED_DIR.
log.Println("Phase 5: Writing cluster names to SHARED_DIR")
// Phase 6: Day-2 operations that disrupt ClusterOperators (e.g., External OIDC).
// These run after VersionState=Completed so the initial rollout isn't blocked.
log.Println("Phase 6: Running platform post-version-rollout hooks (day-2 operations)")
if err := cfg.platform.PostVersionRollout(ctx, mgmtClient, cfg.namespace, clusterNames); err != nil {
return fmt.Errorf("platform post-version-rollout hook: %w", err)
}

// Phase 7: Write cluster names to SHARED_DIR.
log.Println("Phase 7: Writing cluster names to SHARED_DIR")
for _, ns := range named {
outputPath := filepath.Join(cfg.sharedDir, ns.OutputFile)
if err := os.WriteFile(outputPath, []byte(ns.name), 0600); err != nil {
Expand Down Expand Up @@ -346,52 +377,55 @@ func waitForVersionRollout(ctx context.Context, cl crclient.WithWatch, cfg envCo
}

func watchForCondition(ctx context.Context, cl crclient.WithWatch, namespace, name string, predicate func(*hyperv1.HostedCluster) bool) error {
key := crclient.ObjectKey{Namespace: namespace, Name: name}
hc := &hyperv1.HostedCluster{}
if err := cl.Get(ctx, crclient.ObjectKey{Namespace: namespace, Name: name}, hc); err == nil {
if predicate(hc) {
return nil
}
}

hcList := &hyperv1.HostedClusterList{}
watcher, err := cl.Watch(ctx, hcList,
crclient.InNamespace(namespace),
crclient.MatchingFields{"metadata.name": name},
)
if err != nil {
return fmt.Errorf("starting watch for %s/%s: %w", namespace, name, err)
}
defer watcher.Stop()
for {
if err := cl.Get(ctx, key, hc); err == nil {
if predicate(hc) {
return nil
}
}

if err := cl.Get(ctx, crclient.ObjectKey{Namespace: namespace, Name: name}, hc); err == nil {
if predicate(hc) {
return nil
hcList := &hyperv1.HostedClusterList{}
watcher, err := cl.Watch(ctx, hcList,
crclient.InNamespace(namespace),
crclient.MatchingFields{"metadata.name": name},
)
if err != nil {
return fmt.Errorf("starting watch for %s/%s: %w", namespace, name, err)
}
}

for {
select {
case <-ctx.Done():
return fmt.Errorf("timed out waiting for %s/%s: %w", namespace, name, ctx.Err())
case event, ok := <-watcher.ResultChan():
if !ok {
return fmt.Errorf("watch channel closed for %s/%s", namespace, name)
}
if event.Type == watch.Error {
return fmt.Errorf("watch error for %s/%s: %v", namespace, name, event.Object)
}
if event.Type != watch.Added && event.Type != watch.Modified {
continue
}
watchedHC, ok := event.Object.(*hyperv1.HostedCluster)
if !ok {
continue
}
logClusterProgress(watchedHC)
if predicate(watchedHC) {
return nil
closed := false
for !closed {
select {
case <-ctx.Done():
watcher.Stop()
return fmt.Errorf("timed out waiting for %s/%s: %w", namespace, name, ctx.Err())
case event, ok := <-watcher.ResultChan():
if !ok {
closed = true
break
}
if event.Type == watch.Error {
closed = true
break
}
if event.Type != watch.Added && event.Type != watch.Modified {
continue
}
watchedHC, ok := event.Object.(*hyperv1.HostedCluster)
if !ok {
continue
}
logClusterProgress(watchedHC)
if predicate(watchedHC) {
watcher.Stop()
return nil
}
}
}
watcher.Stop()
}
}

Expand Down
11 changes: 11 additions & 0 deletions test/e2e/v2/internal/env_vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,15 @@ func init() {
"Path to an additional pull secret file for the global pull secret lifecycle test.",
false,
)
// External OIDC test environment variables
RegisterEnvVar(
"E2E_EXTERNAL_OIDC_CA_BUNDLE_FILE",
"Path to the CA bundle file for the External OIDC issuer (Keycloak). Written by the lifecycle PostCreate.",
false,
)
RegisterEnvVar(
"E2E_EXTERNAL_OIDC_TEST_USERS",
"Comma-separated list of test users in user:password format for External OIDC testing. Written by the lifecycle PostCreate.",
false,
Comment thread
bryan-cox marked this conversation as resolved.
)
}
Loading