github.com/helmwave/helmwave@v0.36.4-0.20240509190856-b35563eba4c6/pkg/release/config.go (about)

     1  package release
     2  
     3  import (
     4  	"slices"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/helmwave/helmwave/pkg/hooks"
     9  	"github.com/helmwave/helmwave/pkg/release/uniqname"
    10  	log "github.com/sirupsen/logrus"
    11  	"helm.sh/helm/v3/pkg/chartutil"
    12  	helm "helm.sh/helm/v3/pkg/cli"
    13  	"helm.sh/helm/v3/pkg/postrender"
    14  )
    15  
    16  type configTests struct {
    17  	Filters       map[string][]string `yaml:"filters,omitempty" json:"filters,omitempty" jsonschema:"description=Filter tests by attributes,default={}"`
    18  	Enabled       bool                `yaml:"enabled,omitempty" json:"enabled,omitempty" jsonschema:"description=Whether to run helm tests,default=false"`
    19  	ForceShowLogs bool                `yaml:"force_show_logs,omitempty" json:"force_show_logs,omitempty" jsonschema:"description=Always show tests logs, not only if they failed,default=false"`
    20  }
    21  
    22  type config struct {
    23  	helm *helm.EnvSettings
    24  	log  *log.Entry
    25  
    26  	Lifecycle              hooks.Lifecycle `yaml:"lifecycle,omitempty" json:"lifecycle,omitempty" jsonschema:"description=Lifecycle hooks"`
    27  	Store                  map[string]any  `yaml:"store,omitempty" json:"store,omitempty" jsonschema:"title=The Store,description=It allows to pass your custom fields from helmwave.yml to values"`
    28  	ChartF                 Chart           `yaml:"chart,omitempty" json:"chart,omitempty" jsonschema:"title=Chart reference,description=Describes chart that release uses,oneof_type=string;object"`
    29  	Tests                  configTests     `yaml:"tests,omitempty" json:"tests,omitempty" jsonschema:"description=Configuration for helm tests"`
    30  	PendingReleaseStrategy PendingStrategy `yaml:"pending_release_strategy,omitempty" json:"pending_release_strategy,omitempty" jsonschema:"description=Strategy to handle releases in pending statuses (pending-install/pending-upgrade/pending-rollback)"`
    31  
    32  	uniqName            uniqname.UniqName
    33  	NameF               string `yaml:"name,omitempty" json:"name,omitempty" jsonschema:"required,title=Release name"`
    34  	NamespaceF          string `yaml:"namespace,omitempty" json:"namespace,omitempty" jsonschema:"required,title=Kubernetes namespace"`
    35  	DescriptionF        string `yaml:"description,omitempty" json:"description,omitempty" jsonschema:"default="`
    36  	OfflineKubeVersionF string `yaml:"offline_kube_version,omitempty" json:"offline_kube_version,omitempty" jsonschema:"description=Kubernetes version for offline mode"`
    37  	KubeContextF        string `yaml:"context,omitempty" json:"context,omitempty"`
    38  	DeletePropagation   string `yaml:"delete_propagation,omitempty" json:"delete_propagation,omitempty" jsonschema:"description=Selects the deletion cascading strategy for the dependents,enum=background,enum=orphan,enum=foreground,default=background"`
    39  
    40  	DependsOnF    []*DependsOnReference `yaml:"depends_on,omitempty" json:"depends_on,omitempty" jsonschema:"title=Needs,description=List of dependencies that are required to succeed before this release"`
    41  	MonitorsF     []MonitorReference    `yaml:"monitors,omitempty" json:"monitors,omitempty" jsonschema:"title=Monitors to execute after upgrade"`
    42  	ValuesF       []ValuesReference     `yaml:"values,omitempty" json:"values,omitempty" jsonschema:"title=Values of the release,oneof_type=string;object"`
    43  	TagsF         []string              `yaml:"tags,omitempty" json:"tags,omitempty" jsonschema:"description=Tags allows you choose releases for build"`
    44  	Labels        map[string]string     `yaml:"labels,omitempty" json:"labels,omitempty" jsonschema:"Labels that would be added to release metadata on sync"`
    45  	PostRendererF []string              `yaml:"post_renderer,omitempty" json:"post_renderer,omitempty" jsonschema:"description=List of post_renders to manipulate with manifests"`
    46  	Timeout       time.Duration         `yaml:"timeout,omitempty" json:"timeout,omitempty" jsonschema:"oneof_type=string;integer,default=5m"`
    47  
    48  	// Lock for parallel testing
    49  	lock sync.RWMutex
    50  
    51  	MaxHistory               int  `yaml:"max_history,omitempty" json:"max_history,omitempty" jsonschema:"default=0"`
    52  	AllowFailureF            bool `yaml:"allow_failure,omitempty" json:"allow_failure,omitempty" jsonschema:"description=Whether to ignore errors and proceed with dependant releases,default=false"`
    53  	Atomic                   bool `yaml:"atomic,omitempty" json:"atomic,omitempty" jsonschema:"default=false"`
    54  	CleanupOnFail            bool `yaml:"cleanup_on_fail,omitempty" json:"cleanup_on_fail,omitempty" jsonschema:"default=false"`
    55  	CreateNamespace          bool `yaml:"create_namespace,omitempty" json:"create_namespace,omitempty" jsonschema:"description=Whether to create namespace if it doesnt exits,default=false"`
    56  	DisableHooks             bool `yaml:"disable_hooks,omitempty" json:"disable_hooks,omitempty" jsonschema:"default=false"`
    57  	DisableOpenAPIValidation bool `yaml:"disable_open_api_validation,omitempty" json:"disable_open_api_validation,omitempty" jsonschema:"default=false"`
    58  	EnableDNS                bool `yaml:"enable_dns,omitempty" json:"enable_dns,omitempty" jsonschema:"default=false"`
    59  	Force                    bool `yaml:"force,omitempty" json:"force,omitempty" jsonschema:"default=false"`
    60  	Recreate                 bool `yaml:"recreate,omitempty" json:"recreate,omitempty" jsonschema:"default=false"`
    61  	ResetValues              bool `yaml:"reset_values,omitempty" json:"reset_values,omitempty" jsonschema:"default=false"`
    62  	ReuseValues              bool `yaml:"reuse_values,omitempty" json:"reuse_values,omitempty" jsonschema:"default=false"`
    63  	ResetThenReuseValues     bool `yaml:"reset_then_reuse_values,omitempty" json:"reset_then_reuse_values,omitempty" jsonschema:"default=false"`
    64  	SkipCRDs                 bool `yaml:"skip_crds,omitempty" json:"skip_crds,omitempty" jsonschema:"default=false"`
    65  	ShowNotes                bool `yaml:"show_notes,omitempty" json:"show_notes,omitempty" jsonschema:"description=Output rendered chart notes after upgrade/install"`
    66  	SubNotes                 bool `yaml:"sub_notes,omitempty" json:"sub_notes,omitempty" jsonschema:"default=false"`
    67  	Wait                     bool `yaml:"wait,omitempty" json:"wait,omitempty" jsonschema:"description=Whether to wait for all resource to become ready,default=false"`
    68  	WaitForJobs              bool `yaml:"wait_for_jobs,omitempty" json:"wait_for_jobs,omitempty" jsonschema:"description=Whether to wait for all jobs to become ready,default=false"`
    69  
    70  	// special field for templating and building
    71  	dryRun bool `jsonschema:"default=false,-"`
    72  }
    73  
    74  func (rel *config) DryRun(b bool) {
    75  	rel.dryRun = b
    76  }
    77  
    78  // Uniq like redis@my-namespace.
    79  func (rel *config) Uniq() uniqname.UniqName {
    80  	if rel.uniqName == "" {
    81  		var err error
    82  		rel.uniqName, err = uniqname.Generate(rel.Name(), rel.Namespace())
    83  		if err != nil {
    84  			rel.Logger().WithFields(log.Fields{
    85  				"name":       rel.Name(),
    86  				"namespace":  rel.Namespace(),
    87  				log.ErrorKey: err,
    88  			}).Error("failed to generate valid uniqname")
    89  		}
    90  	}
    91  
    92  	return rel.uniqName
    93  }
    94  
    95  func (rel *config) Equal(a Config) bool {
    96  	return rel.Uniq().Equal(a.Uniq())
    97  }
    98  
    99  func (rel *config) Name() string {
   100  	return rel.NameF
   101  }
   102  
   103  func (rel *config) Namespace() string {
   104  	return rel.NamespaceF
   105  }
   106  
   107  func (rel *config) Description() string {
   108  	return rel.DescriptionF
   109  }
   110  
   111  func (rel *config) Chart() *Chart {
   112  	rel.lock.RLock()
   113  	defer rel.lock.RUnlock()
   114  
   115  	return &rel.ChartF
   116  }
   117  
   118  func (rel *config) DependsOn() []*DependsOnReference {
   119  	rel.lock.RLock()
   120  	defer rel.lock.RUnlock()
   121  
   122  	return rel.DependsOnF
   123  }
   124  
   125  func (rel *config) SetDependsOn(deps []*DependsOnReference) {
   126  	rel.lock.Lock()
   127  	defer rel.lock.Unlock()
   128  
   129  	rel.DependsOnF = deps
   130  }
   131  
   132  func (rel *config) Tags() []string {
   133  	return rel.TagsF
   134  }
   135  
   136  func (rel *config) Values() []ValuesReference {
   137  	return rel.ValuesF
   138  }
   139  
   140  func (rel *config) Logger() *log.Entry {
   141  	if rel.log == nil {
   142  		rel.log = log.WithField("release", rel.Uniq())
   143  	}
   144  
   145  	return rel.log
   146  }
   147  
   148  func (rel *config) AllowFailure() bool {
   149  	return rel.AllowFailureF
   150  }
   151  
   152  func (rel *config) HelmWait() bool {
   153  	return rel.Wait
   154  }
   155  
   156  func (rel *config) buildAfterUnmarshal(allReleases []*config) {
   157  	rel.buildAfterUnmarshalDependsOn(allReleases)
   158  
   159  	// set default timeout
   160  	if rel.Timeout <= 0 {
   161  		rel.Logger().Debug("timeout is not set, defaulting to 5m")
   162  		rel.Timeout = 5 * time.Minute
   163  	}
   164  }
   165  
   166  func (rel *config) buildAfterUnmarshalDependsOn(allReleases []*config) {
   167  	newDeps := make([]*DependsOnReference, 0)
   168  
   169  	for _, dep := range rel.DependsOn() {
   170  		l := rel.Logger().WithField("dependency", dep)
   171  		switch dep.Type() {
   172  		case DependencyRelease:
   173  			err := rel.buildAfterUnmarshalDependency(dep)
   174  			if err == nil {
   175  				newDeps = append(newDeps, dep)
   176  			}
   177  		case DependencyTag:
   178  			for _, r := range allReleases {
   179  				if !slices.Contains(r.Tags(), dep.Tag) {
   180  					continue
   181  				}
   182  
   183  				newDep := &DependsOnReference{
   184  					Name:     r.Uniq().String(),
   185  					Optional: dep.Optional,
   186  				}
   187  				newDeps = append(newDeps, newDep)
   188  			}
   189  		case DependencyInvalid:
   190  			l.Warn("invalid dependency, skipping")
   191  		}
   192  	}
   193  
   194  	rel.lock.Lock()
   195  	rel.DependsOnF = newDeps
   196  	rel.lock.Unlock()
   197  }
   198  
   199  func (rel *config) buildAfterUnmarshalDependency(dep *DependsOnReference) error {
   200  	u, err := uniqname.GenerateWithDefaultNamespace(dep.Name, rel.Namespace())
   201  	if err != nil {
   202  		rel.Logger().WithField("dependency", dep).WithError(err).Error("can't parse dependency")
   203  
   204  		return err
   205  	}
   206  
   207  	// generate full uniqname string if it was short
   208  	dep.Name = u.String()
   209  
   210  	return nil
   211  }
   212  
   213  func (rel *config) PostRenderer() (postrender.PostRenderer, error) {
   214  	if len(rel.PostRendererF) < 1 {
   215  		return nil, nil
   216  	}
   217  
   218  	return postrender.NewExec(rel.PostRendererF[0], rel.PostRendererF[1:]...) //nolint:wrapcheck
   219  }
   220  
   221  func (rel *config) KubeContext() string {
   222  	return rel.KubeContextF
   223  }
   224  
   225  // MarshalYAML is a marshaller for gopkg.in/yaml.v3.
   226  // It is required to avoid data race with getting read lock.
   227  func (rel *config) MarshalYAML() (any, error) {
   228  	rel.lock.RLock()
   229  	defer rel.lock.RUnlock()
   230  
   231  	type raw config
   232  	r := raw(*rel) //nolint:govet
   233  
   234  	return r, nil //nolint:govet
   235  }
   236  
   237  func (rel *config) HooksDisabled() bool {
   238  	return rel.DisableHooks
   239  }
   240  
   241  func (rel *config) OfflineKubeVersion() *chartutil.KubeVersion {
   242  	if rel.OfflineKubeVersionF != "" {
   243  		v, err := chartutil.ParseKubeVersion(rel.OfflineKubeVersionF)
   244  		if err != nil {
   245  			log.Fatalf("invalid kube version %q: %s", rel.OfflineKubeVersionF, err)
   246  
   247  			return nil
   248  		}
   249  
   250  		return v
   251  	}
   252  
   253  	return nil
   254  }
   255  
   256  func (rel *config) Monitors() []MonitorReference {
   257  	rel.lock.RLock()
   258  	defer rel.lock.RUnlock()
   259  
   260  	return rel.MonitorsF
   261  }