1
0
mirror of https://github.com/siderolabs/omni.git synced 2026-02-05 06:45:34 +01:00

fix: handle deletion event on InstallationMediaConfig validation

While tearing down an InstallationMediaConfig we were still trying to apply validations. Deletion fails for resources that were created before validation rules were created and is in invalid state.

Fixes: #2253

Signed-off-by: Oguz Kilcan <oguz.kilcan@siderolabs.com>
This commit is contained in:
Oguz Kilcan
2026-02-04 12:09:06 +01:00
parent 4cc3a3da8f
commit eae8f84ef6
6 changed files with 89 additions and 4 deletions

View File

@@ -182,8 +182,8 @@ const { height } = useElementSize(slider)
:style="{ height: expanded ? `${height}px` : '0' }"
>
<ClusterMachines
:pause-watches="!expanded"
ref="slider"
:pause-watches="!expanded"
class="h-min"
:cluster-i-d="item.metadata.id!"
is-subgrid

View File

@@ -123,7 +123,7 @@ async function close() {
</TableRoot>
<div class="flex gap-1">
<p class="flex items-center gap-1.5 text-xs" v-if="imageIsGenerating">
<p v-if="imageIsGenerating" class="flex items-center gap-1.5 text-xs">
<TSpinner class="size-3" />
<span>Generating image...</span>
</p>

View File

@@ -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()
}

View File

@@ -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),
)

View File

@@ -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")
}

View File

@@ -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
}