github.com/grafana/tanka@v0.26.1-0.20240506093700-c22cfc35c21a/cmd/tk/workflow.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "os" 6 7 "github.com/fatih/color" 8 "github.com/go-clix/cli" 9 "github.com/posener/complete" 10 11 "github.com/grafana/tanka/pkg/process" 12 "github.com/grafana/tanka/pkg/tanka" 13 "github.com/grafana/tanka/pkg/term" 14 ) 15 16 // special exit codes for tk diff 17 const ( 18 // no changes 19 ExitStatusClean = 0 20 // differences between the local config and the cluster 21 ExitStatusDiff = 16 22 ) 23 24 var ( 25 colorValues = cli.PredictSet("auto", "always", "never") 26 ) 27 28 func setForceColor(opts *tanka.DiffBaseOpts) error { 29 switch opts.Color { 30 case "": 31 case "auto": 32 case "always": 33 color.NoColor = false 34 case "never": 35 color.NoColor = true 36 default: 37 return fmt.Errorf(`--color must be either: "auto", "always", or "never"`) 38 } 39 return nil 40 } 41 42 func validateDryRun(dryRunStr string) error { 43 switch dryRunStr { 44 case "", "none", "client", "server": 45 return nil 46 } 47 return fmt.Errorf(`--dry-run must be either: "", "none", "server" or "client"`) 48 } 49 50 func validateAutoApprove(autoApproveDeprecated bool, autoApproveString string) (tanka.AutoApproveSetting, error) { 51 var result tanka.AutoApproveSetting 52 53 if autoApproveString != "" && autoApproveDeprecated { 54 return result, fmt.Errorf("--dangerous-auto-approve and --auto-approve are mutually exclusive") 55 } 56 if autoApproveString == "" { 57 if autoApproveDeprecated { 58 result = tanka.AutoApproveAlways 59 } else { 60 result = tanka.AutoApproveNever 61 } 62 } else { 63 if autoApproveString != string(tanka.AutoApproveAlways) && autoApproveString != string(tanka.AutoApproveNever) && autoApproveString != string(tanka.AutoApproveNoChanges) { 64 return result, fmt.Errorf("invalid value for --auto-approve: %s", autoApproveString) 65 } 66 result = tanka.AutoApproveSetting(autoApproveString) 67 } 68 69 return result, nil 70 } 71 72 func applyCmd() *cli.Command { 73 cmd := &cli.Command{ 74 Use: "apply <path>", 75 Short: "apply the configuration to the cluster", 76 Args: workflowArgs, 77 Predictors: complete.Flags{ 78 "color": colorValues, 79 "diff-strategy": cli.PredictSet("native", "subset", "validate", "server", "none"), 80 "apply-strategy": cli.PredictSet("client", "server"), 81 }, 82 } 83 84 var opts tanka.ApplyOpts 85 cmd.Flags().BoolVar(&opts.Validate, "validate", true, "validation of resources (kubectl --validate=false)") 86 cmd.Flags().StringVar(&opts.ApplyStrategy, "apply-strategy", "", "force the apply strategy to use. Automatically chosen if not set.") 87 cmd.Flags().StringVar(&opts.DiffStrategy, "diff-strategy", "", "force the diff strategy to use. Automatically chosen if not set.") 88 89 var ( 90 autoApproveDeprecated bool 91 autoApproveString string 92 ) 93 addApplyFlags(cmd.Flags(), &opts.ApplyBaseOpts, &autoApproveDeprecated, &autoApproveString) 94 addDiffFlags(cmd.Flags(), &opts.DiffBaseOpts) 95 vars := workflowFlags(cmd.Flags()) 96 getJsonnetOpts := jsonnetFlags(cmd.Flags()) 97 98 cmd.Run = func(cmd *cli.Command, args []string) error { 99 err := validateDryRun(opts.DryRun) 100 if err != nil { 101 return err 102 } 103 if opts.AutoApprove, err = validateAutoApprove(autoApproveDeprecated, autoApproveString); err != nil { 104 return err 105 } 106 if err := setForceColor(&opts.DiffBaseOpts); err != nil { 107 return err 108 } 109 110 filters, err := process.StrExps(vars.targets...) 111 if err != nil { 112 return err 113 } 114 opts.Filters = filters 115 opts.JsonnetOpts = getJsonnetOpts() 116 opts.Name = vars.name 117 opts.JsonnetImplementation = vars.jsonnetImplementation 118 119 return tanka.Apply(args[0], opts) 120 } 121 return cmd 122 } 123 124 func pruneCmd() *cli.Command { 125 cmd := &cli.Command{ 126 Use: "prune <path>", 127 Short: "delete resources removed from Jsonnet", 128 Args: workflowArgs, 129 Predictors: complete.Flags{ 130 "color": colorValues, 131 }, 132 } 133 134 var opts tanka.PruneOpts 135 cmd.Flags().StringVar(&opts.Name, "name", "", "string that only a single inline environment contains in its name") 136 var ( 137 autoApproveDeprecated bool 138 autoApproveString string 139 ) 140 addApplyFlags(cmd.Flags(), &opts.ApplyBaseOpts, &autoApproveDeprecated, &autoApproveString) 141 addDiffFlags(cmd.Flags(), &opts.DiffBaseOpts) 142 getJsonnetOpts := jsonnetFlags(cmd.Flags()) 143 144 cmd.Run = func(cmd *cli.Command, args []string) error { 145 err := validateDryRun(opts.DryRun) 146 if err != nil { 147 return err 148 } 149 if opts.AutoApprove, err = validateAutoApprove(autoApproveDeprecated, autoApproveString); err != nil { 150 return err 151 } 152 if err := setForceColor(&opts.DiffBaseOpts); err != nil { 153 return err 154 } 155 156 opts.JsonnetOpts = getJsonnetOpts() 157 158 return tanka.Prune(args[0], opts) 159 } 160 161 return cmd 162 } 163 164 func deleteCmd() *cli.Command { 165 cmd := &cli.Command{ 166 Use: "delete <path>", 167 Short: "delete the environment from cluster", 168 Args: workflowArgs, 169 Predictors: complete.Flags{ 170 "color": colorValues, 171 }, 172 } 173 174 var opts tanka.DeleteOpts 175 176 var ( 177 autoApproveDeprecated bool 178 autoApproveString string 179 ) 180 addApplyFlags(cmd.Flags(), &opts.ApplyBaseOpts, &autoApproveDeprecated, &autoApproveString) 181 addDiffFlags(cmd.Flags(), &opts.DiffBaseOpts) 182 vars := workflowFlags(cmd.Flags()) 183 getJsonnetOpts := jsonnetFlags(cmd.Flags()) 184 185 cmd.Run = func(cmd *cli.Command, args []string) error { 186 err := validateDryRun(opts.DryRun) 187 if err != nil { 188 return err 189 } 190 if opts.AutoApprove, err = validateAutoApprove(autoApproveDeprecated, autoApproveString); err != nil { 191 return err 192 } 193 if err := setForceColor(&opts.DiffBaseOpts); err != nil { 194 return err 195 } 196 197 filters, err := process.StrExps(vars.targets...) 198 if err != nil { 199 return err 200 } 201 opts.Filters = filters 202 opts.JsonnetOpts = getJsonnetOpts() 203 opts.Name = vars.name 204 opts.JsonnetImplementation = vars.jsonnetImplementation 205 206 return tanka.Delete(args[0], opts) 207 } 208 return cmd 209 } 210 211 func diffCmd() *cli.Command { 212 cmd := &cli.Command{ 213 Use: "diff <path>", 214 Short: "differences between the configuration and the cluster", 215 Args: workflowArgs, 216 Predictors: complete.Flags{ 217 "color": colorValues, 218 "diff-strategy": cli.PredictSet("native", "subset", "validate", "server"), 219 }, 220 } 221 222 var opts tanka.DiffOpts 223 addDiffFlags(cmd.Flags(), &opts.DiffBaseOpts) 224 cmd.Flags().StringVar(&opts.Strategy, "diff-strategy", "", "force the diff-strategy to use. Automatically chosen if not set.") 225 cmd.Flags().BoolVarP(&opts.Summarize, "summarize", "s", false, "print summary of the differences, not the actual contents") 226 cmd.Flags().BoolVarP(&opts.WithPrune, "with-prune", "p", false, "include objects deleted from the configuration in the differences") 227 cmd.Flags().BoolVarP(&opts.ExitZero, "exit-zero", "z", false, "Exit with 0 even when differences are found.") 228 229 vars := workflowFlags(cmd.Flags()) 230 getJsonnetOpts := jsonnetFlags(cmd.Flags()) 231 232 cmd.Run = func(cmd *cli.Command, args []string) error { 233 if err := setForceColor(&opts.DiffBaseOpts); err != nil { 234 return err 235 } 236 filters, err := process.StrExps(vars.targets...) 237 if err != nil { 238 return err 239 } 240 opts.Filters = filters 241 opts.JsonnetOpts = getJsonnetOpts() 242 opts.Name = vars.name 243 opts.JsonnetImplementation = vars.jsonnetImplementation 244 245 changes, err := tanka.Diff(args[0], opts) 246 if err != nil { 247 return err 248 } 249 250 if changes == nil { 251 fmt.Fprintln(os.Stderr, "No differences.") 252 os.Exit(ExitStatusClean) 253 } 254 255 r := term.Colordiff(*changes) 256 if err := fPageln(r); err != nil { 257 return err 258 } 259 260 exitStatusDiff := ExitStatusDiff 261 if opts.ExitZero { 262 exitStatusDiff = ExitStatusClean 263 } 264 os.Exit(exitStatusDiff) 265 return nil 266 } 267 268 return cmd 269 } 270 271 func showCmd() *cli.Command { 272 cmd := &cli.Command{ 273 Use: "show <path>", 274 Short: "jsonnet as yaml", 275 Args: workflowArgs, 276 } 277 278 allowRedirect := cmd.Flags().Bool("dangerous-allow-redirect", false, "allow redirecting output to a file or a pipe.") 279 280 vars := workflowFlags(cmd.Flags()) 281 getJsonnetOpts := jsonnetFlags(cmd.Flags()) 282 283 cmd.Run = func(cmd *cli.Command, args []string) error { 284 if !interactive && !*allowRedirect { 285 fmt.Fprintln(os.Stderr, `Redirection of the output of tk show is discouraged and disabled by default. 286 If you want to export .yaml files for use with other tools, try 'tk export'. 287 Otherwise run tk show --dangerous-allow-redirect to bypass this check.`) 288 return nil 289 } 290 291 filters, err := process.StrExps(vars.targets...) 292 if err != nil { 293 return err 294 } 295 296 pretty, err := tanka.Show(args[0], tanka.Opts{ 297 JsonnetOpts: getJsonnetOpts(), 298 Filters: filters, 299 Name: vars.name, 300 JsonnetImplementation: vars.jsonnetImplementation, 301 }) 302 303 if err != nil { 304 return err 305 } 306 307 return pageln(pretty.String()) 308 } 309 return cmd 310 }