github.com/grafana/tanka@v0.26.1-0.20240506093700-c22cfc35c21a/cmd/tk/export.go (about) 1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "regexp" 7 "runtime" 8 9 "github.com/go-clix/cli" 10 11 "github.com/grafana/tanka/pkg/process" 12 "github.com/grafana/tanka/pkg/spec/v1alpha1" 13 "github.com/grafana/tanka/pkg/tanka" 14 ) 15 16 func exportCmd() *cli.Command { 17 args := workflowArgs 18 args.Validator = cli.ArgsMin(2) 19 20 cmd := &cli.Command{ 21 Use: "export <outputDir> <path> [<path>...]", 22 Short: "export environments found in path(s)", 23 Args: args, 24 } 25 26 format := cmd.Flags().String( 27 "format", 28 "{{.apiVersion}}.{{.kind}}-{{or .metadata.name .metadata.generateName}}", 29 "https://tanka.dev/exporting#filenames", 30 ) 31 32 extension := cmd.Flags().String("extension", "yaml", "File extension") 33 parallel := cmd.Flags().IntP("parallel", "p", 8, "Number of environments to process in parallel") 34 cachePath := cmd.Flags().StringP("cache-path", "c", "", "Local file path where cached evaluations should be stored") 35 cacheEnvs := cmd.Flags().StringArrayP("cache-envs", "e", nil, "Regexes which define which environment should be cached (if caching is enabled)") 36 ballastBytes := cmd.Flags().Int("mem-ballast-size-bytes", 0, "Size of memory ballast to allocate. This may improve performance for large environments.") 37 38 merge := cmd.Flags().Bool("merge", false, "Allow merging with existing directory") 39 if err := cmd.Flags().MarkDeprecated("merge", "use --merge-strategy=fail-on-conflicts instead"); err != nil { 40 panic(err) 41 } 42 mergeStrategy := cmd.Flags().String("merge-strategy", "", "What to do when exporting to an existing directory. The default setting is to disallow exporting to an existing directory. Values: 'fail-on-conflicts', 'replace-envs'") 43 mergeDeletedEnvs := cmd.Flags().StringArray("merge-deleted-envs", nil, "Tanka main files that have been deleted. This is used when using a merge strategy to also delete the files of these deleted environments.") 44 45 vars := workflowFlags(cmd.Flags()) 46 getJsonnetOpts := jsonnetFlags(cmd.Flags()) 47 getLabelSelector := labelSelectorFlag(cmd.Flags()) 48 49 recursive := cmd.Flags().BoolP("recursive", "r", false, "Look recursively for Tanka environments") 50 51 cmd.Run = func(cmd *cli.Command, args []string) error { 52 // Allocate a block of memory to alter GC behaviour. See https://github.com/golang/go/issues/23044 53 ballast := make([]byte, *ballastBytes) 54 defer runtime.KeepAlive(ballast) 55 56 filters, err := process.StrExps(vars.targets...) 57 if err != nil { 58 return err 59 } 60 61 opts := tanka.ExportEnvOpts{ 62 Format: *format, 63 Extension: *extension, 64 Opts: tanka.Opts{ 65 JsonnetImplementation: vars.jsonnetImplementation, 66 JsonnetOpts: getJsonnetOpts(), 67 Filters: filters, 68 Name: vars.name, 69 }, 70 Selector: getLabelSelector(), 71 Parallelism: *parallel, 72 MergeDeletedEnvs: *mergeDeletedEnvs, 73 } 74 75 if opts.MergeStrategy, err = determineMergeStrategy(*merge, *mergeStrategy); err != nil { 76 return err 77 } 78 79 opts.Opts.CachePath = *cachePath 80 for _, expr := range *cacheEnvs { 81 regex, err := regexp.Compile(expr) 82 if err != nil { 83 return err 84 } 85 opts.Opts.CachePathRegexes = append(opts.Opts.CachePathRegexes, regex) 86 } 87 88 var exportEnvs []*v1alpha1.Environment 89 // find possible environments 90 if *recursive { 91 // get absolute path to Environment 92 envs, err := tanka.FindEnvsFromPaths(args[1:], tanka.FindOpts{Selector: opts.Selector, Parallelism: opts.Parallelism}) 93 if err != nil { 94 return err 95 } 96 97 for _, env := range envs { 98 if opts.Opts.Name != "" && opts.Opts.Name != env.Metadata.Name { 99 continue 100 } 101 exportEnvs = append(exportEnvs, env) 102 } 103 } else { 104 if len(args[1:]) > 1 { 105 return fmt.Errorf("recursive flag is required when exporting multiple environments") 106 } 107 108 // validate environment 109 env, err := tanka.Peek(args[1], opts.Opts) 110 if err != nil { 111 switch err.(type) { 112 case tanka.ErrMultipleEnvs: 113 fmt.Println("Please use --name to export a single environment or --recursive to export multiple environments.") 114 return err 115 default: 116 return err 117 } 118 } 119 120 exportEnvs = append(exportEnvs, env) 121 } 122 123 // export them 124 return tanka.ExportEnvironments(exportEnvs, args[0], &opts) 125 } 126 return cmd 127 } 128 129 // `--merge` is deprecated in favor of `--merge-strategy`. However, merge has to keep working for now. 130 func determineMergeStrategy(deprecatedMergeFlag bool, mergeStrategy string) (tanka.ExportMergeStrategy, error) { 131 if deprecatedMergeFlag && mergeStrategy != "" { 132 return "", errors.New("cannot use --merge and --merge-strategy at the same time") 133 } 134 if deprecatedMergeFlag { 135 return tanka.ExportMergeStrategyFailConflicts, nil 136 } 137 138 switch strategy := tanka.ExportMergeStrategy(mergeStrategy); strategy { 139 case tanka.ExportMergeStrategyFailConflicts, tanka.ExportMergeStrategyReplaceEnvs, tanka.ExportMergeStrategyNone: 140 return strategy, nil 141 } 142 143 return "", fmt.Errorf("invalid merge strategy: %q", mergeStrategy) 144 }