Skip to content

Commit

Permalink
feat: rewrite install disk selector to use CEL expressions
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
smira committed Nov 11, 2024
1 parent eba35f4 commit 9a02ecc
Show file tree
Hide file tree
Showing 31 changed files with 603 additions and 194 deletions.
12 changes: 6 additions & 6 deletions hack/release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand Down Expand Up @@ -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]
Expand Down
19 changes: 13 additions & 6 deletions internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions internal/app/machined/pkg/controllers/block/volume_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down
22 changes: 18 additions & 4 deletions internal/app/machined/pkg/controllers/config/acquire.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"io"
"net/http"
"os"
"slices"
"strings"

"github.com/cosi-project/runtime/pkg/controller"
Expand Down Expand Up @@ -68,6 +69,7 @@ type AcquireController struct {
EventPublisher talosruntime.Publisher
ValidationMode validation.RuntimeMode
ConfigPath string
ResourceState state.State

configSourcesUsed []string
}
Expand Down Expand Up @@ -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))
}

Expand All @@ -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
}
Expand All @@ -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)
Expand Down Expand Up @@ -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))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ func TestAcquireSuite(t *testing.T) {
EventPublisher: s.eventPublisher,
ValidationMode: validationModeMock{},
ConfigPath: s.configPath,
ResourceState: s.State(),
}))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -2015,16 +2016,36 @@ 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)
if err != nil {
return err
}

logger.Printf("installing Talos to disk %s", disk)

err = install.RunInstallerContainer(
disk,
r.State().Platform().Name(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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{},
Expand Down
10 changes: 8 additions & 2 deletions internal/app/maintenance/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"io/fs"
"log"
"slices"
"strings"

cosiv1alpha1 "github.com/cosi-project/runtime/api/v1alpha1"
Expand Down Expand Up @@ -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")
}
Expand All @@ -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),
},
},
}
Expand Down
10 changes: 2 additions & 8 deletions internal/integration/api/generate-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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())
Expand Down Expand Up @@ -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())
Expand All @@ -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(),
Expand Down
66 changes: 66 additions & 0 deletions pkg/machinery/cel/build.go
Original file line number Diff line number Diff line change
@@ -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 changes: 32 additions & 0 deletions pkg/machinery/cel/build_test.go
Original file line number Diff line number Diff line change
@@ -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())
}
Loading

0 comments on commit 9a02ecc

Please sign in to comment.