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 }