Skip to content

Commit

Permalink
feat: add dry-run flag in apply-config and edit commands
Browse files Browse the repository at this point in the history
Dry run prints out config diff, selected application mode without
changing the configuration.

Signed-off-by: Artem Chernyshev <[email protected]>
  • Loading branch information
Unix4ever committed Apr 14, 2022
1 parent 8af50fc commit 2b9722d
Show file tree
Hide file tree
Showing 15 changed files with 1,375 additions and 1,189 deletions.
1 change: 1 addition & 0 deletions api/machine/machine.proto
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ message ApplyConfigurationRequest {
deprecated = true
];
Mode mode = 4;
bool dry_run = 5;
}

// ApplyConfigurationResponse describes the response to a configuration request.
Expand Down
6 changes: 5 additions & 1 deletion cmd/talosctl/cmd/talos/apply-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var applyConfigCmdFlags struct {
certFingerprints []string
filename string
insecure bool
dryRun bool
}

// applyConfigCmd represents the applyConfiguration command.
Expand Down Expand Up @@ -79,7 +80,7 @@ var applyConfigCmd = &cobra.Command{
if len(Endpoints) > 0 {
return WithClientNoNodes(func(bootstrapCtx context.Context, bootstrapClient *client.Client) error {
opts := []installer.Option{}
opts = append(opts, installer.WithBootstrapNode(bootstrapCtx, bootstrapClient, Endpoints[0]))
opts = append(opts, installer.WithBootstrapNode(bootstrapCtx, bootstrapClient, Endpoints[0]), installer.WithDryRun(applyConfigCmdFlags.dryRun))

conn, err := installer.NewConnection(
ctx,
Expand All @@ -99,6 +100,7 @@ var applyConfigCmd = &cobra.Command{
ctx,
c,
node,
installer.WithDryRun(applyConfigCmdFlags.dryRun),
)
if err != nil {
return err
Expand All @@ -112,6 +114,7 @@ var applyConfigCmd = &cobra.Command{
Mode: applyConfigCmdFlags.Mode.Mode,
OnReboot: applyConfigCmdFlags.OnReboot,
Immediate: applyConfigCmdFlags.Immediate,
DryRun: applyConfigCmdFlags.dryRun,
})
if err != nil {
return fmt.Errorf("error applying new configuration: %s", err)
Expand All @@ -127,6 +130,7 @@ var applyConfigCmd = &cobra.Command{
func init() {
applyConfigCmd.Flags().StringVarP(&applyConfigCmdFlags.filename, "file", "f", "", "the filename of the updated configuration")
applyConfigCmd.Flags().BoolVarP(&applyConfigCmdFlags.insecure, "insecure", "i", false, "apply the config using the insecure (encrypted with no auth) maintenance service")
applyConfigCmd.Flags().BoolVar(&applyConfigCmdFlags.dryRun, "dry-run", false, "check how the config change will be applied in dry-run mode")
applyConfigCmd.Flags().StringSliceVar(&applyConfigCmdFlags.certFingerprints, "cert-fingerprint", nil, "list of server certificate fingeprints to accept (defaults to no check)")
helpers.AddModeFlags(&applyConfigCmdFlags.Mode, applyConfigCmd)
addCommand(applyConfigCmd)
Expand Down
3 changes: 3 additions & 0 deletions cmd/talosctl/cmd/talos/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
var editCmdFlags struct {
helpers.Mode
namespace string
dryRun bool
}

//nolint:gocyclo
Expand Down Expand Up @@ -118,6 +119,7 @@ func editFn(c *client.Client) func(context.Context, client.ResourceResponse) err
Mode: editCmdFlags.Mode.Mode,
OnReboot: editCmdFlags.OnReboot,
Immediate: editCmdFlags.Immediate,
DryRun: editCmdFlags.dryRun,
})
if err != nil {
lastError = err.Error()
Expand Down Expand Up @@ -177,5 +179,6 @@ or 'notepad' for Windows.`,
func init() {
editCmd.Flags().StringVar(&editCmdFlags.namespace, "namespace", "", "resource namespace (default is to use default namespace per resource)")
helpers.AddModeFlags(&editCmdFlags.Mode, editCmd)
editCmd.Flags().BoolVar(&editCmdFlags.dryRun, "dry-run", false, "do not apply the change after editing and print the change summary instead")
addCommand(editCmd)
}
3 changes: 3 additions & 0 deletions cmd/talosctl/cmd/talos/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var patchCmdFlags struct {
namespace string
patch []string
patchFile string
dryRun bool
}

func patchFn(c *client.Client, patch jsonpatch.Patch) func(context.Context, client.ResourceResponse) error {
Expand Down Expand Up @@ -54,6 +55,7 @@ func patchFn(c *client.Client, patch jsonpatch.Patch) func(context.Context, clie
Mode: patchCmdFlags.Mode.Mode,
OnReboot: patchCmdFlags.OnReboot,
Immediate: patchCmdFlags.Immediate,
DryRun: patchCmdFlags.dryRun,
})

if bytes.Equal(
Expand Down Expand Up @@ -113,6 +115,7 @@ func init() {
patchCmd.Flags().StringVar(&patchCmdFlags.namespace, "namespace", "", "resource namespace (default is to use default namespace per resource)")
patchCmd.Flags().StringVar(&patchCmdFlags.patchFile, "patch-file", "", "a file containing a patch to be applied to the resource.")
patchCmd.Flags().StringArrayVarP(&patchCmdFlags.patch, "patch", "p", nil, "the patch to be applied to the resource file, use @file to read a patch from file.")
patchCmd.Flags().BoolVar(&patchCmdFlags.dryRun, "dry-run", false, "print the change summary and patch preview without applying the changes")
helpers.AddModeFlags(&patchCmdFlags.Mode, patchCmd)
addCommand(patchCmd)
}
7 changes: 7 additions & 0 deletions hack/release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ The policy is part of the Talos machine configuration, and it can be modified to
description="""\
Talos is built for x86-64 architecture with support for [x86-64-v2 microarchitecture level](https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels),
so Talos no longer runs on processors supporting only baseline `x86-64` microarchitecture (before 2009).
"""

[notes.apply-config]
title = "Apply Config `--dry-run`"
description="""\
The commands `talosctl apply-config`, `talosctl patch mc` and `talosctl edit mc` now support `--dry-run` flag.
If enabled it just prints out the selected config application mode and the configuration diff.
"""

[make_deps]
Expand Down
43 changes: 38 additions & 5 deletions internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/oci"
criconstants "github.com/containerd/containerd/pkg/cri/constants"
"github.com/google/go-cmp/cmp"
multierror "github.com/hashicorp/go-multierror"
"github.com/prometheus/procfs"
"github.com/rs/xid"
Expand Down Expand Up @@ -73,6 +74,7 @@ import (
timeapi "github.com/talos-systems/talos/pkg/machinery/api/time"
clientconfig "github.com/talos-systems/talos/pkg/machinery/client/config"
"github.com/talos-systems/talos/pkg/machinery/config"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/generate"
machinetype "github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/machine"
"github.com/talos-systems/talos/pkg/machinery/constants"
Expand Down Expand Up @@ -136,10 +138,11 @@ func (s *Server) Register(obj *grpc.Server) {

// ApplyConfiguration implements machine.MachineService.
//
//nolint:gocyclo
//nolint:gocyclo,cyclop
func (s *Server) ApplyConfiguration(ctx context.Context, in *machine.ApplyConfigurationRequest) (*machine.ApplyConfigurationResponse, error) {
mode := in.Mode.String()
modeDetails := ""
modeDetails := "Applied configuration with a reboot"
modeErr := ""

// TODO: remove in v1.1
switch {
Expand All @@ -161,19 +164,49 @@ func (s *Server) ApplyConfiguration(ctx context.Context, in *machine.ApplyConfig
if err = s.Controller.Runtime().CanApplyImmediate(cfgProvider); err != nil {
return nil, status.Errorf(codes.InvalidArgument, err.Error())
}

modeDetails = "Applied configuration without a reboot"
// --mode=staged
case machine.ApplyConfigurationRequest_STAGED:
modeDetails = "Staged configuration to be applied after the next reboot"
// --mode=auto detect actual update mode
case machine.ApplyConfigurationRequest_AUTO:
if err = s.Controller.Runtime().CanApplyImmediate(cfgProvider); err != nil {
in.Mode = machine.ApplyConfigurationRequest_REBOOT
modeDetails = fmt.Sprintf("applied configuration with a reboot: %s", err)
modeDetails = "Applied configuration with a reboot"
modeErr = ": " + err.Error()
} else {
in.Mode = machine.ApplyConfigurationRequest_NO_REBOOT
modeDetails = "applied configuration without a reboot"
modeDetails = "Applied configuration without a reboot"
}

mode = fmt.Sprintf("%s(%s)", mode, in.Mode)
}

if in.DryRun {
var config interface{}
if s.Controller.Runtime().Config() != nil {
config = s.Controller.Runtime().Config().Raw()
}

diff := cmp.Diff(config, cfgProvider.Raw(), cmp.AllowUnexported(v1alpha1.InstallDiskSizeMatcher{}))
if diff == "" {
diff = "No changes."
}

return &machine.ApplyConfigurationResponse{
Messages: []*machine.ApplyConfiguration{
{
Mode: in.Mode,
ModeDetails: fmt.Sprintf(`Dry run summary:
%s (skipped in dry-run).
Config diff:
%s`, modeDetails, diff),
},
},
}, nil
}

log.Printf("apply config request: mode %s", strings.ToLower(mode))

cfg, err := cfgProvider.Bytes()
Expand Down Expand Up @@ -215,7 +248,7 @@ func (s *Server) ApplyConfiguration(ctx context.Context, in *machine.ApplyConfig
Messages: []*machine.ApplyConfiguration{
{
Mode: in.Mode,
ModeDetails: modeDetails,
ModeDetails: modeDetails + modeErr,
},
},
}, nil
Expand Down
7 changes: 7 additions & 0 deletions internal/app/maintenance/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ func (s *Server) ApplyConfiguration(ctx context.Context, in *machine.ApplyConfig
},
}

if in.DryRun {
reply.Messages[0].ModeDetails = `Dry run summary:
Node is running in maintenance mode and does not have a config yet.`

return reply, nil
}

s.cfgCh <- in.GetData()

return reply, nil
Expand Down
49 changes: 43 additions & 6 deletions internal/integration/api/apply-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,6 @@ func (suite *ApplyConfigSuite) SuiteName() string {

// SetupTest ...
func (suite *ApplyConfigSuite) SetupTest() {
if testing.Short() {
suite.T().Skip("skipping in short mode")
}

// make sure we abort at some point in time, but give enough room for Recovers
suite.ctx, suite.ctxCancel = context.WithTimeout(context.Background(), 30*time.Minute)
}
Expand All @@ -79,6 +75,10 @@ func (suite *ApplyConfigSuite) TearDownTest() {

// TestApply verifies the apply config API.
func (suite *ApplyConfigSuite) TestApply() {
if testing.Short() {
suite.T().Skip("skipping in short mode")
}

if !suite.Capabilities().SupportsReboot {
suite.T().Skip("cluster doesn't support reboot")
}
Expand Down Expand Up @@ -119,7 +119,7 @@ func (suite *ApplyConfigSuite) TestApply() {
)
if err != nil {
// It is expected that the connection will EOF here, so just log the error
suite.Assert().Nilf("failed to apply configuration (node %q): %w", node, err)
suite.Assert().Nilf(err, "failed to apply configuration (node %q): %w", node, err)
}

return nil
Expand Down Expand Up @@ -317,7 +317,7 @@ func (suite *ApplyConfigSuite) TestApplyConfigRotateEncryptionSecrets() {
)
if err != nil {
// It is expected that the connection will EOF here, so just log the error
suite.Assert().Nilf("failed to apply configuration (node %q): %w", node, err)
suite.Assert().Nilf(err, "failed to apply configuration (node %q): %w", node, err)
}

return nil
Expand Down Expand Up @@ -408,6 +408,43 @@ func (suite *ApplyConfigSuite) TestApplyNoReboot() {
suite.Require().Equal(codes.InvalidArgument, status.Code(nodeError.Err))
}

// TestApplyDryRun verifies the apply config API with dry run enabled.
func (suite *ApplyConfigSuite) TestApplyDryRun() {
nodes := suite.DiscoverNodes(suite.ctx).NodesByType(machine.TypeWorker)
suite.Require().NotEmpty(nodes)

suite.WaitForBootDone(suite.ctx)

sort.Strings(nodes)

node := nodes[0]

nodeCtx := client.WithNodes(suite.ctx, node)

provider, err := suite.ReadConfigFromNode(nodeCtx)
suite.Assert().Nilf(err, "failed to read existing config from node %q: %w", node, err)

cfg, ok := provider.Raw().(*v1alpha1.Config)
suite.Require().True(ok)

// this won't be possible without a reboot
cfg.MachineConfig.MachineType = "controlplane"

cfgDataOut, err := cfg.Bytes()
suite.Assert().Nilf(err, "failed to marshal updated machine config data (node %q): %w", node, err)

reply, err := suite.Client.ApplyConfiguration(
nodeCtx, &machineapi.ApplyConfigurationRequest{
Data: cfgDataOut,
Mode: machineapi.ApplyConfigurationRequest_AUTO,
DryRun: true,
},
)

suite.Assert().Nilf(err, "failed to apply configuration (node %q): %w", node, err)
suite.Require().Contains(reply.Messages[0].ModeDetails, "Dry run summary")
}

func init() {
allSuites = append(allSuites, new(ApplyConfigSuite))
}
1 change: 1 addition & 0 deletions internal/integration/cli/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func (suite *PatchSuite) TestSuccess() {
suite.Require().NoError(err)

suite.RunCLI([]string{"patch", "--nodes", node, "--patch", string(data), "machineconfig", "--mode=no-reboot"})
suite.RunCLI([]string{"patch", "--nodes", node, "--patch", string(data), "machineconfig", "--mode=no-reboot", "--dry-run"})
}

// TestError runs comand with error.
Expand Down
10 changes: 10 additions & 0 deletions internal/pkg/tui/installer/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Connection struct {
bootstrapClient *client.Client
nodeCtx context.Context //nolint:containedctx
bootstrapCtx context.Context //nolint:containedctx
dryRun bool
}

// NewConnection creates new installer connection.
Expand Down Expand Up @@ -172,3 +173,12 @@ func WithBootstrapNode(ctx context.Context, bootstrapClient *client.Client, boot
return nil
}
}

// WithDryRun enables dry run mode in the installer.
func WithDryRun(dryRun bool) Option {
return func(c *Connection) error {
c.dryRun = dryRun

return nil
}
}
38 changes: 36 additions & 2 deletions internal/pkg/tui/installer/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,16 +370,50 @@ func (installer *Installer) apply(conn *Connection) error {
)
s.SetBackgroundColor(color)

var reply *machineapi.ApplyConfigurationResponse

// TODO: progress bar, logs?
list.AddItem(s, 1, 1, false)
_, err = conn.ApplyConfiguration(
reply, err = conn.ApplyConfiguration(
&machineapi.ApplyConfigurationRequest{
Data: config,
Data: config,
DryRun: conn.dryRun,
},
)

if conn.dryRun {
err = fmt.Errorf("skipped in dry run")
}

s.Stop(err == nil)

if conn.dryRun {
text := tview.NewTextView()
addLines := func(lines ...string) {
t := text.GetText(false)
t += strings.Join(lines, "\n")
text.SetText(t)
installer.app.Draw()
}

for _, m := range reply.Messages {
addLines("", m.ModeDetails)
}

addLines(
"",
"Press any key to exit.",
)

text.SetBackgroundColor(color)
list.AddItem(text, 0, 1, false)
installer.app.Draw()

installer.awaitKey()

return nil
}

if err != nil {
return err
}
Expand Down
Loading

0 comments on commit 2b9722d

Please sign in to comment.