diff --git a/frontend/src/views/omni/Clusters/ClusterItem.vue b/frontend/src/views/omni/Clusters/ClusterItem.vue index 16bbb999..3ed861fb 100644 --- a/frontend/src/views/omni/Clusters/ClusterItem.vue +++ b/frontend/src/views/omni/Clusters/ClusterItem.vue @@ -182,8 +182,8 @@ const { height } = useElementSize(slider) :style="{ height: expanded ? `${height}px` : '0' }" >
-

+

Generating image...

diff --git a/internal/backend/runtime/omni/export_test.go b/internal/backend/runtime/omni/export_test.go index 5c01a98f..a85de525 100644 --- a/internal/backend/runtime/omni/export_test.go +++ b/internal/backend/runtime/omni/export_test.go @@ -109,3 +109,7 @@ func InfraProviderValidationOptions(st state.State) []validated.StateOption { func RotateSecretsValidationOptions(st state.State) []validated.StateOption { return rotateSecretsValidationOptions(st) } + +func InstallationMediaConfigValidationOptions() []validated.StateOption { + return installationMediaConfigValidationOptions() +} diff --git a/internal/backend/runtime/omni/omni.go b/internal/backend/runtime/omni/omni.go index 82121421..c890b3f0 100644 --- a/internal/backend/runtime/omni/omni.go +++ b/internal/backend/runtime/omni/omni.go @@ -351,7 +351,7 @@ func NewRuntime(talosClientFactory *talos.ClientFactory, dnsService *dns.Service defaultJoinTokenValidationOptions(defaultState), importedClusterSecretValidationOptions(defaultState, config.Config.Features.GetEnableClusterImport()), infraProviderValidationOptions(defaultState), - installationMediaConfigOptions(), + installationMediaConfigValidationOptions(), rotateSecretsValidationOptions(defaultState), ) diff --git a/internal/backend/runtime/omni/state_validation.go b/internal/backend/runtime/omni/state_validation.go index eebf1c1e..9a200ecc 100644 --- a/internal/backend/runtime/omni/state_validation.go +++ b/internal/backend/runtime/omni/state_validation.go @@ -1554,8 +1554,12 @@ func infraProviderValidationOptions(st state.State) []validated.StateOption { } } -func installationMediaConfigOptions() []validated.StateOption { +func installationMediaConfigValidationOptions() []validated.StateOption { validateInstallationMedia := func(res *omni.InstallationMediaConfig) error { + if res.Metadata().Phase() == resource.PhaseTearingDown { + return nil + } + if res.TypedSpec().Value.TalosVersion == "" { return errors.New("invalid installation media config: talos version is required") } diff --git a/internal/backend/runtime/omni/state_validation_test.go b/internal/backend/runtime/omni/state_validation_test.go index d8e19ab6..d48de7fc 100644 --- a/internal/backend/runtime/omni/state_validation_test.go +++ b/internal/backend/runtime/omni/state_validation_test.go @@ -19,6 +19,7 @@ import ( "testing" "time" + "github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/safe" "github.com/cosi-project/runtime/pkg/state" "github.com/cosi-project/runtime/pkg/state/impl/inmem" @@ -32,6 +33,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "github.com/siderolabs/omni/client/api/omni/specs" + "github.com/siderolabs/omni/client/pkg/constants" "github.com/siderolabs/omni/client/pkg/omni/resources/auth" "github.com/siderolabs/omni/client/pkg/omni/resources/infra" omnires "github.com/siderolabs/omni/client/pkg/omni/resources/omni" @@ -1975,6 +1977,81 @@ func TestRotateSecretsValidation(t *testing.T) { require.NoError(t, st.Destroy(ctx, rotateTalosCA.Metadata())) } +func TestInstallationMediaConfigValidation(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithTimeout(t.Context(), time.Second) + t.Cleanup(cancel) + + st := validated.NewState(state.WrapCore(namespaced.NewState(inmem.Build)), omni.InstallationMediaConfigValidationOptions()...) + + installationMediaConfig := omnires.NewInstallationMediaConfig("test") + + err := st.Create(ctx, installationMediaConfig) + require.Contains(t, err.Error(), "invalid installation media config: talos version is required") + + installationMediaConfig.TypedSpec().Value.TalosVersion = constants.DefaultTalosVersion + err = st.Create(ctx, installationMediaConfig) + require.Contains(t, err.Error(), "invalid installation media config: architecture is required") + + installationMediaConfig.TypedSpec().Value.Architecture = specs.PlatformConfigSpec_AMD64 + err = st.Create(ctx, installationMediaConfig) + require.Contains(t, err.Error(), "invalid installation media config: join token is required") + + installationMediaConfig.TypedSpec().Value.JoinToken = "test-token" + installationMediaConfig.TypedSpec().Value.Cloud = &specs.InstallationMediaConfigSpec_Cloud{} + installationMediaConfig.TypedSpec().Value.Sbc = &specs.InstallationMediaConfigSpec_SBC{} + + err = st.Create(ctx, installationMediaConfig) + require.Contains(t, err.Error(), "invalid installation media config: both sbc and cloud fields are set") + + installationMediaConfig.TypedSpec().Value.Sbc = nil + err = st.Create(ctx, installationMediaConfig) + require.NoError(t, err) + + modifiedInstallationMediaConfig, ok := installationMediaConfig.DeepCopy().(*omnires.InstallationMediaConfig) + require.True(t, ok) + + modifiedInstallationMediaConfig.TypedSpec().Value.TalosVersion = "" + err = st.Update(ctx, modifiedInstallationMediaConfig) + require.Contains(t, err.Error(), "invalid installation media config: talos version is required") + + modifiedInstallationMediaConfig, ok = installationMediaConfig.DeepCopy().(*omnires.InstallationMediaConfig) + require.True(t, ok) + + modifiedInstallationMediaConfig.TypedSpec().Value.Architecture = specs.PlatformConfigSpec_UNKNOWN_ARCH + err = st.Update(ctx, modifiedInstallationMediaConfig) + require.Contains(t, err.Error(), "invalid installation media config: architecture is required") + + modifiedInstallationMediaConfig, ok = installationMediaConfig.DeepCopy().(*omnires.InstallationMediaConfig) + require.True(t, ok) + + modifiedInstallationMediaConfig.TypedSpec().Value.JoinToken = "" + err = st.Update(ctx, modifiedInstallationMediaConfig) + require.Contains(t, err.Error(), "invalid installation media config: join token is required") + + modifiedInstallationMediaConfig, ok = installationMediaConfig.DeepCopy().(*omnires.InstallationMediaConfig) + require.True(t, ok) + + modifiedInstallationMediaConfig.TypedSpec().Value.Sbc = &specs.InstallationMediaConfigSpec_SBC{} + err = st.Update(ctx, modifiedInstallationMediaConfig) + require.Contains(t, err.Error(), "invalid installation media config: both sbc and cloud fields are set") + + installationMediaConfig.TypedSpec().Value.Cloud.Platform = "AWS" + require.NoError(t, st.Update(ctx, installationMediaConfig)) + + installationMediaConfig.Metadata().SetPhase(resource.PhaseTearingDown) + installationMediaConfig.TypedSpec().Value.TalosVersion = "" + installationMediaConfig.TypedSpec().Value.Architecture = specs.PlatformConfigSpec_UNKNOWN_ARCH + installationMediaConfig.TypedSpec().Value.JoinToken = "" + installationMediaConfig.TypedSpec().Value.Cloud = &specs.InstallationMediaConfigSpec_Cloud{} + installationMediaConfig.TypedSpec().Value.Sbc = &specs.InstallationMediaConfigSpec_SBC{} + require.NoError(t, st.Update(ctx, installationMediaConfig)) + + err = st.Destroy(ctx, installationMediaConfig.Metadata()) + require.NoError(t, err) +} + type mockEtcdBackupStoreFactory struct { store etcdbackup.Store }