github.com/SamarSidharth/kpt@v0.0.0-20231122062228-c7d747ae3ace/commands/live/apply/cmdapply.go (about)

     1  // Copyright 2021 The kpt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package apply
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"os"
    21  	"time"
    22  
    23  	alphaprinterstable "github.com/GoogleContainerTools/kpt/internal/alpha/printers/table"
    24  	"github.com/GoogleContainerTools/kpt/internal/cmdutil"
    25  	"github.com/GoogleContainerTools/kpt/internal/docs/generated/livedocs"
    26  	"github.com/GoogleContainerTools/kpt/internal/util/argutil"
    27  	"github.com/GoogleContainerTools/kpt/internal/util/strings"
    28  	"github.com/GoogleContainerTools/kpt/pkg/live"
    29  	"github.com/GoogleContainerTools/kpt/pkg/status"
    30  	"github.com/spf13/cobra"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    33  	"k8s.io/cli-runtime/pkg/genericclioptions"
    34  	"k8s.io/kubectl/pkg/cmd/util"
    35  	"sigs.k8s.io/cli-utils/cmd/flagutils"
    36  	"sigs.k8s.io/cli-utils/pkg/apply"
    37  	"sigs.k8s.io/cli-utils/pkg/common"
    38  	"sigs.k8s.io/cli-utils/pkg/inventory"
    39  	"sigs.k8s.io/cli-utils/pkg/printers"
    40  	cliutilsprinter "sigs.k8s.io/cli-utils/pkg/printers/printer"
    41  )
    42  
    43  // NewRunner returns a command runner
    44  func NewRunner(
    45  	ctx context.Context,
    46  	factory util.Factory,
    47  	ioStreams genericclioptions.IOStreams,
    48  	alpha bool,
    49  ) *Runner {
    50  	r := &Runner{
    51  		ctx:         ctx,
    52  		ioStreams:   ioStreams,
    53  		factory:     factory,
    54  		applyRunner: runApply,
    55  		alpha:       alpha,
    56  	}
    57  	c := &cobra.Command{
    58  		Use:     "apply [PKG_PATH | -]",
    59  		RunE:    r.runE,
    60  		PreRunE: r.preRunE,
    61  		Short:   livedocs.ApplyShort,
    62  		Long:    livedocs.ApplyShort + "\n" + livedocs.ApplyLong,
    63  		Example: livedocs.ApplyExamples,
    64  	}
    65  	r.Command = c
    66  
    67  	c.Flags().BoolVar(&r.serverSideOptions.ServerSideApply, "server-side", false,
    68  		"If true, apply merge patch is calculated on API server instead of client.")
    69  	c.Flags().BoolVar(&r.serverSideOptions.ForceConflicts, "force-conflicts", false,
    70  		"If true, overwrite applied fields on server if field manager conflict.")
    71  	c.Flags().StringVar(&r.serverSideOptions.FieldManager, "field-manager", common.DefaultFieldManager,
    72  		"The client owner of the fields being applied on the server-side.")
    73  	c.Flags().StringVar(&r.output, "output", printers.DefaultPrinter(),
    74  		fmt.Sprintf("Output format, must be one of %s", strings.JoinStringsWithQuotes(printers.SupportedPrinters())))
    75  	c.Flags().DurationVar(&r.reconcileTimeout, "reconcile-timeout", time.Duration(0),
    76  		"Timeout threshold for waiting for all resources to reach the Current status.")
    77  	c.Flags().StringVar(&r.prunePropagationPolicyString, "prune-propagation-policy",
    78  		"Background", "Propagation policy for pruning")
    79  	c.Flags().DurationVar(&r.pruneTimeout, "prune-timeout", time.Duration(0),
    80  		"Timeout threshold for waiting for all pruned resources to be deleted")
    81  	c.Flags().StringVar(&r.inventoryPolicyString, flagutils.InventoryPolicyFlag, flagutils.InventoryPolicyStrict,
    82  		"It determines the behavior when the resources don't belong to current inventory. Available options "+
    83  			fmt.Sprintf("%q and %q.", flagutils.InventoryPolicyStrict, flagutils.InventoryPolicyAdopt))
    84  	c.Flags().BoolVar(&r.installCRD, "install-resource-group", true,
    85  		"If true, install the inventory ResourceGroup CRD before applying.")
    86  	c.Flags().BoolVar(&r.dryRun, "dry-run", false,
    87  		"dry-run apply for the resources in the package.")
    88  	c.Flags().BoolVar(&r.printStatusEvents, "show-status-events", false,
    89  		"Print status events (always enabled for table output)")
    90  	return r
    91  }
    92  
    93  func NewCommand(ctx context.Context, factory util.Factory,
    94  	ioStreams genericclioptions.IOStreams, alpha bool) *cobra.Command {
    95  	return NewRunner(ctx, factory, ioStreams, alpha).Command
    96  }
    97  
    98  // Runner contains the run function
    99  type Runner struct {
   100  	ctx        context.Context
   101  	alpha      bool
   102  	Command    *cobra.Command
   103  	PreProcess func(info inventory.Info, strategy common.DryRunStrategy) (inventory.Policy, error)
   104  	ioStreams  genericclioptions.IOStreams
   105  	factory    util.Factory
   106  
   107  	installCRD                   bool
   108  	serverSideOptions            common.ServerSideOptions
   109  	output                       string
   110  	reconcileTimeout             time.Duration
   111  	prunePropagationPolicyString string
   112  	pruneTimeout                 time.Duration
   113  	inventoryPolicyString        string
   114  	dryRun                       bool
   115  	printStatusEvents            bool
   116  
   117  	inventoryPolicy inventory.Policy
   118  	prunePropPolicy metav1.DeletionPropagation
   119  
   120  	applyRunner func(r *Runner, invInfo inventory.Info, objs []*unstructured.Unstructured,
   121  		dryRunStrategy common.DryRunStrategy) error
   122  }
   123  
   124  func (r *Runner) preRunE(cmd *cobra.Command, _ []string) error {
   125  	var err error
   126  	r.prunePropPolicy, err = flagutils.ConvertPropagationPolicy(r.prunePropagationPolicyString)
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	r.inventoryPolicy, err = flagutils.ConvertInventoryPolicy(r.inventoryPolicyString)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	if found := printers.ValidatePrinterType(r.output); !found {
   137  		return fmt.Errorf("unknown output type %q", r.output)
   138  	}
   139  
   140  	// We default the install-resource-group flag to false if we are doing
   141  	// dry-run, unless the user has explicitly used the install-resource-group flag.
   142  	if r.dryRun && !cmd.Flags().Changed("install-resource-group") {
   143  		r.installCRD = false
   144  	}
   145  
   146  	if !r.installCRD {
   147  		err := cmdutil.VerifyResourceGroupCRD(r.factory)
   148  		if err != nil {
   149  			return err
   150  		}
   151  	}
   152  	return nil
   153  }
   154  
   155  func (r *Runner) runE(c *cobra.Command, args []string) error {
   156  	if len(args) == 0 {
   157  		// default to the current working directory
   158  		cwd, err := os.Getwd()
   159  		if err != nil {
   160  			return err
   161  		}
   162  		args = append(args, cwd)
   163  	}
   164  	path := args[0]
   165  	var err error
   166  	if args[0] != "-" {
   167  		path, err = argutil.ResolveSymlink(r.ctx, path)
   168  		if err != nil {
   169  			return err
   170  		}
   171  	}
   172  
   173  	objs, inv, err := live.Load(r.factory, path, c.InOrStdin())
   174  	if err != nil {
   175  		return err
   176  	}
   177  
   178  	// objs may contain kind List
   179  	objs, err = live.Flatten(objs)
   180  	if err != nil {
   181  		return err
   182  	}
   183  
   184  	invInfo, err := live.ToInventoryInfo(inv)
   185  	if err != nil {
   186  		return err
   187  	}
   188  
   189  	dryRunStrategy := common.DryRunNone
   190  	if r.dryRun {
   191  		if r.serverSideOptions.ServerSideApply {
   192  			dryRunStrategy = common.DryRunServer
   193  		} else {
   194  			dryRunStrategy = common.DryRunClient
   195  		}
   196  	}
   197  
   198  	// TODO(mortent): Figure out if we can do this differently.
   199  	if r.PreProcess != nil {
   200  		r.inventoryPolicy, err = r.PreProcess(invInfo, dryRunStrategy)
   201  		if err != nil {
   202  			return err
   203  		}
   204  	}
   205  
   206  	return r.applyRunner(r, invInfo, objs, dryRunStrategy)
   207  }
   208  
   209  func runApply(r *Runner, invInfo inventory.Info, objs []*unstructured.Unstructured,
   210  	dryRunStrategy common.DryRunStrategy) error {
   211  	if r.installCRD {
   212  		f := r.factory
   213  		// Install the ResourceGroup CRD if it is not already installed
   214  		// or if the ResourceGroup CRD doesn't match the CRD in the
   215  		// kpt binary.
   216  		err := cmdutil.VerifyResourceGroupCRD(f)
   217  		if err != nil {
   218  			if err = cmdutil.InstallResourceGroupCRD(r.ctx, f); err != nil {
   219  				return err
   220  			}
   221  		} else if !live.ResourceGroupCRDMatched(f) {
   222  			if err = cmdutil.InstallResourceGroupCRD(r.ctx, f); err != nil {
   223  				return &cmdutil.ResourceGroupCRDNotLatestError{
   224  					Err: err,
   225  				}
   226  			}
   227  		}
   228  	}
   229  
   230  	// Run the applier. It will return a channel where we can receive updates
   231  	// to keep track of progress and any issues.
   232  	invClient, err := inventory.NewClient(r.factory, live.WrapInventoryObj, live.InvToUnstructuredFunc, inventory.StatusPolicyAll, live.ResourceGroupGVK)
   233  	if err != nil {
   234  		return err
   235  	}
   236  
   237  	statusWatcher, err := status.NewStatusWatcher(r.factory)
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	applier, err := apply.NewApplierBuilder().
   243  		WithFactory(r.factory).
   244  		WithInventoryClient(invClient).
   245  		WithStatusWatcher(statusWatcher).
   246  		Build()
   247  	if err != nil {
   248  		return err
   249  	}
   250  
   251  	ch := applier.Run(r.ctx, invInfo, objs, apply.ApplierOptions{
   252  		ServerSideOptions:      r.serverSideOptions,
   253  		ReconcileTimeout:       r.reconcileTimeout,
   254  		EmitStatusEvents:       true, // We are always waiting for reconcile.
   255  		DryRunStrategy:         dryRunStrategy,
   256  		PrunePropagationPolicy: r.prunePropPolicy,
   257  		PruneTimeout:           r.pruneTimeout,
   258  		InventoryPolicy:        r.inventoryPolicy,
   259  	})
   260  
   261  	// Print the preview strategy unless the output format is json.
   262  	if dryRunStrategy.ClientOrServerDryRun() && r.output != printers.JSONPrinter {
   263  		if dryRunStrategy.ServerDryRun() {
   264  			fmt.Println("Dry-run strategy: server")
   265  		} else {
   266  			fmt.Println("Dry-run strategy: client")
   267  		}
   268  	}
   269  
   270  	// The printer will print updates from the channel. It will block
   271  	// until the channel is closed.
   272  	var printer cliutilsprinter.Printer
   273  	if r.alpha && r.output == printers.TablePrinter {
   274  		printer = &alphaprinterstable.Printer{
   275  			IOStreams: r.ioStreams,
   276  		}
   277  	} else {
   278  		printer = printers.GetPrinter(r.output, r.ioStreams)
   279  	}
   280  	return printer.Print(ch, dryRunStrategy, r.printStatusEvents)
   281  }