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 }