...

Source file src/k8s.io/kubectl/pkg/cmd/diff/diff.go

Documentation: k8s.io/kubectl/pkg/cmd/diff

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package diff
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"path/filepath"
    24  	"regexp"
    25  	"strings"
    26  
    27  	"github.com/jonboulle/clockwork"
    28  	"github.com/spf13/cobra"
    29  	"k8s.io/apimachinery/pkg/api/errors"
    30  	"k8s.io/apimachinery/pkg/api/meta"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/types"
    35  	"k8s.io/cli-runtime/pkg/genericiooptions"
    36  	"k8s.io/cli-runtime/pkg/resource"
    37  	"k8s.io/client-go/dynamic"
    38  	"k8s.io/client-go/openapi3"
    39  	"k8s.io/klog/v2"
    40  	"k8s.io/kubectl/pkg/cmd/apply"
    41  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    42  	"k8s.io/kubectl/pkg/scheme"
    43  	"k8s.io/kubectl/pkg/util"
    44  	"k8s.io/kubectl/pkg/util/i18n"
    45  	"k8s.io/kubectl/pkg/util/openapi"
    46  	"k8s.io/kubectl/pkg/util/prune"
    47  	"k8s.io/kubectl/pkg/util/templates"
    48  	"k8s.io/utils/exec"
    49  	"sigs.k8s.io/yaml"
    50  )
    51  
    52  var (
    53  	diffLong = templates.LongDesc(i18n.T(`
    54  		Diff configurations specified by file name or stdin between the current online
    55  		configuration, and the configuration as it would be if applied.
    56  
    57  		The output is always YAML.
    58  
    59  		KUBECTL_EXTERNAL_DIFF environment variable can be used to select your own
    60  		diff command. Users can use external commands with params too, example:
    61  		KUBECTL_EXTERNAL_DIFF="colordiff -N -u"
    62  
    63  		By default, the "diff" command available in your path will be
    64  		run with the "-u" (unified diff) and "-N" (treat absent files as empty) options.
    65  
    66  		Exit status:
    67  		 0
    68  		No differences were found.
    69  		 1
    70  		Differences were found.
    71  		 >1
    72  		Kubectl or diff failed with an error.
    73  
    74  		Note: KUBECTL_EXTERNAL_DIFF, if used, is expected to follow that convention.`))
    75  
    76  	diffExample = templates.Examples(i18n.T(`
    77  		# Diff resources included in pod.json
    78  		kubectl diff -f pod.json
    79  
    80  		# Diff file read from stdin
    81  		cat service.yaml | kubectl diff -f -`))
    82  )
    83  
    84  // Number of times we try to diff before giving-up
    85  const maxRetries = 4
    86  
    87  // Constants for masking sensitive values
    88  const (
    89  	sensitiveMaskDefault = "***"
    90  	sensitiveMaskBefore  = "*** (before)"
    91  	sensitiveMaskAfter   = "*** (after)"
    92  )
    93  
    94  // diffError returns the ExitError if the status code is less than 1,
    95  // nil otherwise.
    96  func diffError(err error) exec.ExitError {
    97  	if err, ok := err.(exec.ExitError); ok && err.ExitStatus() <= 1 {
    98  		return err
    99  	}
   100  	return nil
   101  }
   102  
   103  type DiffOptions struct {
   104  	FilenameOptions resource.FilenameOptions
   105  
   106  	ServerSideApply   bool
   107  	FieldManager      string
   108  	ForceConflicts    bool
   109  	ShowManagedFields bool
   110  
   111  	Concurrency      int
   112  	Selector         string
   113  	OpenAPIGetter    openapi.OpenAPIResourcesGetter
   114  	OpenAPIV3Root    openapi3.Root
   115  	DynamicClient    dynamic.Interface
   116  	CmdNamespace     string
   117  	EnforceNamespace bool
   118  	Builder          *resource.Builder
   119  	Diff             *DiffProgram
   120  
   121  	pruner  *pruner
   122  	tracker *tracker
   123  }
   124  
   125  func NewDiffOptions(ioStreams genericiooptions.IOStreams) *DiffOptions {
   126  	return &DiffOptions{
   127  		Diff: &DiffProgram{
   128  			Exec:      exec.New(),
   129  			IOStreams: ioStreams,
   130  		},
   131  	}
   132  }
   133  
   134  func NewCmdDiff(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   135  	options := NewDiffOptions(streams)
   136  	cmd := &cobra.Command{
   137  		Use:                   "diff -f FILENAME",
   138  		DisableFlagsInUseLine: true,
   139  		Short:                 i18n.T("Diff the live version against a would-be applied version"),
   140  		Long:                  diffLong,
   141  		Example:               diffExample,
   142  		Run: func(cmd *cobra.Command, args []string) {
   143  			cmdutil.CheckDiffErr(options.Complete(f, cmd, args))
   144  			cmdutil.CheckDiffErr(options.Validate())
   145  			// `kubectl diff` propagates the error code from
   146  			// diff or `KUBECTL_EXTERNAL_DIFF`. Also, we
   147  			// don't want to print an error if diff returns
   148  			// error code 1, which simply means that changes
   149  			// were found. We also don't want kubectl to
   150  			// return 1 if there was a problem.
   151  			if err := options.Run(); err != nil {
   152  				if exitErr := diffError(err); exitErr != nil {
   153  					cmdutil.CheckErr(cmdutil.ErrExit)
   154  				}
   155  				cmdutil.CheckDiffErr(err)
   156  			}
   157  		},
   158  	}
   159  
   160  	// Flag errors exit with code 1, however according to the diff
   161  	// command it means changes were found.
   162  	// Thus, it should return status code greater than 1.
   163  	cmd.SetFlagErrorFunc(func(command *cobra.Command, err error) error {
   164  		cmdutil.CheckDiffErr(cmdutil.UsageErrorf(cmd, err.Error()))
   165  		return nil
   166  	})
   167  
   168  	usage := "contains the configuration to diff"
   169  	cmd.Flags().StringArray("prune-allowlist", []string{}, "Overwrite the default allowlist with <group/version/kind> for --prune")
   170  	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")
   171  	cmd.Flags().BoolVar(&options.ShowManagedFields, "show-managed-fields", options.ShowManagedFields, "If true, include managed fields in the diff.")
   172  	cmd.Flags().IntVar(&options.Concurrency, "concurrency", 1, "Number of objects to process in parallel when diffing against the live version. Larger number = faster, but more memory, I/O and CPU over that shorter period of time.")
   173  	cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
   174  	cmdutil.AddServerSideApplyFlags(cmd)
   175  	cmdutil.AddFieldManagerFlagVar(cmd, &options.FieldManager, apply.FieldManagerClientSideApply)
   176  	cmdutil.AddLabelSelectorFlagVar(cmd, &options.Selector)
   177  
   178  	return cmd
   179  }
   180  
   181  // DiffProgram finds and run the diff program. The value of
   182  // KUBECTL_EXTERNAL_DIFF environment variable will be used a diff
   183  // program. By default, `diff(1)` will be used.
   184  type DiffProgram struct {
   185  	Exec exec.Interface
   186  	genericiooptions.IOStreams
   187  }
   188  
   189  func (d *DiffProgram) getCommand(args ...string) (string, exec.Cmd) {
   190  	diff := ""
   191  	if envDiff := os.Getenv("KUBECTL_EXTERNAL_DIFF"); envDiff != "" {
   192  		diffCommand := strings.Split(envDiff, " ")
   193  		diff = diffCommand[0]
   194  
   195  		if len(diffCommand) > 1 {
   196  			// Regex accepts: Alphanumeric (case-insensitive), dash and equal
   197  			isValidChar := regexp.MustCompile(`^[a-zA-Z0-9-=]+$`).MatchString
   198  			for i := 1; i < len(diffCommand); i++ {
   199  				if isValidChar(diffCommand[i]) {
   200  					args = append(args, diffCommand[i])
   201  				}
   202  			}
   203  		}
   204  	} else {
   205  		diff = "diff"
   206  		args = append([]string{"-u", "-N"}, args...)
   207  	}
   208  
   209  	cmd := d.Exec.Command(diff, args...)
   210  	cmd.SetStdout(d.Out)
   211  	cmd.SetStderr(d.ErrOut)
   212  
   213  	return diff, cmd
   214  }
   215  
   216  // Run runs the detected diff program. `from` and `to` are the directory to diff.
   217  func (d *DiffProgram) Run(from, to string) error {
   218  	diff, cmd := d.getCommand(from, to)
   219  	if err := cmd.Run(); err != nil {
   220  		// Let's not wrap diff errors, or we won't be able to
   221  		// differentiate them later.
   222  		if diffErr := diffError(err); diffErr != nil {
   223  			return diffErr
   224  		}
   225  		return fmt.Errorf("failed to run %q: %v", diff, err)
   226  	}
   227  	return nil
   228  }
   229  
   230  // Printer is used to print an object.
   231  type Printer struct{}
   232  
   233  // Print the object inside the writer w.
   234  func (p *Printer) Print(obj runtime.Object, w io.Writer) error {
   235  	if obj == nil {
   236  		return nil
   237  	}
   238  	data, err := yaml.Marshal(obj)
   239  	if err != nil {
   240  		return err
   241  	}
   242  	_, err = w.Write(data)
   243  	return err
   244  
   245  }
   246  
   247  // DiffVersion gets the proper version of objects, and aggregate them into a directory.
   248  type DiffVersion struct {
   249  	Dir  *Directory
   250  	Name string
   251  }
   252  
   253  // NewDiffVersion creates a new DiffVersion with the named version.
   254  func NewDiffVersion(name string) (*DiffVersion, error) {
   255  	dir, err := CreateDirectory(name)
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  	return &DiffVersion{
   260  		Dir:  dir,
   261  		Name: name,
   262  	}, nil
   263  }
   264  
   265  func (v *DiffVersion) getObject(obj Object) (runtime.Object, error) {
   266  	switch v.Name {
   267  	case "LIVE":
   268  		return obj.Live(), nil
   269  	case "MERGED":
   270  		return obj.Merged()
   271  	}
   272  	return nil, fmt.Errorf("Unknown version: %v", v.Name)
   273  }
   274  
   275  // Print prints the object using the printer into a new file in the directory.
   276  func (v *DiffVersion) Print(name string, obj runtime.Object, printer Printer) error {
   277  	f, err := v.Dir.NewFile(name)
   278  	if err != nil {
   279  		return err
   280  	}
   281  	defer f.Close()
   282  	return printer.Print(obj, f)
   283  }
   284  
   285  // Directory creates a new temp directory, and allows to easily create new files.
   286  type Directory struct {
   287  	Name string
   288  }
   289  
   290  // CreateDirectory does create the actual disk directory, and return a
   291  // new representation of it.
   292  func CreateDirectory(prefix string) (*Directory, error) {
   293  	name, err := os.MkdirTemp("", prefix+"-")
   294  	if err != nil {
   295  		return nil, err
   296  	}
   297  
   298  	return &Directory{
   299  		Name: name,
   300  	}, nil
   301  }
   302  
   303  // NewFile creates a new file in the directory.
   304  func (d *Directory) NewFile(name string) (*os.File, error) {
   305  	return os.OpenFile(filepath.Join(d.Name, name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700)
   306  }
   307  
   308  // Delete removes the directory recursively.
   309  func (d *Directory) Delete() error {
   310  	return os.RemoveAll(d.Name)
   311  }
   312  
   313  // Object is an interface that let's you retrieve multiple version of
   314  // it.
   315  type Object interface {
   316  	Live() runtime.Object
   317  	Merged() (runtime.Object, error)
   318  
   319  	Name() string
   320  }
   321  
   322  // InfoObject is an implementation of the Object interface. It gets all
   323  // the information from the Info object.
   324  type InfoObject struct {
   325  	LocalObj        runtime.Object
   326  	Info            *resource.Info
   327  	Encoder         runtime.Encoder
   328  	OpenAPIGetter   openapi.OpenAPIResourcesGetter
   329  	OpenAPIV3Root   openapi3.Root
   330  	Force           bool
   331  	ServerSideApply bool
   332  	FieldManager    string
   333  	ForceConflicts  bool
   334  	genericiooptions.IOStreams
   335  }
   336  
   337  var _ Object = &InfoObject{}
   338  
   339  // Returns the live version of the object
   340  func (obj InfoObject) Live() runtime.Object {
   341  	return obj.Info.Object
   342  }
   343  
   344  // Returns the "merged" object, as it would look like if applied or
   345  // created.
   346  func (obj InfoObject) Merged() (runtime.Object, error) {
   347  	helper := resource.NewHelper(obj.Info.Client, obj.Info.Mapping).
   348  		DryRun(true).
   349  		WithFieldManager(obj.FieldManager)
   350  	if obj.ServerSideApply {
   351  		data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj.LocalObj)
   352  		if err != nil {
   353  			return nil, err
   354  		}
   355  		options := metav1.PatchOptions{
   356  			Force:        &obj.ForceConflicts,
   357  			FieldManager: obj.FieldManager,
   358  		}
   359  		return helper.Patch(
   360  			obj.Info.Namespace,
   361  			obj.Info.Name,
   362  			types.ApplyPatchType,
   363  			data,
   364  			&options,
   365  		)
   366  	}
   367  
   368  	// Build the patcher, and then apply the patch with dry-run, unless the object doesn't exist, in which case we need to create it.
   369  	if obj.Live() == nil {
   370  		// Dry-run create if the object doesn't exist.
   371  		return helper.CreateWithOptions(
   372  			obj.Info.Namespace,
   373  			true,
   374  			obj.LocalObj,
   375  			&metav1.CreateOptions{},
   376  		)
   377  	}
   378  
   379  	var resourceVersion *string
   380  	if !obj.Force {
   381  		accessor, err := meta.Accessor(obj.Info.Object)
   382  		if err != nil {
   383  			return nil, err
   384  		}
   385  		str := accessor.GetResourceVersion()
   386  		resourceVersion = &str
   387  	}
   388  
   389  	modified, err := util.GetModifiedConfiguration(obj.LocalObj, false, unstructured.UnstructuredJSONScheme)
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  
   394  	// This is using the patcher from apply, to keep the same behavior.
   395  	// We plan on replacing this with server-side apply when it becomes available.
   396  	patcher := &apply.Patcher{
   397  		Mapping:         obj.Info.Mapping,
   398  		Helper:          helper,
   399  		Overwrite:       true,
   400  		BackOff:         clockwork.NewRealClock(),
   401  		OpenAPIGetter:   obj.OpenAPIGetter,
   402  		OpenAPIV3Root:   obj.OpenAPIV3Root,
   403  		ResourceVersion: resourceVersion,
   404  	}
   405  
   406  	_, result, err := patcher.Patch(obj.Info.Object, modified, obj.Info.Source, obj.Info.Namespace, obj.Info.Name, obj.ErrOut)
   407  	return result, err
   408  }
   409  
   410  func (obj InfoObject) Name() string {
   411  	group := ""
   412  	if obj.Info.Mapping.GroupVersionKind.Group != "" {
   413  		group = fmt.Sprintf("%v.", obj.Info.Mapping.GroupVersionKind.Group)
   414  	}
   415  	return group + fmt.Sprintf(
   416  		"%v.%v.%v.%v",
   417  		obj.Info.Mapping.GroupVersionKind.Version,
   418  		obj.Info.Mapping.GroupVersionKind.Kind,
   419  		obj.Info.Namespace,
   420  		obj.Info.Name,
   421  	)
   422  }
   423  
   424  // toUnstructured converts a runtime.Object into an unstructured.Unstructured object.
   425  func toUnstructured(obj runtime.Object) (*unstructured.Unstructured, error) {
   426  	if obj == nil {
   427  		return nil, nil
   428  	}
   429  	c, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj.DeepCopyObject())
   430  	if err != nil {
   431  		return nil, fmt.Errorf("convert to unstructured: %w", err)
   432  	}
   433  	u := &unstructured.Unstructured{}
   434  	u.SetUnstructuredContent(c)
   435  	return u, nil
   436  }
   437  
   438  // Masker masks sensitive values in an object while preserving diff-able
   439  // changes.
   440  //
   441  // All sensitive values in the object will be masked with a fixed-length
   442  // asterisk mask. If two values are different, an additional suffix will
   443  // be added so they can be diff-ed.
   444  type Masker struct {
   445  	from *unstructured.Unstructured
   446  	to   *unstructured.Unstructured
   447  }
   448  
   449  func NewMasker(from, to runtime.Object) (*Masker, error) {
   450  	// Convert objects to unstructured
   451  	f, err := toUnstructured(from)
   452  	if err != nil {
   453  		return nil, fmt.Errorf("convert to unstructured: %w", err)
   454  	}
   455  	t, err := toUnstructured(to)
   456  	if err != nil {
   457  		return nil, fmt.Errorf("convert to unstructured: %w", err)
   458  	}
   459  
   460  	// Run masker
   461  	m := &Masker{
   462  		from: f,
   463  		to:   t,
   464  	}
   465  	if err := m.run(); err != nil {
   466  		return nil, fmt.Errorf("run masker: %w", err)
   467  	}
   468  	return m, nil
   469  }
   470  
   471  // dataFromUnstructured returns the underlying nested map in the data key.
   472  func (m Masker) dataFromUnstructured(u *unstructured.Unstructured) (map[string]interface{}, error) {
   473  	if u == nil {
   474  		return nil, nil
   475  	}
   476  	data, found, err := unstructured.NestedMap(u.UnstructuredContent(), "data")
   477  	if err != nil {
   478  		return nil, fmt.Errorf("get nested map: %w", err)
   479  	}
   480  	if !found {
   481  		return nil, nil
   482  	}
   483  	return data, nil
   484  }
   485  
   486  // run compares and patches sensitive values.
   487  func (m *Masker) run() error {
   488  	// Extract nested map object
   489  	from, err := m.dataFromUnstructured(m.from)
   490  	if err != nil {
   491  		return fmt.Errorf("extract 'data' field: %w", err)
   492  	}
   493  	to, err := m.dataFromUnstructured(m.to)
   494  	if err != nil {
   495  		return fmt.Errorf("extract 'data' field: %w", err)
   496  	}
   497  
   498  	for k := range from {
   499  		// Add before/after suffix when key exists on both
   500  		// objects and are not equal, so that it will be
   501  		// visible in diffs.
   502  		if _, ok := to[k]; ok {
   503  			if from[k] != to[k] {
   504  				from[k] = sensitiveMaskBefore
   505  				to[k] = sensitiveMaskAfter
   506  				continue
   507  			}
   508  			to[k] = sensitiveMaskDefault
   509  		}
   510  		from[k] = sensitiveMaskDefault
   511  	}
   512  	for k := range to {
   513  		// Mask remaining keys that were not in 'from'
   514  		if _, ok := from[k]; !ok {
   515  			to[k] = sensitiveMaskDefault
   516  		}
   517  	}
   518  
   519  	// Patch objects with masked data
   520  	if m.from != nil && from != nil {
   521  		if err := unstructured.SetNestedMap(m.from.UnstructuredContent(), from, "data"); err != nil {
   522  			return fmt.Errorf("patch masked data: %w", err)
   523  		}
   524  	}
   525  	if m.to != nil && to != nil {
   526  		if err := unstructured.SetNestedMap(m.to.UnstructuredContent(), to, "data"); err != nil {
   527  			return fmt.Errorf("patch masked data: %w", err)
   528  		}
   529  	}
   530  	return nil
   531  }
   532  
   533  // From returns the masked version of the 'from' object.
   534  func (m *Masker) From() runtime.Object {
   535  	return m.from
   536  }
   537  
   538  // To returns the masked version of the 'to' object.
   539  func (m *Masker) To() runtime.Object {
   540  	return m.to
   541  }
   542  
   543  // Differ creates two DiffVersion and diffs them.
   544  type Differ struct {
   545  	From *DiffVersion
   546  	To   *DiffVersion
   547  }
   548  
   549  func NewDiffer(from, to string) (*Differ, error) {
   550  	differ := Differ{}
   551  	var err error
   552  	differ.From, err = NewDiffVersion(from)
   553  	if err != nil {
   554  		return nil, err
   555  	}
   556  	differ.To, err = NewDiffVersion(to)
   557  	if err != nil {
   558  		differ.From.Dir.Delete()
   559  		return nil, err
   560  	}
   561  
   562  	return &differ, nil
   563  }
   564  
   565  // Diff diffs to versions of a specific object, and print both versions to directories.
   566  func (d *Differ) Diff(obj Object, printer Printer, showManagedFields bool) error {
   567  	from, err := d.From.getObject(obj)
   568  	if err != nil {
   569  		return err
   570  	}
   571  	to, err := d.To.getObject(obj)
   572  	if err != nil {
   573  		return err
   574  	}
   575  
   576  	if !showManagedFields {
   577  		from = omitManagedFields(from)
   578  		to = omitManagedFields(to)
   579  	}
   580  
   581  	// Mask secret values if object is V1Secret
   582  	if gvk := to.GetObjectKind().GroupVersionKind(); gvk.Version == "v1" && gvk.Kind == "Secret" {
   583  		m, err := NewMasker(from, to)
   584  		if err != nil {
   585  			return err
   586  		}
   587  		from, to = m.From(), m.To()
   588  	}
   589  
   590  	if err := d.From.Print(obj.Name(), from, printer); err != nil {
   591  		return err
   592  	}
   593  	if err := d.To.Print(obj.Name(), to, printer); err != nil {
   594  		return err
   595  	}
   596  	return nil
   597  }
   598  
   599  func omitManagedFields(o runtime.Object) runtime.Object {
   600  	a, err := meta.Accessor(o)
   601  	if err != nil {
   602  		// The object is not a `metav1.Object`, ignore it.
   603  		return o
   604  	}
   605  	a.SetManagedFields(nil)
   606  	return o
   607  }
   608  
   609  // Run runs the diff program against both directories.
   610  func (d *Differ) Run(diff *DiffProgram) error {
   611  	return diff.Run(d.From.Dir.Name, d.To.Dir.Name)
   612  }
   613  
   614  // TearDown removes both temporary directories recursively.
   615  func (d *Differ) TearDown() {
   616  	d.From.Dir.Delete() // Ignore error
   617  	d.To.Dir.Delete()   // Ignore error
   618  }
   619  
   620  func isConflict(err error) bool {
   621  	return err != nil && errors.IsConflict(err)
   622  }
   623  
   624  func (o *DiffOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
   625  	if len(args) != 0 {
   626  		return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args)
   627  	}
   628  
   629  	var err error
   630  
   631  	err = o.FilenameOptions.RequireFilenameOrKustomize()
   632  	if err != nil {
   633  		return err
   634  	}
   635  
   636  	o.ServerSideApply = cmdutil.GetServerSideApplyFlag(cmd)
   637  	o.FieldManager = apply.GetApplyFieldManagerFlag(cmd, o.ServerSideApply)
   638  	o.ForceConflicts = cmdutil.GetForceConflictsFlag(cmd)
   639  	if o.ForceConflicts && !o.ServerSideApply {
   640  		return fmt.Errorf("--force-conflicts only works with --server-side")
   641  	}
   642  
   643  	if !o.ServerSideApply {
   644  		o.OpenAPIGetter = f
   645  		if !cmdutil.OpenAPIV3Patch.IsDisabled() {
   646  			openAPIV3Client, err := f.OpenAPIV3Client()
   647  			if err == nil {
   648  				o.OpenAPIV3Root = openapi3.NewRoot(openAPIV3Client)
   649  			} else {
   650  				klog.V(4).Infof("warning: OpenAPI V3 Patch is enabled but is unable to be loaded. Will fall back to OpenAPI V2")
   651  			}
   652  		}
   653  	}
   654  
   655  	o.DynamicClient, err = f.DynamicClient()
   656  	if err != nil {
   657  		return err
   658  	}
   659  
   660  	o.CmdNamespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
   661  	if err != nil {
   662  		return err
   663  	}
   664  
   665  	if cmdutil.GetFlagBool(cmd, "prune") {
   666  		mapper, err := f.ToRESTMapper()
   667  		if err != nil {
   668  			return err
   669  		}
   670  
   671  		resources, err := prune.ParseResources(mapper, cmdutil.GetFlagStringArray(cmd, "prune-allowlist"))
   672  		if err != nil {
   673  			return err
   674  		}
   675  		o.tracker = newTracker()
   676  		o.pruner = newPruner(o.DynamicClient, mapper, resources, o.Selector)
   677  	}
   678  
   679  	o.Builder = f.NewBuilder()
   680  	return nil
   681  }
   682  
   683  // Run uses the factory to parse file arguments, find the version to
   684  // diff, and find each Info object for each files, and runs against the
   685  // differ.
   686  func (o *DiffOptions) Run() error {
   687  	differ, err := NewDiffer("LIVE", "MERGED")
   688  	if err != nil {
   689  		return err
   690  	}
   691  	defer differ.TearDown()
   692  
   693  	printer := Printer{}
   694  
   695  	r := o.Builder.
   696  		Unstructured().
   697  		VisitorConcurrency(o.Concurrency).
   698  		NamespaceParam(o.CmdNamespace).DefaultNamespace().
   699  		FilenameParam(o.EnforceNamespace, &o.FilenameOptions).
   700  		LabelSelectorParam(o.Selector).
   701  		Flatten().
   702  		Do()
   703  	if err := r.Err(); err != nil {
   704  		return err
   705  	}
   706  
   707  	err = r.Visit(func(info *resource.Info, err error) error {
   708  		if err != nil {
   709  			return err
   710  		}
   711  
   712  		local := info.Object.DeepCopyObject()
   713  		for i := 1; i <= maxRetries; i++ {
   714  			if err = info.Get(); err != nil {
   715  				if !errors.IsNotFound(err) {
   716  					return err
   717  				}
   718  				info.Object = nil
   719  			}
   720  
   721  			force := i == maxRetries
   722  			if force {
   723  				klog.Warningf(
   724  					"Object (%v: %v) keeps changing, diffing without lock",
   725  					info.Object.GetObjectKind().GroupVersionKind(),
   726  					info.Name,
   727  				)
   728  			}
   729  			obj := InfoObject{
   730  				LocalObj:        local,
   731  				Info:            info,
   732  				Encoder:         scheme.DefaultJSONEncoder(),
   733  				OpenAPIGetter:   o.OpenAPIGetter,
   734  				OpenAPIV3Root:   o.OpenAPIV3Root,
   735  				Force:           force,
   736  				ServerSideApply: o.ServerSideApply,
   737  				FieldManager:    o.FieldManager,
   738  				ForceConflicts:  o.ForceConflicts,
   739  				IOStreams:       o.Diff.IOStreams,
   740  			}
   741  
   742  			if o.tracker != nil {
   743  				o.tracker.MarkVisited(info)
   744  			}
   745  
   746  			err = differ.Diff(obj, printer, o.ShowManagedFields)
   747  			if !isConflict(err) {
   748  				break
   749  			}
   750  		}
   751  
   752  		apply.WarnIfDeleting(info.Object, o.Diff.ErrOut)
   753  
   754  		return err
   755  	})
   756  
   757  	if o.pruner != nil {
   758  		prunedObjs, err := o.pruner.pruneAll(o.tracker, o.CmdNamespace != "")
   759  		if err != nil {
   760  			klog.Warningf("pruning failed and could not be evaluated err: %v", err)
   761  		}
   762  
   763  		// Print pruned objects into old file and thus, diff
   764  		// command will show them as pruned.
   765  		for _, p := range prunedObjs {
   766  			name, err := getObjectName(p)
   767  			if err != nil {
   768  				klog.Warningf("pruning failed and object name could not be retrieved: %v", err)
   769  				continue
   770  			}
   771  			if err := differ.From.Print(name, p, printer); err != nil {
   772  				return err
   773  			}
   774  		}
   775  	}
   776  
   777  	if err != nil {
   778  		return err
   779  	}
   780  
   781  	return differ.Run(o.Diff)
   782  }
   783  
   784  // Validate makes sure provided values for DiffOptions are valid
   785  func (o *DiffOptions) Validate() error {
   786  	return nil
   787  }
   788  
   789  func getObjectName(obj runtime.Object) (string, error) {
   790  	gvk := obj.GetObjectKind().GroupVersionKind()
   791  	metadata, err := meta.Accessor(obj)
   792  	if err != nil {
   793  		return "", err
   794  	}
   795  	name := metadata.GetName()
   796  	ns := metadata.GetNamespace()
   797  
   798  	group := ""
   799  	if gvk.Group != "" {
   800  		group = fmt.Sprintf("%v.", gvk.Group)
   801  	}
   802  	return group + fmt.Sprintf(
   803  		"%v.%v.%v.%v",
   804  		gvk.Version,
   805  		gvk.Kind,
   806  		ns,
   807  		name,
   808  	), nil
   809  }
   810  

View as plain text