Skip to content

Commit 2d054ad

Browse files
committed
chore: handle documents diff in apply-config dry run
Before this PR diff generator only diffed the v1alpha1 config and nothing else. With this PR it also takes separate docs into the account. ```shell ~ > <editor> controlplane.yaml ~ > talosctl -n talos-default-controlplane-1 apply-config --file controlplane.yaml --dry-run Dry run summary: Applied configuration without a reboot (skipped in dry-run). Config diff: No changes. Documents diff: []config.Document{ + &runtime.KmsgLogV1Alpha1{ + Meta: meta.Meta{MetaAPIVersion: "v1alpha1", MetaKind: "KmsgLogConfig"}, + MetaName: "omni-kmsg", + KmsgLogURL: s"tcp://[fdae:41e4:649b:9303::1]:8092", + }, } ~ > talosctl -n talos-default-controlplane-1 apply-config --file controlplane.yaml Applied configuration without a reboot ~ > ~ > ~ > ~ > <editor> controlplane.yaml ~ > talosctl -n talos-default-controlplane-1 apply-config --file controlplane.yaml --dry-run Dry run summary: Applied configuration without a reboot (skipped in dry-run). Config diff: No changes. Documents diff: []config.Document{ &runtime.KmsgLogV1Alpha1{Meta: {MetaAPIVersion: "v1alpha1", MetaKind: "KmsgLogConfig"}, MetaName: "omni-kmsg", KmsgLogURL: {URL: &{Scheme: "tcp", Host: "[fdae:41e4:649b:9303::1]:8092"}}}, + &network.DefaultActionConfigV1Alpha1{ + Meta: meta.Meta{MetaAPIVersion: "v1alpha1", MetaKind: "NetworkDefaultActionConfig"}, + Ingress: s"block", + }, } ``` Closes #8885 Signed-off-by: Dmitriy Matrenichev <dmitry.matrenichev@siderolabs.com>
1 parent bd34f71 commit 2d054ad

File tree

3 files changed

+111
-47
lines changed

3 files changed

+111
-47
lines changed

cmd/talosctl/cmd/talos/apply-config.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ var applyConfigCmd = &cobra.Command{
4343
RunE: func(cmd *cobra.Command, args []string) error {
4444
var (
4545
cfgBytes []byte
46-
e error
46+
err error
4747
)
4848

4949
if len(args) > 0 {
@@ -59,9 +59,9 @@ var applyConfigCmd = &cobra.Command{
5959
}
6060

6161
if applyConfigCmdFlags.filename != "" {
62-
cfgBytes, e = os.ReadFile(applyConfigCmdFlags.filename)
63-
if e != nil {
64-
return fmt.Errorf("failed to read configuration from %q: %w", applyConfigCmdFlags.filename, e)
62+
cfgBytes, err = os.ReadFile(applyConfigCmdFlags.filename)
63+
if err != nil {
64+
return fmt.Errorf("failed to read configuration from %q: %w", applyConfigCmdFlags.filename, err)
6565
}
6666

6767
if len(cfgBytes) < 1 {
@@ -74,19 +74,19 @@ var applyConfigCmd = &cobra.Command{
7474
patches []configpatcher.Patch
7575
)
7676

77-
patches, e = configpatcher.LoadPatches(applyConfigCmdFlags.patches)
78-
if e != nil {
79-
return e
77+
patches, err = configpatcher.LoadPatches(applyConfigCmdFlags.patches)
78+
if err != nil {
79+
return err
8080
}
8181

82-
cfg, e = configpatcher.Apply(configpatcher.WithBytes(cfgBytes), patches)
83-
if e != nil {
84-
return e
82+
cfg, err = configpatcher.Apply(configpatcher.WithBytes(cfgBytes), patches)
83+
if err != nil {
84+
return err
8585
}
8686

87-
cfgBytes, e = cfg.Bytes()
88-
if e != nil {
89-
return e
87+
cfgBytes, err = cfg.Bytes()
88+
if err != nil {
89+
return err
9090
}
9191
}
9292
} else if applyConfigCmdFlags.Mode.Mode != helpers.InteractiveMode {
@@ -108,8 +108,10 @@ var applyConfigCmd = &cobra.Command{
108108

109109
if len(GlobalArgs.Endpoints) > 0 {
110110
return WithClientNoNodes(func(bootstrapCtx context.Context, bootstrapClient *client.Client) error {
111-
opts := []installer.Option{}
112-
opts = append(opts, installer.WithBootstrapNode(bootstrapCtx, bootstrapClient, GlobalArgs.Endpoints[0]), installer.WithDryRun(applyConfigCmdFlags.dryRun))
111+
opts := []installer.Option{
112+
installer.WithBootstrapNode(bootstrapCtx, bootstrapClient, GlobalArgs.Endpoints[0]),
113+
installer.WithDryRun(applyConfigCmdFlags.dryRun),
114+
}
113115

114116
conn, err := installer.NewConnection(
115117
ctx,

internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"archive/tar"
99
"bufio"
1010
"bytes"
11+
stdcmp "cmp"
1112
"compress/gzip"
1213
"context"
1314
"encoding/json"
@@ -76,6 +77,8 @@ import (
7677
"github.com/siderolabs/talos/pkg/machinery/api/storage"
7778
timeapi "github.com/siderolabs/talos/pkg/machinery/api/time"
7879
clientconfig "github.com/siderolabs/talos/pkg/machinery/client/config"
80+
"github.com/siderolabs/talos/pkg/machinery/config"
81+
docscfg "github.com/siderolabs/talos/pkg/machinery/config/config"
7982
"github.com/siderolabs/talos/pkg/machinery/config/configloader"
8083
"github.com/siderolabs/talos/pkg/machinery/config/generate/secrets"
8184
machinetype "github.com/siderolabs/talos/pkg/machinery/config/machine"
@@ -218,24 +221,15 @@ func (s *Server) ApplyConfiguration(ctx context.Context, in *machine.ApplyConfig
218221
}
219222

220223
if in.DryRun {
221-
var config interface{}
222-
if s.Controller.Runtime().Config() != nil {
223-
config = s.Controller.Runtime().ConfigContainer().RawV1Alpha1()
224-
}
225-
226-
diff := cmp.Diff(config, cfgProvider.RawV1Alpha1(), cmp.AllowUnexported(v1alpha1.InstallDiskSizeMatcher{}))
227-
if diff == "" {
228-
diff = "No changes."
229-
}
224+
details := generateDiff(s.Controller.Runtime(), cfgProvider)
230225

231226
return &machine.ApplyConfigurationResponse{
232227
Messages: []*machine.ApplyConfiguration{
233228
{
234229
Mode: in.Mode,
235230
ModeDetails: fmt.Sprintf(`Dry run summary:
236231
%s (skipped in dry-run).
237-
Config diff:
238-
%s`, modeDetails, diff),
232+
%s`, modeDetails, details),
239233
},
240234
},
241235
}, nil
@@ -301,6 +295,35 @@ Config diff:
301295
}, nil
302296
}
303297

298+
func generateDiff(r runtime.Runtime, provider config.Provider) string {
299+
var cfg *v1alpha1.Config
300+
301+
if r.Config() != nil {
302+
cfg = r.ConfigContainer().RawV1Alpha1()
303+
}
304+
305+
v1alpha1Diff := cmp.Diff(cfg, provider.RawV1Alpha1(), cmp.AllowUnexported(v1alpha1.InstallDiskSizeMatcher{}))
306+
if v1alpha1Diff == "" {
307+
v1alpha1Diff = "No changes."
308+
}
309+
310+
origDocs := slices.DeleteFunc(r.ConfigContainer().Documents(), func(doc docscfg.Document) bool { return doc.Kind() == v1alpha1.Version })
311+
newDocs := slices.DeleteFunc(provider.Documents(), func(doc docscfg.Document) bool { return doc.Kind() == v1alpha1.Version })
312+
313+
slices.SortStableFunc(origDocs, func(a, b docscfg.Document) int { return stdcmp.Compare(a.Kind(), b.Kind()) })
314+
slices.SortStableFunc(newDocs, func(a, b docscfg.Document) int { return stdcmp.Compare(a.Kind(), b.Kind()) })
315+
316+
documentsDiff := cmp.Diff(origDocs, newDocs)
317+
if documentsDiff == "" {
318+
documentsDiff = "No changes."
319+
}
320+
321+
return fmt.Sprintf(`Config diff:
322+
%s
323+
Documents diff:
324+
%s`, v1alpha1Diff, documentsDiff)
325+
}
326+
304327
// GenerateConfiguration implements the machine.MachineServer interface.
305328
func (s *Server) GenerateConfiguration(ctx context.Context, in *machine.GenerateConfigurationRequest) (reply *machine.GenerateConfigurationResponse, err error) {
306329
if s.Controller.Runtime().Config().Machine().Type() == machinetype.TypeWorker {

internal/integration/api/apply-config.go

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ package api
88

99
import (
1010
"context"
11+
"net/url"
1112
"os"
12-
"sort"
13+
"slices"
1314
"testing"
1415
"time"
1516

1617
"github.com/cosi-project/runtime/pkg/safe"
18+
"github.com/siderolabs/gen/ensure"
1719
"github.com/siderolabs/go-pointer"
1820
"github.com/siderolabs/go-retry/retry"
1921
"google.golang.org/grpc/codes"
@@ -23,7 +25,9 @@ import (
2325
machineapi "github.com/siderolabs/talos/pkg/machinery/api/machine"
2426
"github.com/siderolabs/talos/pkg/machinery/client"
2527
"github.com/siderolabs/talos/pkg/machinery/config"
28+
"github.com/siderolabs/talos/pkg/machinery/config/container"
2629
"github.com/siderolabs/talos/pkg/machinery/config/machine"
30+
"github.com/siderolabs/talos/pkg/machinery/config/types/runtime"
2731
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
2832
"github.com/siderolabs/talos/pkg/machinery/constants"
2933
mc "github.com/siderolabs/talos/pkg/machinery/resources/config"
@@ -88,14 +92,13 @@ func (suite *ApplyConfigSuite) TestApply() {
8892

8993
suite.WaitForBootDone(suite.ctx)
9094

91-
sort.Strings(nodes)
95+
slices.Sort(nodes)
9296

9397
node := nodes[0]
94-
9598
nodeCtx := client.WithNode(suite.ctx, node)
9699

97100
provider, err := suite.ReadConfigFromNode(nodeCtx)
98-
suite.Assert().Nilf(err, "failed to read existing config from node %q: %w", node, err)
101+
suite.Assert().NoErrorf(err, "failed to read existing config from node %q: %w", node, err)
99102

100103
cfgDataOut := suite.PatchV1Alpha1Config(provider, func(cfg *v1alpha1.Config) {
101104
if cfg.MachineConfig.MachineSysctls == nil {
@@ -115,7 +118,7 @@ func (suite *ApplyConfigSuite) TestApply() {
115118
)
116119
if err != nil {
117120
// It is expected that the connection will EOF here, so just log the error
118-
suite.Assert().Nilf(err, "failed to apply configuration (node %q): %w", node, err)
121+
suite.Assert().NoErrorf(err, "failed to apply configuration (node %q): %w", node, err)
119122
}
120123

121124
return nil
@@ -125,7 +128,7 @@ func (suite *ApplyConfigSuite) TestApply() {
125128
// Verify configuration change
126129
var newProvider config.Provider
127130

128-
suite.Require().Nilf(
131+
suite.Require().NoErrorf(
129132
retry.Constant(time.Minute, retry.WithUnits(time.Second)).Retry(
130133
func() error {
131134
newProvider, err = suite.ReadConfigFromNode(nodeCtx)
@@ -300,7 +303,7 @@ func (suite *ApplyConfigSuite) TestApplyConfigRotateEncryptionSecrets() {
300303
)
301304
if err != nil {
302305
// It is expected that the connection will EOF here, so just log the error
303-
suite.Assert().Nilf(err, "failed to apply configuration (node %q): %w", node, err)
306+
suite.Assert().Errorf(err, "failed to apply configuration (node %q): %w", node, err)
304307
}
305308

306309
return nil
@@ -312,7 +315,7 @@ func (suite *ApplyConfigSuite) TestApplyConfigRotateEncryptionSecrets() {
312315
// Verify configuration change
313316
var newProvider config.Provider
314317

315-
suite.Require().Nilf(
318+
suite.Require().Errorf(
316319
retry.Constant(time.Minute, retry.WithUnits(time.Second)).Retry(
317320
func() error {
318321
newProvider, err = suite.ReadConfigFromNode(nodeCtx)
@@ -355,14 +358,13 @@ func (suite *ApplyConfigSuite) TestApplyNoReboot() {
355358

356359
suite.WaitForBootDone(suite.ctx)
357360

358-
sort.Strings(nodes)
361+
slices.Sort(nodes)
359362

360363
node := nodes[0]
361-
362364
nodeCtx := client.WithNode(suite.ctx, node)
363365

364366
provider, err := suite.ReadConfigFromNode(nodeCtx)
365-
suite.Require().Nilf(err, "failed to read existing config from node %q: %s", node, err)
367+
suite.Require().NoErrorf(err, "failed to read existing config from node %q: %s", node, err)
366368

367369
cfgDataOut := suite.PatchV1Alpha1Config(provider, func(cfg *v1alpha1.Config) {
368370
// this won't be possible without a reboot
@@ -387,14 +389,13 @@ func (suite *ApplyConfigSuite) TestApplyDryRun() {
387389

388390
suite.WaitForBootDone(suite.ctx)
389391

390-
sort.Strings(nodes)
392+
slices.Sort(nodes)
391393

392394
node := nodes[0]
393-
394395
nodeCtx := client.WithNode(suite.ctx, node)
395396

396397
provider, err := suite.ReadConfigFromNode(nodeCtx)
397-
suite.Require().Nilf(err, "failed to read existing config from node %q: %s", node, err)
398+
suite.Require().NoErrorf(err, "failed to read existing config from node %q: %s", node, err)
398399

399400
cfgDataOut := suite.PatchV1Alpha1Config(provider, func(cfg *v1alpha1.Config) {
400401
// this won't be possible without a reboot
@@ -416,21 +417,59 @@ func (suite *ApplyConfigSuite) TestApplyDryRun() {
416417
},
417418
)
418419

419-
suite.Require().Nilf(err, "failed to apply configuration (node %q): %s", node, err)
420+
suite.Require().NoErrorf(err, "failed to apply configuration (node %q): %s", node, err)
420421
suite.Assert().Contains(reply.Messages[0].ModeDetails, "Dry run summary")
421422
}
422423

424+
// TestApplyDryRunDocuments verifies the apply config API with multi doc and dry run enabled.
425+
func (suite *ApplyConfigSuite) TestApplyDryRunDocuments() {
426+
nodes := suite.DiscoverNodeInternalIPsByType(suite.ctx, machine.TypeWorker)
427+
suite.Require().NotEmpty(nodes)
428+
429+
suite.WaitForBootDone(suite.ctx)
430+
431+
slices.Sort(nodes)
432+
433+
node := nodes[0]
434+
nodeCtx := client.WithNode(suite.ctx, node)
435+
436+
provider, err := suite.ReadConfigFromNode(nodeCtx)
437+
suite.Require().NoErrorf(err, "failed to read existing config from node %q: %s", node, err)
438+
439+
kmsg := runtime.NewKmsgLogV1Alpha1()
440+
kmsg.MetaName = "omni-kmsg"
441+
kmsg.KmsgLogURL.URL = ensure.Value(url.Parse("tcp://[fdae:41e4:649b:9303::1]:8092"))
442+
443+
cont, err := container.New(provider.RawV1Alpha1(), kmsg)
444+
suite.Require().NoErrorf(err, "failed to create container: %s", err)
445+
446+
cfgDataOut, err := cont.Bytes()
447+
suite.Require().NoErrorf(err, "failed to marshal container: %s", err)
448+
449+
reply, err := suite.Client.ApplyConfiguration(
450+
nodeCtx, &machineapi.ApplyConfigurationRequest{
451+
Data: cfgDataOut,
452+
Mode: machineapi.ApplyConfigurationRequest_AUTO,
453+
DryRun: true,
454+
},
455+
)
456+
457+
suite.Require().NoErrorf(err, "failed to apply configuration (node %q): %s", node, err)
458+
suite.Assert().Contains(reply.Messages[0].ModeDetails, "Dry run summary")
459+
suite.Assert().Contains(reply.Messages[0].ModeDetails, "omni-kmsg")
460+
suite.Assert().Contains(reply.Messages[0].ModeDetails, "tcp://[fdae:41e4:649b:9303::1]:8092")
461+
}
462+
423463
// TestApplyTry applies the config in try mode with a short timeout.
424464
func (suite *ApplyConfigSuite) TestApplyTry() {
425465
nodes := suite.DiscoverNodeInternalIPsByType(suite.ctx, machine.TypeWorker)
426466
suite.Require().NotEmpty(nodes)
427467

428468
suite.WaitForBootDone(suite.ctx)
429469

430-
sort.Strings(nodes)
470+
slices.Sort(nodes)
431471

432472
node := nodes[0]
433-
434473
nodeCtx := client.WithNode(suite.ctx, node)
435474

436475
getMachineConfig := func(ctx context.Context) (*mc.MachineConfig, error) {
@@ -443,7 +482,7 @@ func (suite *ApplyConfigSuite) TestApplyTry() {
443482
}
444483

445484
provider, err := getMachineConfig(nodeCtx)
446-
suite.Require().Nilf(err, "failed to read existing config from node %q: %s", node, err)
485+
suite.Require().NoErrorf(err, "failed to read existing config from node %q: %s", node, err)
447486

448487
cfgDataOut := suite.PatchV1Alpha1Config(provider.Provider(), func(cfg *v1alpha1.Config) {
449488
if cfg.MachineConfig.MachineNetwork == nil {
@@ -465,10 +504,10 @@ func (suite *ApplyConfigSuite) TestApplyTry() {
465504
TryModeTimeout: durationpb.New(time.Second * 1),
466505
},
467506
)
468-
suite.Assert().Nilf(err, "failed to apply configuration (node %q): %s", node, err)
507+
suite.Assert().NoErrorf(err, "failed to apply configuration (node %q): %s", node, err)
469508

470509
provider, err = getMachineConfig(nodeCtx)
471-
suite.Require().Nilf(err, "failed to read existing config from node %q: %w", node, err)
510+
suite.Require().NoErrorf(err, "failed to read existing config from node %q: %w", node, err)
472511

473512
suite.Assert().NotNil(provider.Config().Machine().Network())
474513
suite.Assert().NotNil(provider.Config().Machine().Network().Devices())
@@ -487,7 +526,7 @@ func (suite *ApplyConfigSuite) TestApplyTry() {
487526

488527
for range 100 {
489528
provider, err = getMachineConfig(nodeCtx)
490-
suite.Assert().Nilf(err, "failed to read existing config from node %q: %s", node, err)
529+
suite.Assert().NoErrorf(err, "failed to read existing config from node %q: %s", node, err)
491530

492531
if provider.Config().Machine().Network() == nil {
493532
return

0 commit comments

Comments
 (0)