diff --git a/hack/runtime-migrator/main.go b/hack/runtime-migrator/main.go index ca5adcef..da38ce41 100644 --- a/hack/runtime-migrator/main.go +++ b/hack/runtime-migrator/main.go @@ -206,7 +206,7 @@ func createRuntime(ctx context.Context, shoot v1beta1.Shoot, cfg migrator.Config }, Provider: v1.Provider{ Type: shoot.Spec.Provider.Type, - Workers: shoot.Spec.Provider.Workers, + Workers: adjustWorkersConfig(shoot.Spec.Provider.Workers), }, Networking: v1.Networking{ Type: shoot.Spec.Networking.Type, @@ -214,7 +214,6 @@ func createRuntime(ctx context.Context, shoot v1beta1.Shoot, cfg migrator.Config Nodes: *shoot.Spec.Networking.Nodes, Services: *shoot.Spec.Networking.Services, }, - ControlPlane: getControlPlane(shoot), }, Security: v1.Security{ Administrators: subjects, @@ -235,9 +234,28 @@ func createRuntime(ctx context.Context, shoot v1beta1.Shoot, cfg migrator.Config Conditions: nil, // deliberately left nil by our migrator to show that controller has not picked it yet }, } + + controlPlane := getControlPlane(shoot) + if controlPlane != nil { + runtime.Spec.Shoot.ControlPlane = controlPlane + } + return runtime, nil } +// The goal of this function is to make the migrator output equal to the shoot created by the converter +// As a result we can automatically verify the correctness of the migrator output +func adjustWorkersConfig(workers []v1beta1.Worker) []v1beta1.Worker { + // We need to set the following fields to nil, as they are not set by the KIM converter + for i := 0; i < len(workers); i++ { + workers[i].Machine.Architecture = nil + workers[i].SystemComponents = nil + workers[i].CRI = nil + } + + return workers +} + func getOidcConfig(shoot v1beta1.Shoot) v1beta1.OIDCConfig { var oidcConfig = v1beta1.OIDCConfig{ CABundle: nil, // deliberately left empty diff --git a/internal/auditlogging/auditlogging.go b/internal/auditlogging/auditlogging.go index c4b47aec..feac599d 100644 --- a/internal/auditlogging/auditlogging.go +++ b/internal/auditlogging/auditlogging.go @@ -147,9 +147,14 @@ func ApplyAuditLogConfig(shoot *gardener.Shoot, auditConfigFromFile map[string]m } changedExt, err := configureExtension(shoot, tenant) + + if err != nil { + return false, err + } + changedSec := configureSecret(shoot, tenant) - return changedExt || changedSec, err + return changedExt || changedSec, nil } func configureExtension(shoot *gardener.Shoot, config AuditLogData) (changed bool, err error) { diff --git a/internal/controller/runtime/fsm/runtime_fsm_configure_auditlog.go b/internal/controller/runtime/fsm/runtime_fsm_configure_auditlog.go index 4637e738..4bfbbb0a 100644 --- a/internal/controller/runtime/fsm/runtime_fsm_configure_auditlog.go +++ b/internal/controller/runtime/fsm/runtime_fsm_configure_auditlog.go @@ -2,19 +2,21 @@ package fsm import ( "context" + "strconv" imv1 "github.com/kyma-project/infrastructure-manager/api/v1" "github.com/kyma-project/infrastructure-manager/internal/auditlogging" "github.com/pkg/errors" + k8serrors "k8s.io/apimachinery/pkg/api/errors" ctrl "sigs.k8s.io/controller-runtime" ) func sFnConfigureAuditLog(ctx context.Context, m *fsm, s *systemState) (stateFn, *ctrl.Result, error) { m.log.Info("Configure Audit Log state") - shootNeedsToBeReconciled, err := m.AuditLogging.Enable(ctx, s.shoot) + wasAuditLogEnabled, err := m.AuditLogging.Enable(ctx, s.shoot) - if err == nil && shootNeedsToBeReconciled { + if wasAuditLogEnabled && err == nil { m.log.Info("Audit Log configured for shoot: " + s.shoot.Name) s.instance.UpdateStatePending( imv1.ConditionTypeAuditLogConfigured, @@ -36,6 +38,56 @@ func sFnConfigureAuditLog(ctx context.Context, m *fsm, s *systemState) (stateFn, return updateStatusAndStop() } + return handleError(err, m, s) + //if err != nil { //nolint:nestif + // if k8serrors.IsConflict(err) { + // m.log.Error(err, "Conflict while updating Shoot object after applying Audit Log configuration, retrying") + // s.instance.UpdateStatePending( + // imv1.ConditionTypeAuditLogConfigured, + // imv1.ConditionReasonAuditLogError, + // "True", + // err.Error(), + // ) + // return updateStatusAndRequeue() + // } + // errorMessage := err.Error() + // + // if errors.Is(err, auditlogging.ErrMissingMapping) { + // if m.RCCfg.AuditLogMandatory { + // m.log.Error(err, "AuditLogMandatory", auditLogMandatoryString, "providerType", s.shoot.Spec.Provider.Type, "region", s.shoot.Spec.Region) + // s.instance.UpdateStatePending( + // imv1.ConditionTypeAuditLogConfigured, + // imv1.ConditionReasonAuditLogMissingRegionMapping, + // "False", + // errorMessage, + // ) + // } else { + // m.log.Info(errorMessage, "AuditLogMandatory", auditLogMandatoryString, "providerType", s.shoot.Spec.Provider.Type, "region", s.shoot.Spec.Region) + // s.instance.UpdateStateReady( + // imv1.ConditionTypeAuditLogConfigured, + // imv1.ConditionReasonAuditLogMissingRegionMapping, + // "Missing region mapping for this shoot. Audit Log is not mandatory. Skipping configuration") + // } + // } else { + // if m.RCCfg.AuditLogMandatory { + // m.log.Error(err, "AuditLogMandatory", auditLogMandatoryString) + // s.instance.UpdateStatePending( + // imv1.ConditionTypeAuditLogConfigured, + // imv1.ConditionReasonAuditLogError, + // "False", + // errorMessage) + // } else { + // m.log.Info(errorMessage, "AuditLogMandatory", auditLogMandatoryString) + // s.instance.UpdateStateReady( + // imv1.ConditionTypeAuditLogConfigured, + // imv1.ConditionReasonAuditLogError, + // "Configuration of Audit Log is not mandatory, error for context: "+errorMessage) + // } + // } + //} +} + +func handleError(err error, m *fsm, s *systemState) (stateFn, *ctrl.Result, error) { setStateForAuditLogError := func(reason imv1.RuntimeConditionReason, pendingMsg string, readyMsg string) { if m.RCCfg.AuditLogMandatory { s.instance.UpdateStatePending( @@ -52,31 +104,47 @@ func sFnConfigureAuditLog(ctx context.Context, m *fsm, s *systemState) (stateFn, } } - logError := func(err error, errorMsg, infoMsg string) { + logError := func(err error, errorMsg, infoMsg string, keysAndValues ...any) { if m.RCCfg.AuditLogMandatory { - m.log.Error(err, errorMsg) + m.log.Error(err, errorMsg, keysAndValues) } else { m.log.Info(err.Error(), infoMsg) } } + if k8serrors.IsConflict(err) { + m.log.Error(err, "Conflict while updating Shoot object after applying Audit Log configuration, retrying") + s.instance.UpdateStatePending( + imv1.ConditionTypeAuditLogConfigured, + imv1.ConditionReasonAuditLogError, + "True", + err.Error(), + ) + + return updateStatusAndRequeue() + } + + auditLogMandatoryString := strconv.FormatBool(m.RCCfg.AuditLogMandatory) + if errors.Is(err, auditlogging.ErrMissingMapping) { pendingStatusMsg := err.Error() readyStatusMsg := "Missing region mapping for this shoot. Audit Log is not mandatory. Skipping configuration" setStateForAuditLogError(imv1.ConditionReasonAuditLogMissingRegionMapping, pendingStatusMsg, readyStatusMsg) - errorMsg := "Failed to configure Audit Log, missing region mapping for this shoot" - infoMsg := "Failed to configure Audit Log, missing region mapping for this shoot, but is not mandatory to be configured" - logError(err, errorMsg, infoMsg) - } else { - pendingStatusMsg := err.Error() - readyStatusMsg := "Configuration of Audit Log is not mandatory, error for context: " + err.Error() - setStateForAuditLogError(imv1.ConditionReasonAuditLogError, pendingStatusMsg, readyStatusMsg) - errorMsg := "Failed to configure Audit Log" - infoMsg := "Failed to configure Audit Log, but is not mandatory to be configured" - logError(err, errorMsg, infoMsg) + infoMsg := "Failed to configure Audit Log" + logError(err, errorMsg, infoMsg, "AuditLogMandatory", auditLogMandatoryString, "providerType", s.shoot.Spec.Provider.Type, "region", s.shoot.Spec.Region) + + return updateStatusAndRequeue() } - return updateStatusAndStop() + pendingStatusMsg := err.Error() + readyStatusMsg := "Configuration of Audit Log is not mandatory, error for context: " + err.Error() + setStateForAuditLogError(imv1.ConditionReasonAuditLogError, pendingStatusMsg, readyStatusMsg) + + errorMsg := "Failed to configure Audit Log" + infoMsg := "Failed to configure Audit Log" + logError(err, errorMsg, infoMsg, "AuditLogMandatory", auditLogMandatoryString) + + return updateStatusAndRequeue() } diff --git a/internal/controller/runtime/fsm/runtime_fsm_configure_auditlog_test.go b/internal/controller/runtime/fsm/runtime_fsm_configure_auditlog_test.go index 925a93f2..cf48b935 100644 --- a/internal/controller/runtime/fsm/runtime_fsm_configure_auditlog_test.go +++ b/internal/controller/runtime/fsm/runtime_fsm_configure_auditlog_test.go @@ -2,15 +2,16 @@ package fsm import ( "context" - "github.com/kyma-project/infrastructure-manager/internal/auditlogging" "testing" gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" v1 "github.com/kyma-project/infrastructure-manager/api/v1" + "github.com/kyma-project/infrastructure-manager/internal/auditlogging" "github.com/kyma-project/infrastructure-manager/internal/auditlogging/mocks" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -236,6 +237,43 @@ func TestAuditLogState(t *testing.T) { assert.Equal(t, v1.RuntimeStateReady, string(systemState.instance.Status.State)) assert.Equal(t, expectedRuntimeConditions, systemState.instance.Status.Conditions) }) + + t.Run("Should requeue in case of Kubernetes object conflict error during update of Shoot object and set status on Runtime CR", func(t *testing.T) { + // given + ctx := context.Background() + auditLog := &mocks.AuditLogging{} + shoot := shootForTest() + instance := runtimeForTest() + systemState := &systemState{ + instance: instance, + shoot: shoot, + } + expectedRuntimeConditions := []metav1.Condition{ + { + Type: string(v1.ConditionTypeAuditLogConfigured), + Status: "True", + Reason: string(v1.ConditionReasonAuditLogError), + Message: k8serrors.NewConflict(gardener.Resource("shoots"), shoot.Name, errors.New("k8s conflict on update error")).Error(), + }, + } + + fsm := &fsm{AuditLogging: auditLog} + fsm.RCCfg.AuditLogMandatory = true + + auditLog.On("Enable", ctx, shoot).Return(false, k8serrors.NewConflict(gardener.Resource("shoots"), shoot.Name, errors.New("k8s conflict on update error"))).Once() + + // when + stateFn, _, _ := sFnConfigureAuditLog(ctx, fsm, systemState) + + // set the time to its zero value for comparison purposes + systemState.instance.Status.Conditions[0].LastTransitionTime = metav1.Time{} + + // then + auditLog.AssertExpectations(t) + require.Contains(t, stateFn.name(), "sFnUpdateStatus") + assert.Equal(t, v1.RuntimeStatePending, string(systemState.instance.Status.State)) + assert.Equal(t, expectedRuntimeConditions, systemState.instance.Status.Conditions) + }) } func shootForTest() *gardener.Shoot { diff --git a/internal/controller/runtime/fsm/runtime_fsm_waiting_for_shoot_reconcile.go b/internal/controller/runtime/fsm/runtime_fsm_waiting_for_shoot_reconcile.go index ce700502..b70e09a2 100644 --- a/internal/controller/runtime/fsm/runtime_fsm_waiting_for_shoot_reconcile.go +++ b/internal/controller/runtime/fsm/runtime_fsm_waiting_for_shoot_reconcile.go @@ -47,9 +47,9 @@ func sFnWaitForShootReconcile(_ context.Context, m *fsm, s *systemState) (stateF return ensureStatusConditionIsSetAndContinue( &s.instance, imv1.ConditionTypeRuntimeProvisioned, - imv1.ConditionReasonAuditLogConfigured, + imv1.ConditionReasonConfigurationCompleted, "Runtime processing completed successfully", - sFnConfigureAuditLog) + sFnApplyClusterRoleBindings) } m.log.Info("Update did not processed, exiting with no retry") diff --git a/internal/controller/runtime/runtime_controller_test.go b/internal/controller/runtime/runtime_controller_test.go index ed6fe337..8811ef74 100644 --- a/internal/controller/runtime/runtime_controller_test.go +++ b/internal/controller/runtime/runtime_controller_test.go @@ -157,7 +157,7 @@ var _ = Describe("Runtime Controller", func() { return false } - if !runtime.IsConditionSet(imv1.ConditionTypeRuntimeProvisioned, imv1.ConditionReasonAuditLogConfigured) { + if !runtime.IsConditionSet(imv1.ConditionTypeRuntimeProvisioned, imv1.ConditionReasonConfigurationCompleted) { return false } diff --git a/internal/gardener/shoot/converter.go b/internal/gardener/shoot/converter.go index aa819a47..4e6a891b 100644 --- a/internal/gardener/shoot/converter.go +++ b/internal/gardener/shoot/converter.go @@ -74,7 +74,7 @@ func NewConverter(config ConverterConfig) Converter { extenders := []Extend{ extender.ExtendWithAnnotations, extender.ExtendWithLabels, - extender.NewKubernetesVersionExtender(config.Kubernetes.DefaultVersion), + extender.NewKubernetesExtender(config.Kubernetes.DefaultVersion), extender.NewProviderExtender(config.Provider.AWS.EnableIMDSv2, config.MachineImage.DefaultVersion), extender.NewDNSExtender(config.DNS.SecretName, config.DNS.DomainPrefix, config.DNS.ProviderType), extender.ExtendWithOIDC, diff --git a/internal/gardener/shoot/extender/dns_test.go b/internal/gardener/shoot/extender/dns_test.go index 802c52be..f0e4c032 100644 --- a/internal/gardener/shoot/extender/dns_test.go +++ b/internal/gardener/shoot/extender/dns_test.go @@ -58,6 +58,7 @@ func fixEmptyGardenerShoot(name, namespace string) gardener.Shoot { ObjectMeta: v1.ObjectMeta{ Name: name, Namespace: namespace, + Labels: map[string]string{}, }, Spec: gardener.ShootSpec{}, } diff --git a/internal/gardener/shoot/extender/kubernetes.go b/internal/gardener/shoot/extender/kubernetes.go new file mode 100644 index 00000000..91693f26 --- /dev/null +++ b/internal/gardener/shoot/extender/kubernetes.go @@ -0,0 +1,25 @@ +package extender + +import ( + gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" + imv1 "github.com/kyma-project/infrastructure-manager/api/v1" + "k8s.io/utils/ptr" +) + +// NewKubernetesExtender creates a new Kubernetes extender function. +// It sets the Kubernetes version of the Shoot to the version specified in the Runtime. +// If the version is not specified in the Runtime, it sets the version to the `defaultKubernetesVersion`, set in `converter_config.json`. +// It sets the EnableStaticTokenKubeconfig field of the Shoot to false. +func NewKubernetesExtender(defaultKubernetesVersion string) func(runtime imv1.Runtime, shoot *gardener.Shoot) error { + return func(runtime imv1.Runtime, shoot *gardener.Shoot) error { + kubernetesVersion := runtime.Spec.Shoot.Kubernetes.Version + if kubernetesVersion == nil || *kubernetesVersion == "" { + kubernetesVersion = &defaultKubernetesVersion + } + + shoot.Spec.Kubernetes.Version = *kubernetesVersion + shoot.Spec.Kubernetes.EnableStaticTokenKubeconfig = ptr.To(false) + + return nil + } +} diff --git a/internal/gardener/shoot/extender/kubernetes_version_test.go b/internal/gardener/shoot/extender/kubernetes_test.go similarity index 66% rename from internal/gardener/shoot/extender/kubernetes_version_test.go rename to internal/gardener/shoot/extender/kubernetes_test.go index 9de870e3..edbf99aa 100644 --- a/internal/gardener/shoot/extender/kubernetes_version_test.go +++ b/internal/gardener/shoot/extender/kubernetes_test.go @@ -16,7 +16,7 @@ func TestKubernetesVersionExtender(t *testing.T) { runtime := imv1.Runtime{} // when - kubernetesVersionExtender := NewKubernetesVersionExtender("1.99") + kubernetesVersionExtender := NewKubernetesExtender("1.99") err := kubernetesVersionExtender(runtime, &shoot) // then @@ -24,6 +24,20 @@ func TestKubernetesVersionExtender(t *testing.T) { assert.Equal(t, "1.99", shoot.Spec.Kubernetes.Version) }) + t.Run("Disable static token kubeconfig", func(t *testing.T) { + // given + shoot := fixEmptyGardenerShoot("test", "kcp-system") + runtime := imv1.Runtime{} + + // when + kubernetesVersionExtender := NewKubernetesExtender("1.99") + err := kubernetesVersionExtender(runtime, &shoot) + + // then + require.NoError(t, err) + assert.Equal(t, false, *shoot.Spec.Kubernetes.EnableStaticTokenKubeconfig) + }) + t.Run("Use version provided in the Runtime CR", func(t *testing.T) { // given shoot := fixEmptyGardenerShoot("test", "kcp-system") @@ -38,7 +52,7 @@ func TestKubernetesVersionExtender(t *testing.T) { } // when - kubernetesVersionExtender := NewKubernetesVersionExtender("1.99") + kubernetesVersionExtender := NewKubernetesExtender("1.99") err := kubernetesVersionExtender(runtime, &shoot) // then diff --git a/internal/gardener/shoot/extender/kubernetes_version.go b/internal/gardener/shoot/extender/kubernetes_version.go deleted file mode 100644 index fd2c9ca4..00000000 --- a/internal/gardener/shoot/extender/kubernetes_version.go +++ /dev/null @@ -1,19 +0,0 @@ -package extender - -import ( - gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" - imv1 "github.com/kyma-project/infrastructure-manager/api/v1" -) - -func NewKubernetesVersionExtender(defaultKubernetesVersion string) func(runtime imv1.Runtime, shoot *gardener.Shoot) error { - return func(runtime imv1.Runtime, shoot *gardener.Shoot) error { - kubernetesVersion := runtime.Spec.Shoot.Kubernetes.Version - if kubernetesVersion == nil || *kubernetesVersion == "" { - kubernetesVersion = &defaultKubernetesVersion - } - - shoot.Spec.Kubernetes.Version = *kubernetesVersion - - return nil - } -} diff --git a/internal/gardener/shoot/extender/network_filter.go b/internal/gardener/shoot/extender/network_filter.go index 1f07da72..d562342b 100644 --- a/internal/gardener/shoot/extender/network_filter.go +++ b/internal/gardener/shoot/extender/network_filter.go @@ -3,15 +3,15 @@ package extender import ( gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" imv1 "github.com/kyma-project/infrastructure-manager/api/v1" - "k8s.io/utils/ptr" ) const NetworkFilterType = "shoot-networking-filter" func ExtendWithNetworkFilter(runtime imv1.Runtime, shoot *gardener.Shoot) error { //nolint:revive networkingFilter := gardener.Extension{ - Type: NetworkFilterType, - Disabled: ptr.To(false), + Type: NetworkFilterType, + // this pointer is safe, because runtime is fully pass-by-value + Disabled: &runtime.Spec.Security.Networking.Filter.Egress.Enabled, } shoot.Spec.Extensions = append(shoot.Spec.Extensions, networkingFilter) diff --git a/internal/gardener/shoot/extender/oidc.go b/internal/gardener/shoot/extender/oidc.go index 8e8da762..b776cf71 100644 --- a/internal/gardener/shoot/extender/oidc.go +++ b/internal/gardener/shoot/extender/oidc.go @@ -13,12 +13,25 @@ const ( func ExtendWithOIDC(runtime imv1.Runtime, shoot *gardener.Shoot) error { oidcConfig := runtime.Spec.Shoot.Kubernetes.KubeAPIServer.OidcConfig - setOIDCExtension(shoot) + if CanEnableExtension(runtime) { + setOIDCExtension(shoot) + } setKubeAPIServerOIDCConfig(shoot, oidcConfig) return nil } +func CanEnableExtension(runtime imv1.Runtime) bool { + canEnable := true + createdByMigrator := runtime.Labels["operator.kyma-project.io/created-by-migrator"] + + if createdByMigrator == "true" { + canEnable = false + } + + return canEnable +} + func setOIDCExtension(shoot *gardener.Shoot) { oidcService := gardener.Extension{ Type: OidcExtensionType, diff --git a/internal/gardener/shoot/extender/oidc_test.go b/internal/gardener/shoot/extender/oidc_test.go index 2fda2db5..3fc0e683 100644 --- a/internal/gardener/shoot/extender/oidc_test.go +++ b/internal/gardener/shoot/extender/oidc_test.go @@ -1,6 +1,7 @@ package extender import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "testing" gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" @@ -10,42 +11,74 @@ import ( ) func TestOidcExtender(t *testing.T) { - t.Run("Create kubernetes config", func(t *testing.T) { - // given - clientID := "client-id" - groupsClaim := "groups" - issuerURL := "https://my.cool.tokens.com" - usernameClaim := "sub" + const MigratorLabel = "operator.kyma-project.io/created-by-migrator" + for _, testCase := range []struct { + name string + migratorLabel map[string]string + expectedOidcExtensionEnabled bool + }{ + { + name: "label created-by-migrator=true should not configure OIDC", + migratorLabel: map[string]string{MigratorLabel: "true"}, + expectedOidcExtensionEnabled: false, + }, + { + name: "label created-by-migrator=false should configure OIDC", + migratorLabel: map[string]string{MigratorLabel: "false"}, + expectedOidcExtensionEnabled: true, + }, + { + name: "label created-by-migrator unset should configure OIDC", + migratorLabel: nil, + expectedOidcExtensionEnabled: true, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + // given + clientID := "client-id" + groupsClaim := "groups" + issuerURL := "https://my.cool.tokens.com" + usernameClaim := "sub" - shoot := fixEmptyGardenerShoot("test", "kcp-system") - runtimeShoot := imv1.Runtime{ - Spec: imv1.RuntimeSpec{ - Shoot: imv1.RuntimeShoot{ - Kubernetes: imv1.Kubernetes{ - KubeAPIServer: imv1.APIServer{ - OidcConfig: gardener.OIDCConfig{ - ClientID: &clientID, - GroupsClaim: &groupsClaim, - IssuerURL: &issuerURL, - SigningAlgs: []string{ - "RS256", + shoot := fixEmptyGardenerShoot("test", "kcp-system") + runtimeShoot := imv1.Runtime{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + MigratorLabel: testCase.migratorLabel[MigratorLabel], + }, + }, + Spec: imv1.RuntimeSpec{ + Shoot: imv1.RuntimeShoot{ + Kubernetes: imv1.Kubernetes{ + KubeAPIServer: imv1.APIServer{ + OidcConfig: gardener.OIDCConfig{ + ClientID: &clientID, + GroupsClaim: &groupsClaim, + IssuerURL: &issuerURL, + SigningAlgs: []string{ + "RS256", + }, + UsernameClaim: &usernameClaim, }, - UsernameClaim: &usernameClaim, }, }, }, }, - }, - } + } - // when - err := ExtendWithOIDC(runtimeShoot, &shoot) + // when + err := ExtendWithOIDC(runtimeShoot, &shoot) - // then - require.NoError(t, err) + // then + require.NoError(t, err) - assert.Equal(t, runtimeShoot.Spec.Shoot.Kubernetes.KubeAPIServer.OidcConfig, *shoot.Spec.Kubernetes.KubeAPIServer.OIDCConfig) - assert.Equal(t, false, *shoot.Spec.Extensions[0].Disabled) - assert.Equal(t, "shoot-oidc-service", shoot.Spec.Extensions[0].Type) - }) + assert.Equal(t, runtimeShoot.Spec.Shoot.Kubernetes.KubeAPIServer.OidcConfig, *shoot.Spec.Kubernetes.KubeAPIServer.OIDCConfig) + if testCase.expectedOidcExtensionEnabled { + assert.Equal(t, testCase.expectedOidcExtensionEnabled, !*shoot.Spec.Extensions[0].Disabled) + assert.Equal(t, "shoot-oidc-service", shoot.Spec.Extensions[0].Type) + } else { + assert.Equal(t, 0, len(shoot.Spec.Extensions)) + } + }) + } }