mirror of
https://github.com/siderolabs/talos.git
synced 2026-05-05 20:36:18 +02:00
feat: rewrite install disk selector to use CEL expressions
Rewrite matcher to take out old go-blockdevice library out of the way, implementing translation from go-blockdevice format to CEL. Implement facilities to build CEL expressions programmatically. Now we can add a machine config disk match expression (CEL) easily. Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
parent
eba35f4413
commit
9a02ecc49f
@ -18,11 +18,11 @@ preface = """
|
||||
[notes.updates]
|
||||
title = "Component Updates"
|
||||
description = """\
|
||||
Linux: 6.6.59
|
||||
containerd: 2.0.0
|
||||
Flannel: 0.26.0
|
||||
Kubernetes: 1.32.0-beta.0
|
||||
runc: 1.2.1
|
||||
* Linux: 6.6.59
|
||||
* containerd: 2.0.0
|
||||
* Flannel: 0.26.0
|
||||
* Kubernetes: 1.32.0-beta.0
|
||||
* runc: 1.2.1
|
||||
|
||||
Talos is built with Go 1.23.2.
|
||||
"""
|
||||
@ -68,7 +68,7 @@ This command allows you to view the cgroup resource consumption and limits for a
|
||||
[notes.udevd]
|
||||
title = "udevd"
|
||||
description = """\
|
||||
Talos previously used `udevd` to provide `udevd`, now it uses `systemd-udevd` instead.
|
||||
Talos previously used `eudev` to provide `udevd`, now it uses `systemd-udevd` instead.
|
||||
"""
|
||||
|
||||
[make_deps]
|
||||
|
||||
@ -179,16 +179,23 @@ func (s *Server) ApplyConfiguration(ctx context.Context, in *machine.ApplyConfig
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
warnings, err := cfgProvider.Validate(
|
||||
modeWrapper{
|
||||
Mode: s.Controller.Runtime().State().Platform().Mode(),
|
||||
installed: s.Controller.Runtime().State().Machine().Installed(),
|
||||
},
|
||||
)
|
||||
validationMode := modeWrapper{
|
||||
Mode: s.Controller.Runtime().State().Platform().Mode(),
|
||||
installed: s.Controller.Runtime().State().Machine().Installed(),
|
||||
}
|
||||
|
||||
warnings, err := cfgProvider.Validate(validationMode)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
warningsRuntime, err := cfgProvider.RuntimeValidate(ctx, s.Controller.Runtime().State().V1Alpha2().Resources(), validationMode)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
warnings = slices.Concat(warnings, warningsRuntime)
|
||||
|
||||
//nolint:exhaustive
|
||||
switch in.Mode {
|
||||
// --mode=try
|
||||
|
||||
@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/block/internal/volumes"
|
||||
blockpb "github.com/siderolabs/talos/pkg/machinery/api/resource/definitions/block"
|
||||
"github.com/siderolabs/talos/pkg/machinery/proto"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/block"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/hardware"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/runtime"
|
||||
@ -164,7 +165,7 @@ func (ctrl *VolumeManagerController) Run(ctx context.Context, r controller.Runti
|
||||
discoveredVolumesSpecs, err := safe.Map(discoveredVolumes, func(dv *block.DiscoveredVolume) (*blockpb.DiscoveredVolumeSpec, error) {
|
||||
spec := &blockpb.DiscoveredVolumeSpec{}
|
||||
|
||||
return spec, volumes.ResourceSpecToProto(dv, spec)
|
||||
return spec, proto.ResourceSpecToProto(dv, spec)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error mapping discovered volumes: %w", err)
|
||||
@ -204,7 +205,7 @@ func (ctrl *VolumeManagerController) Run(ctx context.Context, r controller.Runti
|
||||
diskSpecs, err := safe.Map(disks, func(d *block.Disk) (volumes.DiskContext, error) {
|
||||
spec := &blockpb.DiskSpec{}
|
||||
|
||||
if err := volumes.ResourceSpecToProto(d, spec); err != nil {
|
||||
if err := proto.ResourceSpecToProto(d, spec); err != nil {
|
||||
return volumes.DiskContext{}, err
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/controller"
|
||||
@ -68,6 +69,7 @@ type AcquireController struct {
|
||||
EventPublisher talosruntime.Publisher
|
||||
ValidationMode validation.RuntimeMode
|
||||
ConfigPath string
|
||||
ResourceState state.State
|
||||
|
||||
configSourcesUsed []string
|
||||
}
|
||||
@ -345,7 +347,12 @@ func (ctrl *AcquireController) loadFromPlatform(ctx context.Context, logger *zap
|
||||
return nil, fmt.Errorf("failed to validate config acquired via platform %s: %w", platformName, err)
|
||||
}
|
||||
|
||||
for _, warning := range warnings {
|
||||
warningsRuntime, err := cfg.RuntimeValidate(ctx, ctrl.ResourceState, ctrl.ValidationMode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to runtime validate config acquired via platform %s: %w", platformName, err)
|
||||
}
|
||||
|
||||
for _, warning := range slices.Concat(warnings, warningsRuntime) {
|
||||
logger.Warn("config validation warning", zap.String("platform", platformName), zap.String("warning", warning))
|
||||
}
|
||||
|
||||
@ -364,7 +371,7 @@ func (ctrl *AcquireController) stateCmdline(ctx context.Context, r controller.Ru
|
||||
return ctrl.stateMaintenanceEnter, nil, nil
|
||||
}
|
||||
|
||||
cfg, err := ctrl.loadFromCmdline(logger)
|
||||
cfg, err := ctrl.loadFromCmdline(ctx, logger)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -386,7 +393,9 @@ func (ctrl *AcquireController) stateCmdline(ctx context.Context, r controller.Ru
|
||||
}
|
||||
|
||||
// loadFromCmdline is a helper function for stateCmdline.
|
||||
func (ctrl *AcquireController) loadFromCmdline(logger *zap.Logger) (config.Provider, error) {
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (ctrl *AcquireController) loadFromCmdline(ctx context.Context, logger *zap.Logger) (config.Provider, error) {
|
||||
cmdline := ctrl.CmdlineGetter()
|
||||
|
||||
param := cmdline.Get(constants.KernelParamConfigInline)
|
||||
@ -435,7 +444,12 @@ func (ctrl *AcquireController) loadFromCmdline(logger *zap.Logger) (config.Provi
|
||||
return nil, fmt.Errorf("failed to validate config acquired via cmdline %s: %w", constants.KernelParamConfigInline, err)
|
||||
}
|
||||
|
||||
for _, warning := range warnings {
|
||||
warningsRuntime, err := cfg.RuntimeValidate(ctx, ctrl.ResourceState, ctrl.ValidationMode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to validate config acquired via cmdline %s: %w", constants.KernelParamConfigInline, err)
|
||||
}
|
||||
|
||||
for _, warning := range slices.Concat(warnings, warningsRuntime) {
|
||||
logger.Warn("config validation warning", zap.String("cmdline", constants.KernelParamConfigInline), zap.String("warning", warning))
|
||||
}
|
||||
|
||||
|
||||
@ -199,6 +199,7 @@ func TestAcquireSuite(t *testing.T) {
|
||||
EventPublisher: s.eventPublisher,
|
||||
ValidationMode: validationModeMock{},
|
||||
ConfigPath: s.configPath,
|
||||
ResourceState: s.State(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@ -71,6 +71,7 @@ import (
|
||||
"github.com/siderolabs/talos/pkg/kubernetes"
|
||||
machineapi "github.com/siderolabs/talos/pkg/machinery/api/machine"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/machine"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/types/block/blockhelpers"
|
||||
"github.com/siderolabs/talos/pkg/machinery/constants"
|
||||
metamachinery "github.com/siderolabs/talos/pkg/machinery/meta"
|
||||
blockres "github.com/siderolabs/talos/pkg/machinery/resources/block"
|
||||
@ -2015,9 +2016,27 @@ func Install(runtime.Sequence, any) (runtime.TaskExecutionFunc, string) {
|
||||
|
||||
var disk string
|
||||
|
||||
disk, err = r.Config().Machine().Install().Disk()
|
||||
matchExpr, err := r.Config().Machine().Install().DiskMatchExpression()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to get disk match expression: %w", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case matchExpr != nil:
|
||||
logger.Printf("using disk match expression: %s", matchExpr)
|
||||
|
||||
matchedDisks, err := blockhelpers.MatchDisks(ctx, r.State().V1Alpha2().Resources(), matchExpr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(matchedDisks) == 0 {
|
||||
return fmt.Errorf("no disks matched the expression: %s", matchExpr)
|
||||
}
|
||||
|
||||
disk = matchedDisks[0].TypedSpec().DevPath
|
||||
case r.Config().Machine().Install().Disk() != "":
|
||||
disk = r.Config().Machine().Install().Disk()
|
||||
}
|
||||
|
||||
disk, err = filepath.EvalSymlinks(disk)
|
||||
@ -2025,6 +2044,8 @@ func Install(runtime.Sequence, any) (runtime.TaskExecutionFunc, string) {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Printf("installing Talos to disk %s", disk)
|
||||
|
||||
err = install.RunInstallerContainer(
|
||||
disk,
|
||||
r.State().Platform().Name(),
|
||||
|
||||
@ -125,6 +125,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
|
||||
ConfigSetter: ctrl.v1alpha1Runtime,
|
||||
EventPublisher: ctrl.v1alpha1Runtime.Events(),
|
||||
ValidationMode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
|
||||
ResourceState: ctrl.v1alpha1Runtime.State().V1Alpha2().Resources(),
|
||||
},
|
||||
&config.MachineTypeController{},
|
||||
&cri.SeccompProfileController{},
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
cosiv1alpha1 "github.com/cosi-project/runtime/api/v1alpha1"
|
||||
@ -75,7 +76,7 @@ func (s *Server) Register(obj *grpc.Server) {
|
||||
}
|
||||
|
||||
// ApplyConfiguration implements [machine.MachineServiceServer].
|
||||
func (s *Server) ApplyConfiguration(_ context.Context, in *machine.ApplyConfigurationRequest) (*machine.ApplyConfigurationResponse, error) {
|
||||
func (s *Server) ApplyConfiguration(ctx context.Context, in *machine.ApplyConfigurationRequest) (*machine.ApplyConfigurationResponse, error) {
|
||||
if s.mode.IsAgent() {
|
||||
return nil, status.Error(codes.Unimplemented, "API is not implemented in agent mode")
|
||||
}
|
||||
@ -102,10 +103,15 @@ func (s *Server) ApplyConfiguration(_ context.Context, in *machine.ApplyConfigur
|
||||
return nil, status.Errorf(codes.InvalidArgument, "configuration validation failed: %s", err)
|
||||
}
|
||||
|
||||
warningsRuntime, err := cfgProvider.RuntimeValidate(ctx, s.controller.Runtime().State().V1Alpha2().Resources(), s.controller.Runtime().State().Platform().Mode())
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "runtime configuration validation failed: %s", err)
|
||||
}
|
||||
|
||||
reply := &machine.ApplyConfigurationResponse{
|
||||
Messages: []*machine.ApplyConfiguration{
|
||||
{
|
||||
Warnings: warnings,
|
||||
Warnings: slices.Concat(warnings, warningsRuntime),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -95,9 +95,6 @@ func (suite *GenerateConfigSuite) TestGenerate() {
|
||||
|
||||
suite.Require().NoError(err)
|
||||
|
||||
disk, err := config.Machine().Install().Disk()
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Require().EqualValues(request.MachineConfig.Type, config.Machine().Type())
|
||||
suite.Require().EqualValues(request.ClusterConfig.Name, config.Cluster().Name())
|
||||
suite.Require().EqualValues(request.ClusterConfig.ControlPlane.Endpoint, config.Cluster().Endpoint().String())
|
||||
@ -114,7 +111,7 @@ func (suite *GenerateConfigSuite) TestGenerate() {
|
||||
fmt.Sprintf("%s:v%s", constants.KubeletImage, request.MachineConfig.KubernetesVersion),
|
||||
config.Machine().Kubelet().Image(),
|
||||
)
|
||||
suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallDisk, disk)
|
||||
suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallDisk, config.Machine().Install().Disk())
|
||||
suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallImage, config.Machine().Install().Image())
|
||||
suite.Require().EqualValues(request.MachineConfig.NetworkConfig.Hostname, config.Machine().Network().Hostname())
|
||||
suite.Require().EqualValues(request.MachineConfig.NetworkConfig.Hostname, config.Machine().Network().Hostname())
|
||||
@ -149,9 +146,6 @@ func (suite *GenerateConfigSuite) TestGenerate() {
|
||||
|
||||
suite.Require().NoError(err)
|
||||
|
||||
disk, err = config.Machine().Install().Disk()
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Require().EqualValues(request.MachineConfig.Type, joinedConfig.Machine().Type())
|
||||
suite.Require().EqualValues(request.ClusterConfig.Name, joinedConfig.Cluster().Name())
|
||||
suite.Require().EqualValues(request.ClusterConfig.ControlPlane.Endpoint, joinedConfig.Cluster().Endpoint().String())
|
||||
@ -163,7 +157,7 @@ func (suite *GenerateConfigSuite) TestGenerate() {
|
||||
fmt.Sprintf("%s:v%s", constants.KubeletImage, request.MachineConfig.KubernetesVersion),
|
||||
joinedConfig.Machine().Kubelet().Image(),
|
||||
)
|
||||
suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallDisk, disk)
|
||||
suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallDisk, config.Machine().Install().Disk())
|
||||
suite.Require().EqualValues(
|
||||
request.MachineConfig.InstallConfig.InstallImage,
|
||||
joinedConfig.Machine().Install().Image(),
|
||||
|
||||
66
pkg/machinery/cel/build.go
Normal file
66
pkg/machinery/cel/build.go
Normal file
@ -0,0 +1,66 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package cel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common"
|
||||
"github.com/google/cel-go/common/ast"
|
||||
"github.com/google/cel-go/common/types"
|
||||
)
|
||||
|
||||
// Builder allows building CEL expressions programmatically.
|
||||
type Builder struct {
|
||||
ast.ExprFactory
|
||||
env *cel.Env
|
||||
nextID int64
|
||||
}
|
||||
|
||||
// NewBuilder creates a new builder.
|
||||
func NewBuilder(env *cel.Env) *Builder {
|
||||
return &Builder{
|
||||
ExprFactory: ast.NewExprFactory(),
|
||||
env: env,
|
||||
}
|
||||
}
|
||||
|
||||
// NextID returns the next unique ID.
|
||||
func (b *Builder) NextID() int64 {
|
||||
b.nextID++
|
||||
|
||||
return b.nextID
|
||||
}
|
||||
|
||||
// ToBooleanExpression converts the AST to a boolean expression.
|
||||
func (b *Builder) ToBooleanExpression(expr ast.Expr) (*Expression, error) {
|
||||
rawAst := ast.NewAST(expr, nil)
|
||||
|
||||
pbAst, err := ast.ToProto(rawAst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
celAst, err := cel.CheckedExprToAstWithSource(pbAst, common.NewTextSource(""))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var issues *cel.Issues
|
||||
|
||||
celAst, issues = b.env.Check(celAst)
|
||||
if issues != nil && issues.Err() != nil {
|
||||
return nil, issues.Err()
|
||||
}
|
||||
|
||||
if outputType := celAst.OutputType(); !outputType.IsExactType(types.BoolType) {
|
||||
return nil, fmt.Errorf("expression output type is %s, expected bool", outputType)
|
||||
}
|
||||
|
||||
return &Expression{
|
||||
ast: celAst,
|
||||
}, nil
|
||||
}
|
||||
32
pkg/machinery/cel/build_test.go
Normal file
32
pkg/machinery/cel/build_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package cel_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/cel"
|
||||
"github.com/siderolabs/talos/pkg/machinery/cel/celenv"
|
||||
)
|
||||
|
||||
func TestBuildDiskExpression(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
builder := cel.NewBuilder(celenv.DiskLocator())
|
||||
|
||||
expr := builder.NewSelect(
|
||||
builder.NextID(),
|
||||
builder.NewIdent(builder.NextID(), "disk"),
|
||||
"rotational",
|
||||
)
|
||||
|
||||
out, err := builder.ToBooleanExpression(expr)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "disk.rotational", out.String())
|
||||
}
|
||||
@ -11,6 +11,8 @@ import (
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/ryanuber/go-glob"
|
||||
"github.com/siderolabs/gen/xslices"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/api/resource/definitions/block"
|
||||
@ -36,6 +38,13 @@ var DiskLocator = sync.OnceValue(func() *cel.Env {
|
||||
cel.Types(&diskSpec),
|
||||
cel.Variable("disk", cel.ObjectType(string(diskSpec.ProtoReflect().Descriptor().FullName()))),
|
||||
cel.Variable("system_disk", types.BoolType),
|
||||
cel.Function("glob", // glob(pattern, string)
|
||||
cel.Overload("glob_string_string", []*cel.Type{cel.StringType, cel.StringType}, cel.BoolType,
|
||||
cel.BinaryBinding(func(arg1, arg2 ref.Val) ref.Val {
|
||||
return types.Bool(glob.Glob(string(arg1.(types.String)), string(arg2.(types.String))))
|
||||
}),
|
||||
),
|
||||
),
|
||||
},
|
||||
celUnitMultipliersConstants(),
|
||||
)...,
|
||||
|
||||
@ -30,6 +30,10 @@ func TestDiskLocator(t *testing.T) {
|
||||
name: "disk size",
|
||||
expression: "disk.size > 1000u * GiB && !disk.rotational",
|
||||
},
|
||||
{
|
||||
name: "glob",
|
||||
expression: "glob('sd[a-z]', disk.dev_path)",
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/siderolabs/crypto/x509"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/cel"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/machine"
|
||||
)
|
||||
|
||||
@ -91,7 +92,8 @@ type File interface {
|
||||
type Install interface {
|
||||
Image() string
|
||||
Extensions() []Extension
|
||||
Disk() (string, error)
|
||||
Disk() string
|
||||
DiskMatchExpression() (*cel.Expression, error)
|
||||
ExtraKernelArgs() []string
|
||||
Zero() bool
|
||||
LegacyBIOSSupport() bool
|
||||
|
||||
@ -4,7 +4,13 @@
|
||||
|
||||
package config
|
||||
|
||||
import "github.com/siderolabs/talos/pkg/machinery/config/validation"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/validation"
|
||||
)
|
||||
|
||||
// Validator is the interface to validate configuration.
|
||||
//
|
||||
@ -13,3 +19,15 @@ type Validator interface {
|
||||
// Validate checks configuration and returns warnings and fatal errors (as multierror).
|
||||
Validate(validation.RuntimeMode, ...validation.Option) ([]string, error)
|
||||
}
|
||||
|
||||
// RuntimeValidator is the interface to validate configuration in the runtime context.
|
||||
//
|
||||
// This interface is used by Talos itself to validate configuration on the machine (vs. the Validator interface).
|
||||
//
|
||||
// The errors reported by Validator & RuntimeValidator are different.
|
||||
type RuntimeValidator interface {
|
||||
// RuntimeValidate validates the config in the runtime context.
|
||||
//
|
||||
// The method returns warnings and fatal errors (as multierror).
|
||||
RuntimeValidate(context.Context, state.State, validation.RuntimeMode, ...validation.Option) ([]string, error)
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ import (
|
||||
func callMethods(t testing.TB, obj reflect.Value, chain ...string) {
|
||||
t.Helper()
|
||||
|
||||
if obj.Kind() == reflect.Interface && obj.IsNil() {
|
||||
if (obj.Kind() == reflect.Interface || obj.Kind() == reflect.Pointer) && obj.IsNil() {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -7,10 +7,12 @@ package container
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/siderolabs/gen/xslices"
|
||||
|
||||
@ -305,6 +307,35 @@ func (container *Container) Validate(mode validation.RuntimeMode, opt ...validat
|
||||
return warnings, multiErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// RuntimeValidate validates the config in the runtime context.
|
||||
func (container *Container) RuntimeValidate(ctx context.Context, st state.State, mode validation.RuntimeMode, opt ...validation.Option) ([]string, error) {
|
||||
var (
|
||||
warnings []string
|
||||
err error
|
||||
)
|
||||
|
||||
if container.v1alpha1Config != nil {
|
||||
warnings, err = container.v1alpha1Config.RuntimeValidate(ctx, st, mode, opt...)
|
||||
}
|
||||
|
||||
var multiErr *multierror.Error
|
||||
|
||||
if err != nil {
|
||||
multiErr = multierror.Append(multiErr, err)
|
||||
}
|
||||
|
||||
for _, doc := range container.documents {
|
||||
if validatableDoc, ok := doc.(config.RuntimeValidator); ok {
|
||||
docWarnings, docErr := validatableDoc.RuntimeValidate(ctx, st, mode, opt...)
|
||||
|
||||
warnings = append(warnings, docWarnings...)
|
||||
multiErr = multierror.Append(multiErr, docErr)
|
||||
}
|
||||
}
|
||||
|
||||
return warnings, multiErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// RedactSecrets returns a copy of the Provider with all secrets replaced with the given string.
|
||||
func (container *Container) RedactSecrets(replacement string) coreconfig.Provider {
|
||||
clone := container.clone()
|
||||
|
||||
@ -15,6 +15,9 @@ type Encoder = config.Encoder
|
||||
// Validator provides the interface to validate configuration.
|
||||
type Validator = config.Validator
|
||||
|
||||
// RuntimeValidator provides the interface to validate configuration in the runtime context.
|
||||
type RuntimeValidator = config.RuntimeValidator
|
||||
|
||||
// Container provides the interface to access configuration documents.
|
||||
//
|
||||
// Container might contain multiple config documents, supporting encoding/decoding,
|
||||
@ -22,6 +25,7 @@ type Validator = config.Validator
|
||||
type Container interface {
|
||||
Encoder
|
||||
Validator
|
||||
RuntimeValidator
|
||||
|
||||
Readonly() bool
|
||||
|
||||
|
||||
@ -2245,9 +2245,9 @@
|
||||
"busPath": {
|
||||
"type": "string",
|
||||
"title": "busPath",
|
||||
"description": "Disk bus path.\nWarning: This requires special configuration for NVMe drives. For details, see https://github.com/siderolabs/go-blockdevice/issues/114.\n",
|
||||
"markdownDescription": "Disk bus path.\nWarning: This requires special configuration for NVMe drives. For details, see https://github.com/siderolabs/go-blockdevice/issues/114.",
|
||||
"x-intellij-html-description": "\u003cp\u003eDisk bus path.\nWarning: This requires special configuration for NVMe drives. For details, see \u003ca href=\"https://github.com/siderolabs/go-blockdevice/issues/114\" target=\"_blank\"\u003ehttps://github.com/siderolabs/go-blockdevice/issues/114\u003c/a\u003e.\u003c/p\u003e\n"
|
||||
"description": "Disk bus path.\n",
|
||||
"markdownDescription": "Disk bus path.",
|
||||
"x-intellij-html-description": "\u003cp\u003eDisk bus path.\u003c/p\u003e\n"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package blockhelpers provides helper functions for working with block resources.
|
||||
package blockhelpers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/safe"
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
|
||||
blockpb "github.com/siderolabs/talos/pkg/machinery/api/resource/definitions/block"
|
||||
"github.com/siderolabs/talos/pkg/machinery/cel"
|
||||
"github.com/siderolabs/talos/pkg/machinery/cel/celenv"
|
||||
"github.com/siderolabs/talos/pkg/machinery/proto"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/block"
|
||||
)
|
||||
|
||||
// MatchDisks returns a list of disks that match the given expression.
|
||||
func MatchDisks(ctx context.Context, st state.State, expression *cel.Expression) ([]*block.Disk, error) {
|
||||
disks, err := safe.StateListAll[*block.Disk](ctx, st)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var matchedDisks []*block.Disk
|
||||
|
||||
for disk := range disks.All() {
|
||||
spec := &blockpb.DiskSpec{}
|
||||
|
||||
if err = proto.ResourceSpecToProto(disk, spec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matches, err := expression.EvalBool(celenv.DiskLocator(), map[string]any{
|
||||
"disk": spec,
|
||||
"system_disk": false,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if matches {
|
||||
matchedDisks = append(matchedDisks, disk)
|
||||
}
|
||||
}
|
||||
|
||||
return matchedDisks, nil
|
||||
}
|
||||
@ -8,8 +8,6 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/siderolabs/go-blockdevice/blockdevice/util/disk"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
@ -125,25 +123,11 @@ func TestDiskSizeMatcherUnmarshal(t *testing.T) {
|
||||
match: false,
|
||||
},
|
||||
} {
|
||||
var (
|
||||
size uint64
|
||||
err error
|
||||
)
|
||||
|
||||
if test.size != "" {
|
||||
size, err = humanize.ParseBytes(test.size)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal([]byte(fmt.Sprintf("m: '%s'\n", test.condition)), &obj)
|
||||
err := yaml.Unmarshal([]byte(fmt.Sprintf("m: '%s'\n", test.condition)), &obj)
|
||||
if test.err {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
if test.size != "" {
|
||||
require.Equal(t, obj.M.Matcher(&disk.Disk{Size: size}), test.match, test.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,20 +7,23 @@ package v1alpha1
|
||||
import (
|
||||
"crypto/tls"
|
||||
stdx509 "crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/common/ast"
|
||||
"github.com/google/cel-go/common/operators"
|
||||
"github.com/google/cel-go/common/types"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/siderolabs/crypto/x509"
|
||||
"github.com/siderolabs/gen/xslices"
|
||||
"github.com/siderolabs/go-blockdevice/blockdevice/util/disk"
|
||||
"github.com/siderolabs/go-blockdevice/v2/encryption"
|
||||
"github.com/siderolabs/go-pointer"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/cel"
|
||||
"github.com/siderolabs/talos/pkg/machinery/cel/celenv"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/config"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/machine"
|
||||
"github.com/siderolabs/talos/pkg/machinery/constants"
|
||||
@ -1217,72 +1220,174 @@ func (i *InstallConfig) Extensions() []config.Extension {
|
||||
}
|
||||
|
||||
// Disk implements the config.Provider interface.
|
||||
func (i *InstallConfig) Disk() (string, error) {
|
||||
matchers := i.DiskMatchers()
|
||||
if len(matchers) > 0 {
|
||||
d, err := disk.Find(matchers...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if d != nil {
|
||||
return d.DeviceName, nil
|
||||
}
|
||||
|
||||
return "", errors.New("no disk found matching provided parameters")
|
||||
}
|
||||
|
||||
return i.InstallDisk, nil
|
||||
func (i *InstallConfig) Disk() string {
|
||||
return i.InstallDisk
|
||||
}
|
||||
|
||||
// DiskMatchers implements the config.Provider interface.
|
||||
// DiskMatchExpression returns the disk matcher expression by inspecting the InstallDiskSelector.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (i *InstallConfig) DiskMatchers() []disk.Matcher {
|
||||
if i.InstallDiskSelector != nil {
|
||||
selector := i.InstallDiskSelector
|
||||
|
||||
var matchers []disk.Matcher
|
||||
if selector.Size != nil {
|
||||
matchers = append(matchers, selector.Size.Matcher)
|
||||
}
|
||||
|
||||
if selector.UUID != "" {
|
||||
matchers = append(matchers, disk.WithUUID(selector.UUID))
|
||||
}
|
||||
|
||||
if selector.WWID != "" {
|
||||
matchers = append(matchers, disk.WithWWID(selector.WWID))
|
||||
}
|
||||
|
||||
if selector.Model != "" {
|
||||
matchers = append(matchers, disk.WithModel(selector.Model))
|
||||
}
|
||||
|
||||
if selector.Name != "" {
|
||||
matchers = append(matchers, disk.WithName(selector.Name))
|
||||
}
|
||||
|
||||
if selector.Serial != "" {
|
||||
matchers = append(matchers, disk.WithSerial(selector.Serial))
|
||||
}
|
||||
|
||||
if selector.Modalias != "" {
|
||||
matchers = append(matchers, disk.WithModalias(selector.Modalias))
|
||||
}
|
||||
|
||||
if disk.Type(selector.Type) != disk.TypeUnknown {
|
||||
matchers = append(matchers, disk.WithType(disk.Type(selector.Type)))
|
||||
}
|
||||
|
||||
if selector.BusPath != "" {
|
||||
matchers = append(matchers, disk.WithBusPath(selector.BusPath))
|
||||
}
|
||||
|
||||
return matchers
|
||||
func (i *InstallConfig) DiskMatchExpression() (*cel.Expression, error) {
|
||||
if i.InstallDiskSelector == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil
|
||||
var exprs []ast.Expr
|
||||
|
||||
builder := cel.NewBuilder(celenv.DiskLocator())
|
||||
selector := i.InstallDiskSelector
|
||||
|
||||
if selector.Size != nil {
|
||||
op := selector.Size.MatchData.Op
|
||||
if op == "" {
|
||||
op = "=="
|
||||
}
|
||||
|
||||
exprs = append(exprs, // disk.size op value
|
||||
builder.NewCall(
|
||||
builder.NextID(),
|
||||
"_"+op+"_",
|
||||
builder.NewSelect(
|
||||
builder.NextID(),
|
||||
builder.NewIdent(builder.NextID(), "disk"),
|
||||
"size",
|
||||
),
|
||||
builder.NewLiteral(
|
||||
builder.NextID(),
|
||||
types.Uint(selector.Size.MatchData.Size),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
patternMatcherExpr := func(pattern, field string) ast.Expr { // glob(pattern, disk.$field)
|
||||
return builder.NewCall(
|
||||
builder.NextID(),
|
||||
"glob",
|
||||
builder.NewLiteral(builder.NextID(), types.String(pattern)),
|
||||
builder.NewSelect(
|
||||
builder.NextID(),
|
||||
builder.NewIdent(builder.NextID(), "disk"),
|
||||
field,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
directMatchExpr := func(value, field string) ast.Expr { // disk.$field == value
|
||||
return builder.NewCall(
|
||||
builder.NextID(),
|
||||
operators.Equals,
|
||||
builder.NewSelect(
|
||||
builder.NextID(),
|
||||
builder.NewIdent(builder.NextID(), "disk"),
|
||||
field,
|
||||
),
|
||||
builder.NewLiteral(builder.NextID(), types.String(value)),
|
||||
)
|
||||
}
|
||||
|
||||
if selector.UUID != "" {
|
||||
// not supported
|
||||
return nil, fmt.Errorf("selector on uuid is not supported")
|
||||
}
|
||||
|
||||
if selector.WWID != "" {
|
||||
exprs = append(exprs, patternMatcherExpr(selector.WWID, "wwid"))
|
||||
}
|
||||
|
||||
if selector.Model != "" {
|
||||
exprs = append(exprs, patternMatcherExpr(selector.Model, "model"))
|
||||
}
|
||||
|
||||
if selector.Name != "" {
|
||||
// not supported
|
||||
return nil, fmt.Errorf("selector on name is not supported")
|
||||
}
|
||||
|
||||
if selector.Serial != "" {
|
||||
exprs = append(exprs, patternMatcherExpr(selector.Serial, "serial"))
|
||||
}
|
||||
|
||||
if selector.Modalias != "" {
|
||||
exprs = append(exprs, patternMatcherExpr(selector.Modalias, "modalias"))
|
||||
}
|
||||
|
||||
if selector.Type != "" {
|
||||
switch selector.Type {
|
||||
case "nvme": // disk.transport == "nvme"
|
||||
exprs = append(exprs, directMatchExpr("nvme", "transport"))
|
||||
case "sd": // disk.transport == "mmc"
|
||||
exprs = append(exprs, directMatchExpr("mmc", "transport"))
|
||||
case "hdd": // disk.rotational
|
||||
exprs = append(exprs, builder.NewSelect(
|
||||
builder.NextID(),
|
||||
builder.NewIdent(builder.NextID(), "disk"),
|
||||
"rotational",
|
||||
))
|
||||
case "ssd": // disk.transport != "" && !disk.rotational
|
||||
exprs = append(exprs,
|
||||
builder.NewCall(
|
||||
builder.NextID(),
|
||||
operators.NotEquals,
|
||||
builder.NewSelect(
|
||||
builder.NextID(),
|
||||
builder.NewIdent(builder.NextID(), "disk"),
|
||||
"transport",
|
||||
),
|
||||
builder.NewLiteral(builder.NextID(), types.String("")),
|
||||
),
|
||||
builder.NewCall(
|
||||
builder.NextID(),
|
||||
operators.LogicalNot,
|
||||
builder.NewSelect(
|
||||
builder.NextID(),
|
||||
builder.NewIdent(builder.NextID(), "disk"),
|
||||
"rotational",
|
||||
),
|
||||
),
|
||||
)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported disk type %q", selector.Type)
|
||||
}
|
||||
}
|
||||
|
||||
if selector.BusPath != "" {
|
||||
exprs = append(exprs, patternMatcherExpr(selector.BusPath, "buspath"))
|
||||
}
|
||||
|
||||
// exclude readonly disks: !disk.readonly
|
||||
exprs = append(exprs, builder.NewCall(
|
||||
builder.NextID(),
|
||||
operators.LogicalNot,
|
||||
builder.NewSelect(
|
||||
builder.NextID(),
|
||||
builder.NewIdent(builder.NextID(), "disk"),
|
||||
"readonly",
|
||||
),
|
||||
))
|
||||
|
||||
// exclude CD-ROMs: !disk.cdrom
|
||||
exprs = append(exprs, builder.NewCall(
|
||||
builder.NextID(),
|
||||
operators.LogicalNot,
|
||||
builder.NewSelect(
|
||||
builder.NextID(),
|
||||
builder.NewIdent(builder.NextID(), "disk"),
|
||||
"cdrom",
|
||||
),
|
||||
))
|
||||
|
||||
// reduce all expressions to a single one with &&
|
||||
for len(exprs) > 1 {
|
||||
exprs = append(exprs[:len(exprs)-2], builder.NewCall(
|
||||
builder.NextID(),
|
||||
operators.LogicalAnd,
|
||||
exprs[len(exprs)-2],
|
||||
exprs[len(exprs)-1],
|
||||
))
|
||||
}
|
||||
|
||||
return builder.ToBooleanExpression(exprs[0])
|
||||
}
|
||||
|
||||
// ExtraKernelArgs implements the config.Provider interface.
|
||||
|
||||
@ -0,0 +1,93 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package v1alpha1_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
|
||||
)
|
||||
|
||||
func TestInstallDiskSelector(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
|
||||
selector v1alpha1.InstallDiskSelector
|
||||
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "size",
|
||||
|
||||
selector: v1alpha1.InstallDiskSelector{
|
||||
Size: &v1alpha1.InstallDiskSizeMatcher{
|
||||
MatchData: v1alpha1.InstallDiskSizeMatchData{
|
||||
Op: "<=",
|
||||
Size: 256 * 1024,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
expected: `disk.size <= 262144u && !disk.readonly && !disk.cdrom`,
|
||||
},
|
||||
{
|
||||
name: "size and type",
|
||||
|
||||
selector: v1alpha1.InstallDiskSelector{
|
||||
Size: &v1alpha1.InstallDiskSizeMatcher{
|
||||
MatchData: v1alpha1.InstallDiskSizeMatchData{
|
||||
Size: 1024 * 1024,
|
||||
},
|
||||
},
|
||||
Type: v1alpha1.InstallDiskType("nvme"),
|
||||
},
|
||||
|
||||
expected: `disk.size == 1048576u && disk.transport == "nvme" && !disk.readonly && !disk.cdrom`,
|
||||
},
|
||||
{
|
||||
name: "size and type and modalias",
|
||||
|
||||
selector: v1alpha1.InstallDiskSelector{
|
||||
Size: &v1alpha1.InstallDiskSizeMatcher{
|
||||
MatchData: v1alpha1.InstallDiskSizeMatchData{
|
||||
Size: 1024 * 1024,
|
||||
},
|
||||
},
|
||||
Type: v1alpha1.InstallDiskType("hdd"),
|
||||
Modalias: "pci:1234:5678*",
|
||||
},
|
||||
|
||||
expected: `disk.size == 1048576u && glob("pci:1234:5678*", disk.modalias) && disk.rotational &&
|
||||
!disk.readonly && !disk.cdrom`,
|
||||
},
|
||||
{
|
||||
name: "ssd",
|
||||
|
||||
selector: v1alpha1.InstallDiskSelector{
|
||||
Type: v1alpha1.InstallDiskType("ssd"),
|
||||
},
|
||||
|
||||
expected: `disk.transport != "" && !disk.rotational && !disk.readonly && !disk.cdrom`,
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
installCfg := &v1alpha1.InstallConfig{
|
||||
InstallDiskSelector: &test.selector,
|
||||
}
|
||||
|
||||
expr, err := installCfg.DiskMatchExpression()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, expr.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,6 @@ import (
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/siderolabs/crypto/x509"
|
||||
"github.com/siderolabs/go-blockdevice/blockdevice/util/disk"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/config"
|
||||
@ -879,11 +878,6 @@ func (m *InstallDiskSizeMatcher) UnmarshalYAML(unmarshal func(any) error) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// Matcher is a method that can handle some custom disk matching logic.
|
||||
func (m *InstallDiskSizeMatcher) Matcher(d *disk.Disk) bool {
|
||||
return m.MatchData.Compare(d)
|
||||
}
|
||||
|
||||
// InstallDiskSizeMatchData contains data for comparison - Op and Size.
|
||||
//
|
||||
//docgen:nodoc
|
||||
@ -892,53 +886,8 @@ type InstallDiskSizeMatchData struct {
|
||||
Size uint64
|
||||
}
|
||||
|
||||
// Compare is the method to compare disk size.
|
||||
func (in *InstallDiskSizeMatchData) Compare(d *disk.Disk) bool {
|
||||
switch in.Op {
|
||||
case ">=":
|
||||
return d.Size >= in.Size
|
||||
case "<=":
|
||||
return d.Size <= in.Size
|
||||
case ">":
|
||||
return d.Size > in.Size
|
||||
case "<":
|
||||
return d.Size < in.Size
|
||||
case "":
|
||||
fallthrough
|
||||
case "==":
|
||||
return d.Size == in.Size
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// InstallDiskType custom type for disk type selector.
|
||||
type InstallDiskType disk.Type
|
||||
|
||||
// MarshalYAML is a custom marshaller for `InstallDiskSizeMatcher`.
|
||||
func (it InstallDiskType) MarshalYAML() (any, error) {
|
||||
return disk.Type(it).String(), nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML is a custom unmarshaler for `InstallDiskType`.
|
||||
func (it *InstallDiskType) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var (
|
||||
t string
|
||||
err error
|
||||
)
|
||||
|
||||
if err = unmarshal(&t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dt, err := disk.ParseType(t); err == nil {
|
||||
*it = InstallDiskType(dt)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
type InstallDiskType string
|
||||
|
||||
// InstallDiskSelector represents a disk query parameters for the install disk lookup.
|
||||
type InstallDiskSelector struct {
|
||||
@ -974,7 +923,6 @@ type InstallDiskSelector struct {
|
||||
Type InstallDiskType `yaml:"type,omitempty"`
|
||||
// description: |
|
||||
// Disk bus path.
|
||||
// Warning: This requires special configuration for NVMe drives. For details, see https://github.com/siderolabs/go-blockdevice/issues/114.
|
||||
// examples:
|
||||
// - value: '"/pci0000:00/0000:00:17.0/ata1/host0/target0:0:0/0:0:0:0"'
|
||||
// - value: '"/pci0000:00/*"'
|
||||
|
||||
@ -1150,7 +1150,7 @@ func (InstallDiskSelector) Doc() *encoder.Doc {
|
||||
Name: "busPath",
|
||||
Type: "string",
|
||||
Note: "",
|
||||
Description: "Disk bus path.\nWarning: This requires special configuration for NVMe drives. For details, see https://github.com/siderolabs/go-blockdevice/issues/114.",
|
||||
Description: "Disk bus path.",
|
||||
Comments: [3]string{"" /* encoder.HeadComment */, "Disk bus path." /* encoder.LineComment */, "" /* encoder.FootComment */},
|
||||
},
|
||||
},
|
||||
|
||||
@ -5,23 +5,25 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
sideronet "github.com/siderolabs/net"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/config"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/machine"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/types/block/blockhelpers"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/validation"
|
||||
"github.com/siderolabs/talos/pkg/machinery/constants"
|
||||
"github.com/siderolabs/talos/pkg/machinery/kubelet"
|
||||
@ -94,24 +96,13 @@ func (c *Config) Validate(mode validation.RuntimeMode, options ...validation.Opt
|
||||
if c.MachineConfig.MachineInstall == nil {
|
||||
result = multierror.Append(result, fmt.Errorf("install instructions are required in %q mode", mode))
|
||||
} else {
|
||||
if opts.Local {
|
||||
if c.MachineConfig.MachineInstall.InstallDisk == "" && len(c.MachineConfig.MachineInstall.DiskMatchers()) == 0 {
|
||||
result = multierror.Append(result, errors.New("either install disk or diskSelector should be defined"))
|
||||
}
|
||||
} else {
|
||||
disk, err := c.MachineConfig.MachineInstall.Disk()
|
||||
matcher, err := c.MachineConfig.MachineInstall.DiskMatchExpression()
|
||||
if err != nil {
|
||||
result = multierror.Append(result, fmt.Errorf("install disk selector is invalid: %w", err))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
} else {
|
||||
if disk == "" {
|
||||
result = multierror.Append(result, fmt.Errorf("an install disk is required in %q mode", mode))
|
||||
}
|
||||
|
||||
if _, err := os.Stat(disk); os.IsNotExist(err) {
|
||||
result = multierror.Append(result, fmt.Errorf("specified install disk does not exist: %q", disk))
|
||||
}
|
||||
}
|
||||
if c.MachineConfig.MachineInstall.InstallDisk == "" && matcher == nil {
|
||||
result = multierror.Append(result, errors.New("either install disk or diskSelector should be defined"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -916,3 +907,33 @@ func (e *EtcdConfig) Validate() error {
|
||||
|
||||
return result.ErrorOrNil()
|
||||
}
|
||||
|
||||
// RuntimeValidate validates the config in runtime context.
|
||||
//
|
||||
// In runtime context, resource state is available.
|
||||
func (c *Config) RuntimeValidate(ctx context.Context, st state.State, mode validation.RuntimeMode, opt ...validation.Option) ([]string, error) {
|
||||
var (
|
||||
warnings []string
|
||||
result *multierror.Error
|
||||
)
|
||||
|
||||
if c.MachineConfig != nil {
|
||||
if mode.RequiresInstall() && c.MachineConfig.MachineInstall != nil {
|
||||
diskExpr, err := c.MachineConfig.MachineInstall.DiskMatchExpression()
|
||||
if err != nil {
|
||||
result = multierror.Append(result, fmt.Errorf("install disk selector is invalid: %w", err))
|
||||
} else if diskExpr != nil {
|
||||
matchedDisks, err := blockhelpers.MatchDisks(ctx, st, diskExpr)
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
|
||||
if len(matchedDisks) == 0 {
|
||||
result = multierror.Append(result, fmt.Errorf("no disks matched the expression: %s", diskExpr))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return warnings, result.ErrorOrNil()
|
||||
}
|
||||
|
||||
@ -22,11 +22,11 @@ require (
|
||||
github.com/mdlayher/ethtool v0.2.0
|
||||
github.com/opencontainers/runtime-spec v1.2.0
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10
|
||||
github.com/ryanuber/go-glob v1.0.0
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
|
||||
github.com/siderolabs/crypto v0.5.0
|
||||
github.com/siderolabs/gen v0.7.0
|
||||
github.com/siderolabs/go-api-signature v0.3.6
|
||||
github.com/siderolabs/go-blockdevice v0.4.8
|
||||
github.com/siderolabs/go-blockdevice/v2 v2.0.2
|
||||
github.com/siderolabs/go-pointer v1.0.0
|
||||
github.com/siderolabs/net v0.4.0
|
||||
@ -63,7 +63,6 @@ require (
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
|
||||
@ -109,8 +109,6 @@ github.com/siderolabs/gen v0.7.0 h1:uHAt3WD0dof28NHFuguWBbDokaXQraR/HyVxCLw2QCU=
|
||||
github.com/siderolabs/gen v0.7.0/go.mod h1:an3a2Y53O7kUjnnK8Bfu3gewtvnIOu5RTU6HalFtXQQ=
|
||||
github.com/siderolabs/go-api-signature v0.3.6 h1:wDIsXbpl7Oa/FXvxB6uz4VL9INA9fmr3EbmjEZYFJrU=
|
||||
github.com/siderolabs/go-api-signature v0.3.6/go.mod h1:hoH13AfunHflxbXfh+NoploqV13ZTDfQ1mQJWNVSW9U=
|
||||
github.com/siderolabs/go-blockdevice v0.4.8 h1:KfdWvIx0Jft5YVuCsFIJFwjWEF1oqtzkgX9PeU9cX4c=
|
||||
github.com/siderolabs/go-blockdevice v0.4.8/go.mod h1:4PeOuk71pReJj1JQEXDE7kIIQJPVe8a+HZQa+qjxSEA=
|
||||
github.com/siderolabs/go-blockdevice/v2 v2.0.2 h1:GIdOBrCLQ7X9jbr0P/+7paw5SIfp/LL+dx9mTOzmw8w=
|
||||
github.com/siderolabs/go-blockdevice/v2 v2.0.2/go.mod h1:74htzCV913UzaLZ4H+NBXkwWlYnBJIq5m/379ZEcu8w=
|
||||
github.com/siderolabs/go-pointer v1.0.0 h1:6TshPKep2doDQJAAtHUuHWXbca8ZfyRySjSBT/4GsMU=
|
||||
|
||||
@ -2,21 +2,20 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package volumes
|
||||
// Package proto defines a functions to work with proto messages.
|
||||
package proto
|
||||
|
||||
import (
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"github.com/siderolabs/protoenc"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/proto"
|
||||
)
|
||||
|
||||
// ResourceSpecToProto converts a resource spec to a proto message.
|
||||
func ResourceSpecToProto(i resource.Resource, o proto.Message) error {
|
||||
func ResourceSpecToProto(i resource.Resource, o Message) error {
|
||||
marshaled, err := protoenc.Marshal(i.Spec())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return proto.Unmarshal(marshaled, o)
|
||||
return Unmarshal(marshaled, o)
|
||||
}
|
||||
@ -1976,7 +1976,7 @@ size: <= 2TB
|
||||
|`uuid` |string |Disk UUID `/sys/block/<dev>/uuid`. | |
|
||||
|`wwid` |string |Disk WWID `/sys/block/<dev>/wwid`. | |
|
||||
|`type` |InstallDiskType |Disk Type. |`ssd`<br />`hdd`<br />`nvme`<br />`sd`<br /> |
|
||||
|`busPath` |string |<details><summary>Disk bus path.</summary>Warning: This requires special configuration for NVMe drives. For details, see https://github.com/siderolabs/go-blockdevice/issues/114.</details> <details><summary>Show example(s)</summary>{{< highlight yaml >}}
|
||||
|`busPath` |string |Disk bus path. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
|
||||
busPath: /pci0000:00/0000:00:17.0/ata1/host0/target0:0:0/0:0:0:0
|
||||
{{< /highlight >}}{{< highlight yaml >}}
|
||||
busPath: /pci0000:00/*
|
||||
|
||||
@ -2245,9 +2245,9 @@
|
||||
"busPath": {
|
||||
"type": "string",
|
||||
"title": "busPath",
|
||||
"description": "Disk bus path.\nWarning: This requires special configuration for NVMe drives. For details, see https://github.com/siderolabs/go-blockdevice/issues/114.\n",
|
||||
"markdownDescription": "Disk bus path.\nWarning: This requires special configuration for NVMe drives. For details, see https://github.com/siderolabs/go-blockdevice/issues/114.",
|
||||
"x-intellij-html-description": "\u003cp\u003eDisk bus path.\nWarning: This requires special configuration for NVMe drives. For details, see \u003ca href=\"https://github.com/siderolabs/go-blockdevice/issues/114\" target=\"_blank\"\u003ehttps://github.com/siderolabs/go-blockdevice/issues/114\u003c/a\u003e.\u003c/p\u003e\n"
|
||||
"description": "Disk bus path.\n",
|
||||
"markdownDescription": "Disk bus path.",
|
||||
"x-intellij-html-description": "\u003cp\u003eDisk bus path.\u003c/p\u003e\n"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user