omni/internal/pkg/features/features.go
Utku Ozdemir 91c8bff46c
feat: generate omni config from schema
Make all leaf fields nillable, so that we can distinguish unset from explicit empty, and merging of CLI args and YAML configs work correctly.

Generate nil-safe accessors (getter/setters) for these nillable fields and use them in the code.

Wrap the cobra command line parser to support nillable flags.

Move all validations into the JSON schema and drop go-validator usage and its annotations.

Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
2026-01-22 13:23:11 +01:00

91 lines
3.2 KiB
Go

// Copyright (c) 2026 Sidero Labs, Inc.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
// Package features contains the feature flags related functionality.
package features
import (
"context"
"fmt"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/cosi-project/runtime/pkg/state"
"go.uber.org/zap"
"google.golang.org/protobuf/types/known/durationpb"
"github.com/siderolabs/omni/client/api/omni/specs"
"github.com/siderolabs/omni/client/pkg/omni/resources/omni"
"github.com/siderolabs/omni/internal/pkg/config"
)
// UpdateResources creates or updates the features omni.FeaturesConfig resource with the current feature flags.
func UpdateResources(ctx context.Context, st state.State, logger *zap.Logger) error {
workloadProxyEnabled := config.Config.Services.WorkloadProxy.GetEnabled()
updateFeaturesConfig := func(res *omni.FeaturesConfig) error {
res.TypedSpec().Value.EnableWorkloadProxying = workloadProxyEnabled
res.TypedSpec().Value.EmbeddedDiscoveryService = config.Config.Services.EmbeddedDiscoveryService.GetEnabled()
res.TypedSpec().Value.EtcdBackupSettings = &specs.EtcdBackupSettings{
TickInterval: durationpb.New(config.Config.EtcdBackup.GetTickInterval()),
MinInterval: durationpb.New(config.Config.EtcdBackup.GetMinInterval()),
MaxInterval: durationpb.New(config.Config.EtcdBackup.GetMaxInterval()),
}
res.TypedSpec().Value.AuditLogEnabled = config.Config.Logs.Audit.GetPath() != "" //nolint:staticcheck
res.TypedSpec().Value.ImageFactoryBaseUrl = config.Config.Registries.GetImageFactoryBaseURL()
imageFactoryPXEBaseURL, err := config.Config.GetImageFactoryPXEBaseURL()
if err != nil {
return err
}
res.TypedSpec().Value.ImageFactoryPxeBaseUrl = imageFactoryPXEBaseURL.String()
res.TypedSpec().Value.UserPilotSettings = &specs.UserPilotSettings{
AppToken: config.Config.Account.UserPilot.GetAppToken(),
}
res.TypedSpec().Value.StripeSettings = &specs.StripeSettings{
Enabled: config.Config.Logs.Stripe.GetEnabled(),
MinCommit: config.Config.Logs.Stripe.GetMinCommit(),
}
res.TypedSpec().Value.Account = &specs.Account{
Id: config.Config.Account.GetId(),
Name: config.Config.Account.GetName(),
}
res.TypedSpec().Value.TalosPreReleaseVersionsEnabled = config.Config.Features.GetEnableTalosPreReleaseVersions()
return nil
}
featuresConfig := omni.NewFeaturesConfig(omni.FeaturesConfigID)
_, err := st.Get(ctx, featuresConfig.Metadata())
if err != nil {
if !state.IsNotFoundError(err) {
return fmt.Errorf("failed to get features config: %w", err)
}
if err = updateFeaturesConfig(featuresConfig); err != nil {
return fmt.Errorf("failed to update features config: %w", err)
}
err = st.Create(ctx, featuresConfig)
if err != nil {
return fmt.Errorf("failed to create features config: %w", err)
}
logger.Info("created features config resource", zap.Bool("enable_workload_proxying", workloadProxyEnabled))
return nil
}
if _, err = safe.StateUpdateWithConflicts(ctx, st, featuresConfig.Metadata(), updateFeaturesConfig); err != nil {
return fmt.Errorf("failed to update features config: %w", err)
}
logger.Info("updated features config resource", zap.Bool("enable_workload_proxying", workloadProxyEnabled))
return nil
}