github.com/helmwave/helmwave@v0.36.4-0.20240509190856-b35563eba4c6/pkg/action/build.go (about) 1 package action 2 3 import ( 4 "context" 5 "fmt" 6 "net/url" 7 "os" 8 "path/filepath" 9 "sort" 10 "strings" 11 12 "github.com/hashicorp/go-getter" 13 "github.com/helmwave/helmwave/pkg/cache" 14 "github.com/helmwave/helmwave/pkg/helper" 15 "github.com/helmwave/helmwave/pkg/plan" 16 log "github.com/sirupsen/logrus" 17 "github.com/urfave/cli/v2" 18 ) 19 20 var _ Action = (*Build)(nil) 21 22 // Build is a struct for running 'build' CLI command. 23 type Build struct { 24 yml *Yml 25 diff *Diff 26 options plan.BuildOptions 27 remoteSource string 28 plandir string 29 diffMode string 30 tags cli.StringSlice 31 autoYml bool 32 skipUnchanged bool 33 } 34 35 // Run is the main function for 'build' CLI command. 36 // 37 //nolint:funlen,gocognit,cyclop 38 func (i *Build) Run(ctx context.Context) (err error) { 39 wd, err := os.Getwd() 40 if err != nil { 41 return fmt.Errorf("failed to get current directory: %w", err) 42 } 43 44 if i.remoteSource != "" { 45 remoteSource, err := url.Parse(i.remoteSource) 46 if err != nil { 47 return fmt.Errorf("failed to parse remote source: %w", err) 48 } 49 50 downloadPath := cache.DefaultConfig.GetRemoteSourcePath(remoteSource) 51 err = getter.Get( 52 downloadPath, 53 i.remoteSource, 54 getter.WithContext(ctx), 55 getter.WithDetectors(getter.Detectors), 56 getter.WithGetters(getter.Getters), 57 getter.WithDecompressors(getter.Decompressors), 58 ) 59 if err != nil { 60 return fmt.Errorf("failed to download remote source: %w", err) 61 } 62 63 // we need to move plandir back to where it should be 64 defer func() { 65 srcPlandir := filepath.Join(downloadPath, i.plandir) 66 destPlandir := filepath.Join(wd, i.plandir) 67 err := os.RemoveAll(destPlandir) 68 if err != nil { 69 log.WithError(err).Error("failed to clean plandir") 70 } 71 err = helper.MoveFile(srcPlandir, destPlandir) 72 if err != nil { 73 log.WithError(err).Error("failed to move plandir") 74 } 75 }() 76 77 err = os.Chdir(downloadPath) 78 if err != nil { 79 return fmt.Errorf("failed to chdir to downloaded remote source: %w", err) 80 } 81 } 82 83 if i.autoYml { 84 err = i.yml.Run(ctx) 85 if err != nil { 86 return err 87 } 88 } 89 90 newPlan := plan.New(i.plandir) 91 92 i.options.Tags = i.normalizeTags() 93 i.options.Yml = i.yml.file 94 i.options.Templater = i.yml.templater 95 96 err = newPlan.Build(ctx, i.options) 97 if err != nil { 98 return err 99 } 100 101 // Show current plan 102 newPlan.Logger().Info("🏗 Plan") 103 104 switch i.diffMode { 105 case DiffModeLocal: 106 oldPlan := plan.New(i.plandir) 107 if oldPlan.IsExist() { 108 log.Info("🆚 Diff with previous local plan") 109 if err := oldPlan.Import(ctx); err != nil { 110 return err 111 } 112 113 newPlan.DiffPlan(oldPlan, i.diff.Options) 114 } 115 116 case DiffModeLive: 117 log.Info("🆚 Diff manifests in the kubernetes cluster") 118 newPlan.DiffLive(ctx, i.diff.Options, i.diff.ThreeWayMerge) 119 case DiffModeNone: 120 log.Info("🆚 Skip diffing") 121 default: 122 log.Warnf("🆚❔Unknown %q diff mode, skipping", i.diffMode) 123 } 124 125 err = newPlan.Export(ctx, i.skipUnchanged) 126 if err != nil { 127 return err 128 } 129 130 log.Info("🏗 Planfile is ready!") 131 132 return nil 133 } 134 135 // Cmd returns 'build' *cli.Command. 136 func (i *Build) Cmd() *cli.Command { 137 return &cli.Command{ 138 Name: "build", 139 Category: Step1, 140 Usage: "🏗 build a plan", 141 Flags: i.flags(), 142 Before: func(q *cli.Context) error { 143 i.diff.FixFields() 144 145 return nil 146 }, 147 Action: toCtx(i.Run), 148 } 149 } 150 151 // flags return flag set of CLI urfave. 152 func (i *Build) flags() []cli.Flag { 153 // Init sub-structures 154 i.yml = &Yml{} 155 i.diff = &Diff{} 156 157 self := []cli.Flag{ 158 flagPlandir(&i.plandir), 159 flagTags(&i.tags), 160 flagMatchAllTags(&i.options.MatchAll), 161 flagGraphWidth(&i.options.GraphWidth), 162 flagSkipUnchanged(&i.skipUnchanged), 163 flagDiffMode(&i.diffMode), 164 165 &cli.BoolFlag{ 166 Name: "yml", 167 Usage: "auto helmwave.yml.tpl --> helmwave.yml", 168 Value: false, 169 Category: "YML", 170 EnvVars: []string{"HELMWAVE_AUTO_YML", "HELMWAVE_AUTO_YAML"}, 171 Destination: &i.autoYml, 172 }, 173 &cli.StringFlag{ 174 Name: "remote-source", 175 Usage: "go-getter URL to download build sources", 176 Value: "", 177 Category: "BUILD", 178 EnvVars: []string{"HELMWAVE_REMOTE_SOURCE"}, 179 Destination: &i.remoteSource, 180 }, 181 } 182 183 self = append(self, i.diff.flags()...) 184 self = append(self, i.yml.flags()...) 185 186 return self 187 } 188 189 // normalizeTags is wrapper for normalizeTagList. 190 func (i *Build) normalizeTags() []string { 191 return normalizeTagList(i.tags.Value()) 192 } 193 194 // normalizeTags normalizes and splits comma-separated tag list. 195 // ["c", " b ", "a "] -> ["a", "b", "c"]. 196 func normalizeTagList(tags []string) []string { 197 m := helper.SlicesMap(tags, strings.TrimSpace) 198 sort.Strings(m) 199 200 return m 201 }