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  }