diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/diff/diff.go b/staging/src/k8s.io/kubectl/pkg/cmd/diff/diff.go index fb43504227c..82cf5133f4f 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/diff/diff.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/diff/diff.go @@ -103,9 +103,10 @@ func diffError(err error) exec.ExitError { type DiffOptions struct { FilenameOptions resource.FilenameOptions - ServerSideApply bool - FieldManager string - ForceConflicts bool + ServerSideApply bool + FieldManager string + ForceConflicts bool + ShowManagedFields bool Selector string OpenAPISchema openapi.Resources @@ -164,6 +165,7 @@ func NewCmdDiff(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C usage := "contains the configuration to diff" cmd.Flags().StringArray("prune-allowlist", []string{}, "Overwrite the default whitelist with for --prune") cmd.Flags().Bool("prune", false, "Include resources that would be deleted by pruning. Can be used with -l and default shows all resources would be pruned") + cmd.Flags().BoolVar(&options.ShowManagedFields, "show-managed-fields", options.ShowManagedFields, "If true, include managed fields in the diff.") cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) cmdutil.AddServerSideApplyFlags(cmd) cmdutil.AddFieldManagerFlagVar(cmd, &options.FieldManager, apply.FieldManagerClientSideApply) @@ -555,7 +557,7 @@ func NewDiffer(from, to string) (*Differ, error) { } // Diff diffs to versions of a specific object, and print both versions to directories. -func (d *Differ) Diff(obj Object, printer Printer) error { +func (d *Differ) Diff(obj Object, printer Printer, showManagedFields bool) error { from, err := d.From.getObject(obj) if err != nil { return err @@ -565,6 +567,11 @@ func (d *Differ) Diff(obj Object, printer Printer) error { return err } + if !showManagedFields { + from = omitManagedFields(from) + to = omitManagedFields(to) + } + // Mask secret values if object is V1Secret if gvk := to.GetObjectKind().GroupVersionKind(); gvk.Version == "v1" && gvk.Kind == "Secret" { m, err := NewMasker(from, to) @@ -583,6 +590,16 @@ func (d *Differ) Diff(obj Object, printer Printer) error { return nil } +func omitManagedFields(o runtime.Object) runtime.Object { + a, err := meta.Accessor(o) + if err != nil { + // The object is not a `metav1.Object`, ignore it. + return o + } + a.SetManagedFields(nil) + return o +} + // Run runs the diff program against both directories. func (d *Differ) Run(diff *DiffProgram) error { return diff.Run(d.From.Dir.Name, d.To.Dir.Name) @@ -653,7 +670,7 @@ func (o *DiffOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str return nil } -// RunDiff uses the factory to parse file arguments, find the version to +// Run uses the factory to parse file arguments, find the version to // diff, and find each Info object for each files, and runs against the // differ. func (o *DiffOptions) Run() error { @@ -718,7 +735,7 @@ func (o *DiffOptions) Run() error { o.pruner.MarkVisited(info) } - err = differ.Diff(obj, printer) + err = differ.Diff(obj, printer, o.ShowManagedFields) if !isConflict(err) { break } diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/diff/diff_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/diff/diff_test.go index d88f7ec378e..ca86b7ab0a1 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/diff/diff_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/diff/diff_test.go @@ -18,6 +18,7 @@ package diff import ( "bytes" + "fmt" "io/ioutil" "os" "path" @@ -200,7 +201,7 @@ func TestDiffer(t *testing.T) { live: map[string]interface{}{"live": true}, merged: map[string]interface{}{"merged": true}, } - err = diff.Diff(&obj, Printer{}) + err = diff.Diff(&obj, Printer{}, true) if err != nil { t.Fatal(err) } @@ -223,6 +224,85 @@ func TestDiffer(t *testing.T) { } } +func TestShowManagedFields(t *testing.T) { + diff, err := NewDiffer("LIVE", "MERGED") + if err != nil { + t.Fatal(err) + } + defer diff.TearDown() + + testCases := []struct { + name string + showManagedFields bool + expectedFromContent string + expectedToContent string + }{ + { + name: "without managed fields", + showManagedFields: false, + expectedFromContent: `live: true +metadata: + name: foo +`, + expectedToContent: `merged: true +metadata: + name: foo +`, + }, + { + name: "with managed fields", + showManagedFields: true, + expectedFromContent: `live: true +metadata: + managedFields: mf-data + name: foo +`, + expectedToContent: `merged: true +metadata: + managedFields: mf-data + name: foo +`, + }, + } + + for i, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + obj := FakeObject{ + name: fmt.Sprintf("TestCase%d", i), + live: map[string]interface{}{ + "live": true, + "metadata": map[string]interface{}{ + "managedFields": "mf-data", + "name": "foo", + }, + }, + merged: map[string]interface{}{ + "merged": true, + "metadata": map[string]interface{}{ + "managedFields": "mf-data", + "name": "foo", + }, + }, + } + + err = diff.Diff(&obj, Printer{}, tc.showManagedFields) + if err != nil { + t.Fatal(err) + } + + actualFromContent, _ := ioutil.ReadFile(path.Join(diff.From.Dir.Name, obj.Name())) + if string(actualFromContent) != tc.expectedFromContent { + t.Fatalf("File has %q, expected %q", string(actualFromContent), tc.expectedFromContent) + } + + actualToContent, _ := ioutil.ReadFile(path.Join(diff.To.Dir.Name, obj.Name())) + if string(actualToContent) != tc.expectedToContent { + t.Fatalf("File has %q, expected %q", string(actualToContent), tc.expectedToContent) + } + }) + } +} + func TestMasker(t *testing.T) { type diff struct { from runtime.Object