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  }