Skip to content

Commit 9a02ecc

Browse files
committed
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 <[email protected]>
1 parent eba35f4 commit 9a02ecc

File tree

31 files changed

+603
-194
lines changed

31 files changed

+603
-194
lines changed

Diff for: hack/release.toml

+6-6
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ preface = """
1818
[notes.updates]
1919
title = "Component Updates"
2020
description = """\
21-
Linux: 6.6.59
22-
containerd: 2.0.0
23-
Flannel: 0.26.0
24-
Kubernetes: 1.32.0-beta.0
25-
runc: 1.2.1
21+
* Linux: 6.6.59
22+
* containerd: 2.0.0
23+
* Flannel: 0.26.0
24+
* Kubernetes: 1.32.0-beta.0
25+
* runc: 1.2.1
2626
2727
Talos is built with Go 1.23.2.
2828
"""
@@ -68,7 +68,7 @@ This command allows you to view the cgroup resource consumption and limits for a
6868
[notes.udevd]
6969
title = "udevd"
7070
description = """\
71-
Talos previously used `udevd` to provide `udevd`, now it uses `systemd-udevd` instead.
71+
Talos previously used `eudev` to provide `udevd`, now it uses `systemd-udevd` instead.
7272
"""
7373

7474
[make_deps]

Diff for: internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go

+13-6
Original file line numberDiff line numberDiff line change
@@ -179,16 +179,23 @@ func (s *Server) ApplyConfiguration(ctx context.Context, in *machine.ApplyConfig
179179
return nil, status.Error(codes.InvalidArgument, err.Error())
180180
}
181181

182-
warnings, err := cfgProvider.Validate(
183-
modeWrapper{
184-
Mode: s.Controller.Runtime().State().Platform().Mode(),
185-
installed: s.Controller.Runtime().State().Machine().Installed(),
186-
},
187-
)
182+
validationMode := modeWrapper{
183+
Mode: s.Controller.Runtime().State().Platform().Mode(),
184+
installed: s.Controller.Runtime().State().Machine().Installed(),
185+
}
186+
187+
warnings, err := cfgProvider.Validate(validationMode)
188188
if err != nil {
189189
return nil, status.Error(codes.InvalidArgument, err.Error())
190190
}
191191

192+
warningsRuntime, err := cfgProvider.RuntimeValidate(ctx, s.Controller.Runtime().State().V1Alpha2().Resources(), validationMode)
193+
if err != nil {
194+
return nil, status.Error(codes.InvalidArgument, err.Error())
195+
}
196+
197+
warnings = slices.Concat(warnings, warningsRuntime)
198+
192199
//nolint:exhaustive
193200
switch in.Mode {
194201
// --mode=try

Diff for: internal/app/machined/pkg/controllers/block/volume_manager.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/block/internal/volumes"
2424
blockpb "github.com/siderolabs/talos/pkg/machinery/api/resource/definitions/block"
25+
"github.com/siderolabs/talos/pkg/machinery/proto"
2526
"github.com/siderolabs/talos/pkg/machinery/resources/block"
2627
"github.com/siderolabs/talos/pkg/machinery/resources/hardware"
2728
"github.com/siderolabs/talos/pkg/machinery/resources/runtime"
@@ -164,7 +165,7 @@ func (ctrl *VolumeManagerController) Run(ctx context.Context, r controller.Runti
164165
discoveredVolumesSpecs, err := safe.Map(discoveredVolumes, func(dv *block.DiscoveredVolume) (*blockpb.DiscoveredVolumeSpec, error) {
165166
spec := &blockpb.DiscoveredVolumeSpec{}
166167

167-
return spec, volumes.ResourceSpecToProto(dv, spec)
168+
return spec, proto.ResourceSpecToProto(dv, spec)
168169
})
169170
if err != nil {
170171
return fmt.Errorf("error mapping discovered volumes: %w", err)
@@ -204,7 +205,7 @@ func (ctrl *VolumeManagerController) Run(ctx context.Context, r controller.Runti
204205
diskSpecs, err := safe.Map(disks, func(d *block.Disk) (volumes.DiskContext, error) {
205206
spec := &blockpb.DiskSpec{}
206207

207-
if err := volumes.ResourceSpecToProto(d, spec); err != nil {
208+
if err := proto.ResourceSpecToProto(d, spec); err != nil {
208209
return volumes.DiskContext{}, err
209210
}
210211

Diff for: internal/app/machined/pkg/controllers/config/acquire.go

+18-4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"io"
1515
"net/http"
1616
"os"
17+
"slices"
1718
"strings"
1819

1920
"github.com/cosi-project/runtime/pkg/controller"
@@ -68,6 +69,7 @@ type AcquireController struct {
6869
EventPublisher talosruntime.Publisher
6970
ValidationMode validation.RuntimeMode
7071
ConfigPath string
72+
ResourceState state.State
7173

7274
configSourcesUsed []string
7375
}
@@ -345,7 +347,12 @@ func (ctrl *AcquireController) loadFromPlatform(ctx context.Context, logger *zap
345347
return nil, fmt.Errorf("failed to validate config acquired via platform %s: %w", platformName, err)
346348
}
347349

348-
for _, warning := range warnings {
350+
warningsRuntime, err := cfg.RuntimeValidate(ctx, ctrl.ResourceState, ctrl.ValidationMode)
351+
if err != nil {
352+
return nil, fmt.Errorf("failed to runtime validate config acquired via platform %s: %w", platformName, err)
353+
}
354+
355+
for _, warning := range slices.Concat(warnings, warningsRuntime) {
349356
logger.Warn("config validation warning", zap.String("platform", platformName), zap.String("warning", warning))
350357
}
351358

@@ -364,7 +371,7 @@ func (ctrl *AcquireController) stateCmdline(ctx context.Context, r controller.Ru
364371
return ctrl.stateMaintenanceEnter, nil, nil
365372
}
366373

367-
cfg, err := ctrl.loadFromCmdline(logger)
374+
cfg, err := ctrl.loadFromCmdline(ctx, logger)
368375
if err != nil {
369376
return nil, nil, err
370377
}
@@ -386,7 +393,9 @@ func (ctrl *AcquireController) stateCmdline(ctx context.Context, r controller.Ru
386393
}
387394

388395
// loadFromCmdline is a helper function for stateCmdline.
389-
func (ctrl *AcquireController) loadFromCmdline(logger *zap.Logger) (config.Provider, error) {
396+
//
397+
//nolint:gocyclo
398+
func (ctrl *AcquireController) loadFromCmdline(ctx context.Context, logger *zap.Logger) (config.Provider, error) {
390399
cmdline := ctrl.CmdlineGetter()
391400

392401
param := cmdline.Get(constants.KernelParamConfigInline)
@@ -435,7 +444,12 @@ func (ctrl *AcquireController) loadFromCmdline(logger *zap.Logger) (config.Provi
435444
return nil, fmt.Errorf("failed to validate config acquired via cmdline %s: %w", constants.KernelParamConfigInline, err)
436445
}
437446

438-
for _, warning := range warnings {
447+
warningsRuntime, err := cfg.RuntimeValidate(ctx, ctrl.ResourceState, ctrl.ValidationMode)
448+
if err != nil {
449+
return nil, fmt.Errorf("failed to validate config acquired via cmdline %s: %w", constants.KernelParamConfigInline, err)
450+
}
451+
452+
for _, warning := range slices.Concat(warnings, warningsRuntime) {
439453
logger.Warn("config validation warning", zap.String("cmdline", constants.KernelParamConfigInline), zap.String("warning", warning))
440454
}
441455

Diff for: internal/app/machined/pkg/controllers/config/acquire_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ func TestAcquireSuite(t *testing.T) {
199199
EventPublisher: s.eventPublisher,
200200
ValidationMode: validationModeMock{},
201201
ConfigPath: s.configPath,
202+
ResourceState: s.State(),
202203
}))
203204
}
204205

Diff for: internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go

+23-2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import (
7171
"github.com/siderolabs/talos/pkg/kubernetes"
7272
machineapi "github.com/siderolabs/talos/pkg/machinery/api/machine"
7373
"github.com/siderolabs/talos/pkg/machinery/config/machine"
74+
"github.com/siderolabs/talos/pkg/machinery/config/types/block/blockhelpers"
7475
"github.com/siderolabs/talos/pkg/machinery/constants"
7576
metamachinery "github.com/siderolabs/talos/pkg/machinery/meta"
7677
blockres "github.com/siderolabs/talos/pkg/machinery/resources/block"
@@ -2015,16 +2016,36 @@ func Install(runtime.Sequence, any) (runtime.TaskExecutionFunc, string) {
20152016

20162017
var disk string
20172018

2018-
disk, err = r.Config().Machine().Install().Disk()
2019+
matchExpr, err := r.Config().Machine().Install().DiskMatchExpression()
20192020
if err != nil {
2020-
return err
2021+
return fmt.Errorf("failed to get disk match expression: %w", err)
2022+
}
2023+
2024+
switch {
2025+
case matchExpr != nil:
2026+
logger.Printf("using disk match expression: %s", matchExpr)
2027+
2028+
matchedDisks, err := blockhelpers.MatchDisks(ctx, r.State().V1Alpha2().Resources(), matchExpr)
2029+
if err != nil {
2030+
return err
2031+
}
2032+
2033+
if len(matchedDisks) == 0 {
2034+
return fmt.Errorf("no disks matched the expression: %s", matchExpr)
2035+
}
2036+
2037+
disk = matchedDisks[0].TypedSpec().DevPath
2038+
case r.Config().Machine().Install().Disk() != "":
2039+
disk = r.Config().Machine().Install().Disk()
20212040
}
20222041

20232042
disk, err = filepath.EvalSymlinks(disk)
20242043
if err != nil {
20252044
return err
20262045
}
20272046

2047+
logger.Printf("installing Talos to disk %s", disk)
2048+
20282049
err = install.RunInstallerContainer(
20292050
disk,
20302051
r.State().Platform().Name(),

Diff for: internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
125125
ConfigSetter: ctrl.v1alpha1Runtime,
126126
EventPublisher: ctrl.v1alpha1Runtime.Events(),
127127
ValidationMode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
128+
ResourceState: ctrl.v1alpha1Runtime.State().V1Alpha2().Resources(),
128129
},
129130
&config.MachineTypeController{},
130131
&cri.SeccompProfileController{},

Diff for: internal/app/maintenance/server.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"fmt"
1111
"io/fs"
1212
"log"
13+
"slices"
1314
"strings"
1415

1516
cosiv1alpha1 "github.com/cosi-project/runtime/api/v1alpha1"
@@ -75,7 +76,7 @@ func (s *Server) Register(obj *grpc.Server) {
7576
}
7677

7778
// ApplyConfiguration implements [machine.MachineServiceServer].
78-
func (s *Server) ApplyConfiguration(_ context.Context, in *machine.ApplyConfigurationRequest) (*machine.ApplyConfigurationResponse, error) {
79+
func (s *Server) ApplyConfiguration(ctx context.Context, in *machine.ApplyConfigurationRequest) (*machine.ApplyConfigurationResponse, error) {
7980
if s.mode.IsAgent() {
8081
return nil, status.Error(codes.Unimplemented, "API is not implemented in agent mode")
8182
}
@@ -102,10 +103,15 @@ func (s *Server) ApplyConfiguration(_ context.Context, in *machine.ApplyConfigur
102103
return nil, status.Errorf(codes.InvalidArgument, "configuration validation failed: %s", err)
103104
}
104105

106+
warningsRuntime, err := cfgProvider.RuntimeValidate(ctx, s.controller.Runtime().State().V1Alpha2().Resources(), s.controller.Runtime().State().Platform().Mode())
107+
if err != nil {
108+
return nil, status.Errorf(codes.InvalidArgument, "runtime configuration validation failed: %s", err)
109+
}
110+
105111
reply := &machine.ApplyConfigurationResponse{
106112
Messages: []*machine.ApplyConfiguration{
107113
{
108-
Warnings: warnings,
114+
Warnings: slices.Concat(warnings, warningsRuntime),
109115
},
110116
},
111117
}

Diff for: internal/integration/api/generate-config.go

+2-8
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,6 @@ func (suite *GenerateConfigSuite) TestGenerate() {
9595

9696
suite.Require().NoError(err)
9797

98-
disk, err := config.Machine().Install().Disk()
99-
suite.Require().NoError(err)
100-
10198
suite.Require().EqualValues(request.MachineConfig.Type, config.Machine().Type())
10299
suite.Require().EqualValues(request.ClusterConfig.Name, config.Cluster().Name())
103100
suite.Require().EqualValues(request.ClusterConfig.ControlPlane.Endpoint, config.Cluster().Endpoint().String())
@@ -114,7 +111,7 @@ func (suite *GenerateConfigSuite) TestGenerate() {
114111
fmt.Sprintf("%s:v%s", constants.KubeletImage, request.MachineConfig.KubernetesVersion),
115112
config.Machine().Kubelet().Image(),
116113
)
117-
suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallDisk, disk)
114+
suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallDisk, config.Machine().Install().Disk())
118115
suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallImage, config.Machine().Install().Image())
119116
suite.Require().EqualValues(request.MachineConfig.NetworkConfig.Hostname, config.Machine().Network().Hostname())
120117
suite.Require().EqualValues(request.MachineConfig.NetworkConfig.Hostname, config.Machine().Network().Hostname())
@@ -149,9 +146,6 @@ func (suite *GenerateConfigSuite) TestGenerate() {
149146

150147
suite.Require().NoError(err)
151148

152-
disk, err = config.Machine().Install().Disk()
153-
suite.Require().NoError(err)
154-
155149
suite.Require().EqualValues(request.MachineConfig.Type, joinedConfig.Machine().Type())
156150
suite.Require().EqualValues(request.ClusterConfig.Name, joinedConfig.Cluster().Name())
157151
suite.Require().EqualValues(request.ClusterConfig.ControlPlane.Endpoint, joinedConfig.Cluster().Endpoint().String())
@@ -163,7 +157,7 @@ func (suite *GenerateConfigSuite) TestGenerate() {
163157
fmt.Sprintf("%s:v%s", constants.KubeletImage, request.MachineConfig.KubernetesVersion),
164158
joinedConfig.Machine().Kubelet().Image(),
165159
)
166-
suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallDisk, disk)
160+
suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallDisk, config.Machine().Install().Disk())
167161
suite.Require().EqualValues(
168162
request.MachineConfig.InstallConfig.InstallImage,
169163
joinedConfig.Machine().Install().Image(),

Diff for: pkg/machinery/cel/build.go

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
package cel
6+
7+
import (
8+
"fmt"
9+
10+
"github.com/google/cel-go/cel"
11+
"github.com/google/cel-go/common"
12+
"github.com/google/cel-go/common/ast"
13+
"github.com/google/cel-go/common/types"
14+
)
15+
16+
// Builder allows building CEL expressions programmatically.
17+
type Builder struct {
18+
ast.ExprFactory
19+
env *cel.Env
20+
nextID int64
21+
}
22+
23+
// NewBuilder creates a new builder.
24+
func NewBuilder(env *cel.Env) *Builder {
25+
return &Builder{
26+
ExprFactory: ast.NewExprFactory(),
27+
env: env,
28+
}
29+
}
30+
31+
// NextID returns the next unique ID.
32+
func (b *Builder) NextID() int64 {
33+
b.nextID++
34+
35+
return b.nextID
36+
}
37+
38+
// ToBooleanExpression converts the AST to a boolean expression.
39+
func (b *Builder) ToBooleanExpression(expr ast.Expr) (*Expression, error) {
40+
rawAst := ast.NewAST(expr, nil)
41+
42+
pbAst, err := ast.ToProto(rawAst)
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
celAst, err := cel.CheckedExprToAstWithSource(pbAst, common.NewTextSource(""))
48+
if err != nil {
49+
return nil, err
50+
}
51+
52+
var issues *cel.Issues
53+
54+
celAst, issues = b.env.Check(celAst)
55+
if issues != nil && issues.Err() != nil {
56+
return nil, issues.Err()
57+
}
58+
59+
if outputType := celAst.OutputType(); !outputType.IsExactType(types.BoolType) {
60+
return nil, fmt.Errorf("expression output type is %s, expected bool", outputType)
61+
}
62+
63+
return &Expression{
64+
ast: celAst,
65+
}, nil
66+
}

Diff for: pkg/machinery/cel/build_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
package cel_test
6+
7+
import (
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
13+
"github.com/siderolabs/talos/pkg/machinery/cel"
14+
"github.com/siderolabs/talos/pkg/machinery/cel/celenv"
15+
)
16+
17+
func TestBuildDiskExpression(t *testing.T) {
18+
t.Parallel()
19+
20+
builder := cel.NewBuilder(celenv.DiskLocator())
21+
22+
expr := builder.NewSelect(
23+
builder.NextID(),
24+
builder.NewIdent(builder.NextID(), "disk"),
25+
"rotational",
26+
)
27+
28+
out, err := builder.ToBooleanExpression(expr)
29+
require.NoError(t, err)
30+
31+
assert.Equal(t, "disk.rotational", out.String())
32+
}

0 commit comments

Comments
 (0)