1 /* 2 Copyright 2020 The Flux authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package v2beta1 18 19 import ( 20 "encoding/json" 21 "strings" 22 "time" 23 24 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 25 apimeta "k8s.io/apimachinery/pkg/api/meta" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/types" 28 29 "github.com/fluxcd/pkg/apis/kustomize" 30 "github.com/fluxcd/pkg/apis/meta" 31 32 v2 "github.com/fluxcd/helm-controller/api/v2" 33 "github.com/fluxcd/helm-controller/api/v2beta2" 34 ) 35 36 const HelmReleaseKind = "HelmRelease" 37 const HelmReleaseFinalizer = "finalizers.fluxcd.io" 38 39 // Kustomize Helm PostRenderer specification. 40 type Kustomize struct { 41 // Strategic merge and JSON patches, defined as inline YAML objects, 42 // capable of targeting objects based on kind, label and annotation selectors. 43 // +optional 44 Patches []kustomize.Patch `json:"patches,omitempty"` 45 46 // Strategic merge patches, defined as inline YAML objects. 47 // +optional 48 PatchesStrategicMerge []apiextensionsv1.JSON `json:"patchesStrategicMerge,omitempty"` 49 50 // JSON 6902 patches, defined as inline YAML objects. 51 // +optional 52 PatchesJSON6902 []kustomize.JSON6902Patch `json:"patchesJson6902,omitempty"` 53 54 // Images is a list of (image name, new name, new tag or digest) 55 // for changing image names, tags or digests. This can also be achieved with a 56 // patch, but this operator is simpler to specify. 57 // +optional 58 Images []kustomize.Image `json:"images,omitempty" yaml:"images,omitempty"` 59 } 60 61 // PostRenderer contains a Helm PostRenderer specification. 62 type PostRenderer struct { 63 // Kustomization to apply as PostRenderer. 64 // +optional 65 Kustomize *Kustomize `json:"kustomize,omitempty"` 66 } 67 68 // HelmReleaseSpec defines the desired state of a Helm release. 69 type HelmReleaseSpec struct { 70 // Chart defines the template of the v1beta2.HelmChart that should be created 71 // for this HelmRelease. 72 // +required 73 Chart *HelmChartTemplate `json:"chart,omitempty"` 74 75 // ChartRef holds a reference to a source controller resource containing the 76 // Helm chart artifact. 77 // 78 // Note: this field is provisional to the v2 API, and not actively used 79 // by v2beta1 HelmReleases. 80 // +optional 81 ChartRef *v2.CrossNamespaceSourceReference `json:"chartRef,omitempty"` 82 83 // Interval at which to reconcile the Helm release. 84 // This interval is approximate and may be subject to jitter to ensure 85 // efficient use of resources. 86 // +kubebuilder:validation:Type=string 87 // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" 88 // +required 89 Interval metav1.Duration `json:"interval"` 90 91 // KubeConfig for reconciling the HelmRelease on a remote cluster. 92 // When used in combination with HelmReleaseSpec.ServiceAccountName, 93 // forces the controller to act on behalf of that Service Account at the 94 // target cluster. 95 // If the --default-service-account flag is set, its value will be used as 96 // a controller level fallback for when HelmReleaseSpec.ServiceAccountName 97 // is empty. 98 // +optional 99 KubeConfig *meta.KubeConfigReference `json:"kubeConfig,omitempty"` 100 101 // Suspend tells the controller to suspend reconciliation for this HelmRelease, 102 // it does not apply to already started reconciliations. Defaults to false. 103 // +optional 104 Suspend bool `json:"suspend,omitempty"` 105 106 // ReleaseName used for the Helm release. Defaults to a composition of 107 // '[TargetNamespace-]Name'. 108 // +kubebuilder:validation:MinLength=1 109 // +kubebuilder:validation:MaxLength=53 110 // +kubebuilder:validation:Optional 111 // +optional 112 ReleaseName string `json:"releaseName,omitempty"` 113 114 // TargetNamespace to target when performing operations for the HelmRelease. 115 // Defaults to the namespace of the HelmRelease. 116 // +kubebuilder:validation:MinLength=1 117 // +kubebuilder:validation:MaxLength=63 118 // +kubebuilder:validation:Optional 119 // +optional 120 TargetNamespace string `json:"targetNamespace,omitempty"` 121 122 // StorageNamespace used for the Helm storage. 123 // Defaults to the namespace of the HelmRelease. 124 // +kubebuilder:validation:MinLength=1 125 // +kubebuilder:validation:MaxLength=63 126 // +kubebuilder:validation:Optional 127 // +optional 128 StorageNamespace string `json:"storageNamespace,omitempty"` 129 130 // DependsOn may contain a meta.NamespacedObjectReference slice with 131 // references to HelmRelease resources that must be ready before this HelmRelease 132 // can be reconciled. 133 // +optional 134 DependsOn []meta.NamespacedObjectReference `json:"dependsOn,omitempty"` 135 136 // Timeout is the time to wait for any individual Kubernetes operation (like Jobs 137 // for hooks) during the performance of a Helm action. Defaults to '5m0s'. 138 // +kubebuilder:validation:Type=string 139 // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" 140 // +optional 141 Timeout *metav1.Duration `json:"timeout,omitempty"` 142 143 // MaxHistory is the number of revisions saved by Helm for this HelmRelease. 144 // Use '0' for an unlimited number of revisions; defaults to '10'. 145 // +optional 146 MaxHistory *int `json:"maxHistory,omitempty"` 147 148 // The name of the Kubernetes service account to impersonate 149 // when reconciling this HelmRelease. 150 // +optional 151 ServiceAccountName string `json:"serviceAccountName,omitempty"` 152 153 // PersistentClient tells the controller to use a persistent Kubernetes 154 // client for this release. When enabled, the client will be reused for the 155 // duration of the reconciliation, instead of being created and destroyed 156 // for each (step of a) Helm action. 157 // 158 // This can improve performance, but may cause issues with some Helm charts 159 // that for example do create Custom Resource Definitions during installation 160 // outside Helm's CRD lifecycle hooks, which are then not observed to be 161 // available by e.g. post-install hooks. 162 // 163 // If not set, it defaults to true. 164 // 165 // +optional 166 PersistentClient *bool `json:"persistentClient,omitempty"` 167 168 // DriftDetection holds the configuration for detecting and handling 169 // differences between the manifest in the Helm storage and the resources 170 // currently existing in the cluster. 171 // 172 // Note: this field is provisional to the v2beta2 API, and not actively used 173 // by v2beta1 HelmReleases. 174 // +optional 175 DriftDetection *v2beta2.DriftDetection `json:"driftDetection,omitempty"` 176 177 // Install holds the configuration for Helm install actions for this HelmRelease. 178 // +optional 179 Install *Install `json:"install,omitempty"` 180 181 // Upgrade holds the configuration for Helm upgrade actions for this HelmRelease. 182 // +optional 183 Upgrade *Upgrade `json:"upgrade,omitempty"` 184 185 // Test holds the configuration for Helm test actions for this HelmRelease. 186 // +optional 187 Test *Test `json:"test,omitempty"` 188 189 // Rollback holds the configuration for Helm rollback actions for this HelmRelease. 190 // +optional 191 Rollback *Rollback `json:"rollback,omitempty"` 192 193 // Uninstall holds the configuration for Helm uninstall actions for this HelmRelease. 194 // +optional 195 Uninstall *Uninstall `json:"uninstall,omitempty"` 196 197 // ValuesFrom holds references to resources containing Helm values for this HelmRelease, 198 // and information about how they should be merged. 199 ValuesFrom []ValuesReference `json:"valuesFrom,omitempty"` 200 201 // Values holds the values for this Helm release. 202 // +optional 203 Values *apiextensionsv1.JSON `json:"values,omitempty"` 204 205 // PostRenderers holds an array of Helm PostRenderers, which will be applied in order 206 // of their definition. 207 // +optional 208 PostRenderers []PostRenderer `json:"postRenderers,omitempty"` 209 } 210 211 // GetInstall returns the configuration for Helm install actions for the 212 // HelmRelease. 213 func (in HelmReleaseSpec) GetInstall() Install { 214 if in.Install == nil { 215 return Install{} 216 } 217 return *in.Install 218 } 219 220 // GetUpgrade returns the configuration for Helm upgrade actions for this 221 // HelmRelease. 222 func (in HelmReleaseSpec) GetUpgrade() Upgrade { 223 if in.Upgrade == nil { 224 return Upgrade{} 225 } 226 return *in.Upgrade 227 } 228 229 // GetTest returns the configuration for Helm test actions for this HelmRelease. 230 func (in HelmReleaseSpec) GetTest() Test { 231 if in.Test == nil { 232 return Test{} 233 } 234 return *in.Test 235 } 236 237 // GetRollback returns the configuration for Helm rollback actions for this 238 // HelmRelease. 239 func (in HelmReleaseSpec) GetRollback() Rollback { 240 if in.Rollback == nil { 241 return Rollback{} 242 } 243 return *in.Rollback 244 } 245 246 // GetUninstall returns the configuration for Helm uninstall actions for this 247 // HelmRelease. 248 func (in HelmReleaseSpec) GetUninstall() Uninstall { 249 if in.Uninstall == nil { 250 return Uninstall{} 251 } 252 return *in.Uninstall 253 } 254 255 // HelmChartTemplate defines the template from which the controller will 256 // generate a v1beta2.HelmChart object in the same namespace as the referenced 257 // v1beta2.Source. 258 type HelmChartTemplate struct { 259 // ObjectMeta holds the template for metadata like labels and annotations. 260 // +optional 261 ObjectMeta *HelmChartTemplateObjectMeta `json:"metadata,omitempty"` 262 263 // Spec holds the template for the v1beta2.HelmChartSpec for this HelmRelease. 264 // +required 265 Spec HelmChartTemplateSpec `json:"spec"` 266 } 267 268 // HelmChartTemplateObjectMeta defines the template for the ObjectMeta of a 269 // v1beta2.HelmChart. 270 type HelmChartTemplateObjectMeta struct { 271 // Map of string keys and values that can be used to organize and categorize 272 // (scope and select) objects. 273 // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ 274 // +optional 275 Labels map[string]string `json:"labels,omitempty"` 276 277 // Annotations is an unstructured key value map stored with a resource that may be 278 // set by external tools to store and retrieve arbitrary metadata. They are not 279 // queryable and should be preserved when modifying objects. 280 // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ 281 // +optional 282 Annotations map[string]string `json:"annotations,omitempty"` 283 } 284 285 // HelmChartTemplateSpec defines the template from which the controller will 286 // generate a v1beta2.HelmChartSpec object. 287 type HelmChartTemplateSpec struct { 288 // The name or path the Helm chart is available at in the SourceRef. 289 // +required 290 Chart string `json:"chart"` 291 292 // Version semver expression, ignored for charts from v1beta2.GitRepository and 293 // v1beta2.Bucket sources. Defaults to latest when omitted. 294 // +kubebuilder:default:=* 295 // +optional 296 Version string `json:"version,omitempty"` 297 298 // The name and namespace of the v1beta2.Source the chart is available at. 299 // +required 300 SourceRef CrossNamespaceObjectReference `json:"sourceRef"` 301 302 // Interval at which to check the v1beta2.Source for updates. Defaults to 303 // 'HelmReleaseSpec.Interval'. 304 // +kubebuilder:validation:Type=string 305 // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" 306 // +optional 307 Interval *metav1.Duration `json:"interval,omitempty"` 308 309 // Determines what enables the creation of a new artifact. Valid values are 310 // ('ChartVersion', 'Revision'). 311 // See the documentation of the values for an explanation on their behavior. 312 // Defaults to ChartVersion when omitted. 313 // +kubebuilder:validation:Enum=ChartVersion;Revision 314 // +kubebuilder:default:=ChartVersion 315 // +optional 316 ReconcileStrategy string `json:"reconcileStrategy,omitempty"` 317 318 // Alternative list of values files to use as the chart values (values.yaml 319 // is not included by default), expected to be a relative path in the SourceRef. 320 // Values files are merged in the order of this list with the last file overriding 321 // the first. Ignored when omitted. 322 // +optional 323 ValuesFiles []string `json:"valuesFiles,omitempty"` 324 325 // Alternative values file to use as the default chart values, expected to 326 // be a relative path in the SourceRef. Deprecated in favor of ValuesFiles, 327 // for backwards compatibility the file defined here is merged before the 328 // ValuesFiles items. Ignored when omitted. 329 // +optional 330 // +deprecated 331 ValuesFile string `json:"valuesFile,omitempty"` 332 333 // Verify contains the secret name containing the trusted public keys 334 // used to verify the signature and specifies which provider to use to check 335 // whether OCI image is authentic. 336 // This field is only supported for OCI sources. 337 // Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified. 338 // +optional 339 Verify *HelmChartTemplateVerification `json:"verify,omitempty"` 340 } 341 342 // GetInterval returns the configured interval for the v1beta2.HelmChart, 343 // or the given default. 344 func (in HelmChartTemplate) GetInterval(defaultInterval metav1.Duration) metav1.Duration { 345 if in.Spec.Interval == nil { 346 return defaultInterval 347 } 348 return *in.Spec.Interval 349 } 350 351 // GetNamespace returns the namespace targeted namespace for the 352 // v1beta2.HelmChart, or the given default. 353 func (in HelmChartTemplate) GetNamespace(defaultNamespace string) string { 354 if in.Spec.SourceRef.Namespace == "" { 355 return defaultNamespace 356 } 357 return in.Spec.SourceRef.Namespace 358 } 359 360 // HelmChartTemplateVerification verifies the authenticity of an OCI Helm chart. 361 type HelmChartTemplateVerification struct { 362 // Provider specifies the technology used to sign the OCI Helm chart. 363 // +kubebuilder:validation:Enum=cosign 364 // +kubebuilder:default:=cosign 365 Provider string `json:"provider"` 366 367 // SecretRef specifies the Kubernetes Secret containing the 368 // trusted public keys. 369 // +optional 370 SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"` 371 } 372 373 // DeploymentAction defines a consistent interface for Install and Upgrade. 374 // +kubebuilder:object:generate=false 375 type DeploymentAction interface { 376 GetDescription() string 377 GetRemediation() Remediation 378 } 379 380 // Remediation defines a consistent interface for InstallRemediation and 381 // UpgradeRemediation. 382 // +kubebuilder:object:generate=false 383 type Remediation interface { 384 GetRetries() int 385 MustIgnoreTestFailures(bool) bool 386 MustRemediateLastFailure() bool 387 GetStrategy() RemediationStrategy 388 GetFailureCount(hr HelmRelease) int64 389 IncrementFailureCount(hr *HelmRelease) 390 RetriesExhausted(hr HelmRelease) bool 391 } 392 393 // Install holds the configuration for Helm install actions performed for this 394 // HelmRelease. 395 type Install struct { 396 // Timeout is the time to wait for any individual Kubernetes operation (like 397 // Jobs for hooks) during the performance of a Helm install action. Defaults to 398 // 'HelmReleaseSpec.Timeout'. 399 // +kubebuilder:validation:Type=string 400 // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" 401 // +optional 402 Timeout *metav1.Duration `json:"timeout,omitempty"` 403 404 // Remediation holds the remediation configuration for when the Helm install 405 // action for the HelmRelease fails. The default is to not perform any action. 406 // +optional 407 Remediation *InstallRemediation `json:"remediation,omitempty"` 408 409 // DisableWait disables the waiting for resources to be ready after a Helm 410 // install has been performed. 411 // +optional 412 DisableWait bool `json:"disableWait,omitempty"` 413 414 // DisableWaitForJobs disables waiting for jobs to complete after a Helm 415 // install has been performed. 416 // +optional 417 DisableWaitForJobs bool `json:"disableWaitForJobs,omitempty"` 418 419 // DisableHooks prevents hooks from running during the Helm install action. 420 // +optional 421 DisableHooks bool `json:"disableHooks,omitempty"` 422 423 // DisableOpenAPIValidation prevents the Helm install action from validating 424 // rendered templates against the Kubernetes OpenAPI Schema. 425 // +optional 426 DisableOpenAPIValidation bool `json:"disableOpenAPIValidation,omitempty"` 427 428 // Replace tells the Helm install action to re-use the 'ReleaseName', but only 429 // if that name is a deleted release which remains in the history. 430 // +optional 431 Replace bool `json:"replace,omitempty"` 432 433 // SkipCRDs tells the Helm install action to not install any CRDs. By default, 434 // CRDs are installed if not already present. 435 // 436 // Deprecated use CRD policy (`crds`) attribute with value `Skip` instead. 437 // 438 // +deprecated 439 // +optional 440 SkipCRDs bool `json:"skipCRDs,omitempty"` 441 442 // CRDs upgrade CRDs from the Helm Chart's crds directory according 443 // to the CRD upgrade policy provided here. Valid values are `Skip`, 444 // `Create` or `CreateReplace`. Default is `Create` and if omitted 445 // CRDs are installed but not updated. 446 // 447 // Skip: do neither install nor replace (update) any CRDs. 448 // 449 // Create: new CRDs are created, existing CRDs are neither updated nor deleted. 450 // 451 // CreateReplace: new CRDs are created, existing CRDs are updated (replaced) 452 // but not deleted. 453 // 454 // By default, CRDs are applied (installed) during Helm install action. 455 // With this option users can opt-in to CRD replace existing CRDs on Helm 456 // install actions, which is not (yet) natively supported by Helm. 457 // https://helm.sh/docs/chart_best_practices/custom_resource_definitions. 458 // 459 // +kubebuilder:validation:Enum=Skip;Create;CreateReplace 460 // +optional 461 CRDs CRDsPolicy `json:"crds,omitempty"` 462 463 // CreateNamespace tells the Helm install action to create the 464 // HelmReleaseSpec.TargetNamespace if it does not exist yet. 465 // On uninstall, the namespace will not be garbage collected. 466 // +optional 467 CreateNamespace bool `json:"createNamespace,omitempty"` 468 } 469 470 // GetTimeout returns the configured timeout for the Helm install action, 471 // or the given default. 472 func (in Install) GetTimeout(defaultTimeout metav1.Duration) metav1.Duration { 473 if in.Timeout == nil { 474 return defaultTimeout 475 } 476 return *in.Timeout 477 } 478 479 // GetDescription returns a description for the Helm install action. 480 func (in Install) GetDescription() string { 481 return "install" 482 } 483 484 // GetRemediation returns the configured Remediation for the Helm install action. 485 func (in Install) GetRemediation() Remediation { 486 if in.Remediation == nil { 487 return InstallRemediation{} 488 } 489 return *in.Remediation 490 } 491 492 // InstallRemediation holds the configuration for Helm install remediation. 493 type InstallRemediation struct { 494 // Retries is the number of retries that should be attempted on failures before 495 // bailing. Remediation, using an uninstall, is performed between each attempt. 496 // Defaults to '0', a negative integer equals to unlimited retries. 497 // +optional 498 Retries int `json:"retries,omitempty"` 499 500 // IgnoreTestFailures tells the controller to skip remediation when the Helm 501 // tests are run after an install action but fail. Defaults to 502 // 'Test.IgnoreFailures'. 503 // +optional 504 IgnoreTestFailures *bool `json:"ignoreTestFailures,omitempty"` 505 506 // RemediateLastFailure tells the controller to remediate the last failure, when 507 // no retries remain. Defaults to 'false'. 508 // +optional 509 RemediateLastFailure *bool `json:"remediateLastFailure,omitempty"` 510 } 511 512 // GetRetries returns the number of retries that should be attempted on 513 // failures. 514 func (in InstallRemediation) GetRetries() int { 515 return in.Retries 516 } 517 518 // MustIgnoreTestFailures returns the configured IgnoreTestFailures or the given 519 // default. 520 func (in InstallRemediation) MustIgnoreTestFailures(def bool) bool { 521 if in.IgnoreTestFailures == nil { 522 return def 523 } 524 return *in.IgnoreTestFailures 525 } 526 527 // MustRemediateLastFailure returns whether to remediate the last failure when 528 // no retries remain. 529 func (in InstallRemediation) MustRemediateLastFailure() bool { 530 if in.RemediateLastFailure == nil { 531 return false 532 } 533 return *in.RemediateLastFailure 534 } 535 536 // GetStrategy returns the strategy to use for failure remediation. 537 func (in InstallRemediation) GetStrategy() RemediationStrategy { 538 return UninstallRemediationStrategy 539 } 540 541 // GetFailureCount gets the failure count. 542 func (in InstallRemediation) GetFailureCount(hr HelmRelease) int64 { 543 return hr.Status.InstallFailures 544 } 545 546 // IncrementFailureCount increments the failure count. 547 func (in InstallRemediation) IncrementFailureCount(hr *HelmRelease) { 548 hr.Status.InstallFailures++ 549 } 550 551 // RetriesExhausted returns true if there are no remaining retries. 552 func (in InstallRemediation) RetriesExhausted(hr HelmRelease) bool { 553 return in.Retries >= 0 && in.GetFailureCount(hr) > int64(in.Retries) 554 } 555 556 // CRDsPolicy defines the install/upgrade approach to use for CRDs when 557 // installing or upgrading a HelmRelease. 558 type CRDsPolicy string 559 560 const ( 561 // Skip CRDs do neither install nor replace (update) any CRDs. 562 Skip CRDsPolicy = "Skip" 563 // Create CRDs which do not already exist, do not replace (update) already existing 564 // CRDs and keep (do not delete) CRDs which no longer exist in the current release. 565 Create CRDsPolicy = "Create" 566 // Create CRDs which do not already exist, Replace (update) already existing CRDs 567 // and keep (do not delete) CRDs which no longer exist in the current release. 568 CreateReplace CRDsPolicy = "CreateReplace" 569 ) 570 571 // Upgrade holds the configuration for Helm upgrade actions for this 572 // HelmRelease. 573 type Upgrade struct { 574 // Timeout is the time to wait for any individual Kubernetes operation (like 575 // Jobs for hooks) during the performance of a Helm upgrade action. Defaults to 576 // 'HelmReleaseSpec.Timeout'. 577 // +kubebuilder:validation:Type=string 578 // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" 579 // +optional 580 Timeout *metav1.Duration `json:"timeout,omitempty"` 581 582 // Remediation holds the remediation configuration for when the Helm upgrade 583 // action for the HelmRelease fails. The default is to not perform any action. 584 // +optional 585 Remediation *UpgradeRemediation `json:"remediation,omitempty"` 586 587 // DisableWait disables the waiting for resources to be ready after a Helm 588 // upgrade has been performed. 589 // +optional 590 DisableWait bool `json:"disableWait,omitempty"` 591 592 // DisableWaitForJobs disables waiting for jobs to complete after a Helm 593 // upgrade has been performed. 594 // +optional 595 DisableWaitForJobs bool `json:"disableWaitForJobs,omitempty"` 596 597 // DisableHooks prevents hooks from running during the Helm upgrade action. 598 // +optional 599 DisableHooks bool `json:"disableHooks,omitempty"` 600 601 // DisableOpenAPIValidation prevents the Helm upgrade action from validating 602 // rendered templates against the Kubernetes OpenAPI Schema. 603 // +optional 604 DisableOpenAPIValidation bool `json:"disableOpenAPIValidation,omitempty"` 605 606 // Force forces resource updates through a replacement strategy. 607 // +optional 608 Force bool `json:"force,omitempty"` 609 610 // PreserveValues will make Helm reuse the last release's values and merge in 611 // overrides from 'Values'. Setting this flag makes the HelmRelease 612 // non-declarative. 613 // +optional 614 PreserveValues bool `json:"preserveValues,omitempty"` 615 616 // CleanupOnFail allows deletion of new resources created during the Helm 617 // upgrade action when it fails. 618 // +optional 619 CleanupOnFail bool `json:"cleanupOnFail,omitempty"` 620 621 // CRDs upgrade CRDs from the Helm Chart's crds directory according 622 // to the CRD upgrade policy provided here. Valid values are `Skip`, 623 // `Create` or `CreateReplace`. Default is `Skip` and if omitted 624 // CRDs are neither installed nor upgraded. 625 // 626 // Skip: do neither install nor replace (update) any CRDs. 627 // 628 // Create: new CRDs are created, existing CRDs are neither updated nor deleted. 629 // 630 // CreateReplace: new CRDs are created, existing CRDs are updated (replaced) 631 // but not deleted. 632 // 633 // By default, CRDs are not applied during Helm upgrade action. With this 634 // option users can opt-in to CRD upgrade, which is not (yet) natively supported by Helm. 635 // https://helm.sh/docs/chart_best_practices/custom_resource_definitions. 636 // 637 // +kubebuilder:validation:Enum=Skip;Create;CreateReplace 638 // +optional 639 CRDs CRDsPolicy `json:"crds,omitempty"` 640 } 641 642 // GetTimeout returns the configured timeout for the Helm upgrade action, or the 643 // given default. 644 func (in Upgrade) GetTimeout(defaultTimeout metav1.Duration) metav1.Duration { 645 if in.Timeout == nil { 646 return defaultTimeout 647 } 648 return *in.Timeout 649 } 650 651 // GetDescription returns a description for the Helm upgrade action. 652 func (in Upgrade) GetDescription() string { 653 return "upgrade" 654 } 655 656 // GetRemediation returns the configured Remediation for the Helm upgrade 657 // action. 658 func (in Upgrade) GetRemediation() Remediation { 659 if in.Remediation == nil { 660 return UpgradeRemediation{} 661 } 662 return *in.Remediation 663 } 664 665 // UpgradeRemediation holds the configuration for Helm upgrade remediation. 666 type UpgradeRemediation struct { 667 // Retries is the number of retries that should be attempted on failures before 668 // bailing. Remediation, using 'Strategy', is performed between each attempt. 669 // Defaults to '0', a negative integer equals to unlimited retries. 670 // +optional 671 Retries int `json:"retries,omitempty"` 672 673 // IgnoreTestFailures tells the controller to skip remediation when the Helm 674 // tests are run after an upgrade action but fail. 675 // Defaults to 'Test.IgnoreFailures'. 676 // +optional 677 IgnoreTestFailures *bool `json:"ignoreTestFailures,omitempty"` 678 679 // RemediateLastFailure tells the controller to remediate the last failure, when 680 // no retries remain. Defaults to 'false' unless 'Retries' is greater than 0. 681 // +optional 682 RemediateLastFailure *bool `json:"remediateLastFailure,omitempty"` 683 684 // Strategy to use for failure remediation. Defaults to 'rollback'. 685 // +kubebuilder:validation:Enum=rollback;uninstall 686 // +optional 687 Strategy *RemediationStrategy `json:"strategy,omitempty"` 688 } 689 690 // GetRetries returns the number of retries that should be attempted on 691 // failures. 692 func (in UpgradeRemediation) GetRetries() int { 693 return in.Retries 694 } 695 696 // MustIgnoreTestFailures returns the configured IgnoreTestFailures or the given 697 // default. 698 func (in UpgradeRemediation) MustIgnoreTestFailures(def bool) bool { 699 if in.IgnoreTestFailures == nil { 700 return def 701 } 702 return *in.IgnoreTestFailures 703 } 704 705 // MustRemediateLastFailure returns whether to remediate the last failure when 706 // no retries remain. 707 func (in UpgradeRemediation) MustRemediateLastFailure() bool { 708 if in.RemediateLastFailure == nil { 709 return in.Retries > 0 710 } 711 return *in.RemediateLastFailure 712 } 713 714 // GetStrategy returns the strategy to use for failure remediation. 715 func (in UpgradeRemediation) GetStrategy() RemediationStrategy { 716 if in.Strategy == nil { 717 return RollbackRemediationStrategy 718 } 719 return *in.Strategy 720 } 721 722 // GetFailureCount gets the failure count. 723 func (in UpgradeRemediation) GetFailureCount(hr HelmRelease) int64 { 724 return hr.Status.UpgradeFailures 725 } 726 727 // IncrementFailureCount increments the failure count. 728 func (in UpgradeRemediation) IncrementFailureCount(hr *HelmRelease) { 729 hr.Status.UpgradeFailures++ 730 } 731 732 // RetriesExhausted returns true if there are no remaining retries. 733 func (in UpgradeRemediation) RetriesExhausted(hr HelmRelease) bool { 734 return in.Retries >= 0 && in.GetFailureCount(hr) > int64(in.Retries) 735 } 736 737 // RemediationStrategy returns the strategy to use to remediate a failed install 738 // or upgrade. 739 type RemediationStrategy string 740 741 const ( 742 // RollbackRemediationStrategy represents a Helm remediation strategy of Helm 743 // rollback. 744 RollbackRemediationStrategy RemediationStrategy = "rollback" 745 746 // UninstallRemediationStrategy represents a Helm remediation strategy of Helm 747 // uninstall. 748 UninstallRemediationStrategy RemediationStrategy = "uninstall" 749 ) 750 751 // Test holds the configuration for Helm test actions for this HelmRelease. 752 type Test struct { 753 // Enable enables Helm test actions for this HelmRelease after an Helm install 754 // or upgrade action has been performed. 755 // +optional 756 Enable bool `json:"enable,omitempty"` 757 758 // Timeout is the time to wait for any individual Kubernetes operation during 759 // the performance of a Helm test action. Defaults to 'HelmReleaseSpec.Timeout'. 760 // +kubebuilder:validation:Type=string 761 // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" 762 // +optional 763 Timeout *metav1.Duration `json:"timeout,omitempty"` 764 765 // IgnoreFailures tells the controller to skip remediation when the Helm tests 766 // are run but fail. Can be overwritten for tests run after install or upgrade 767 // actions in 'Install.IgnoreTestFailures' and 'Upgrade.IgnoreTestFailures'. 768 // +optional 769 IgnoreFailures bool `json:"ignoreFailures,omitempty"` 770 } 771 772 // GetTimeout returns the configured timeout for the Helm test action, 773 // or the given default. 774 func (in Test) GetTimeout(defaultTimeout metav1.Duration) metav1.Duration { 775 if in.Timeout == nil { 776 return defaultTimeout 777 } 778 return *in.Timeout 779 } 780 781 // Rollback holds the configuration for Helm rollback actions for this 782 // HelmRelease. 783 type Rollback struct { 784 // Timeout is the time to wait for any individual Kubernetes operation (like 785 // Jobs for hooks) during the performance of a Helm rollback action. Defaults to 786 // 'HelmReleaseSpec.Timeout'. 787 // +kubebuilder:validation:Type=string 788 // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" 789 // +optional 790 Timeout *metav1.Duration `json:"timeout,omitempty"` 791 792 // DisableWait disables the waiting for resources to be ready after a Helm 793 // rollback has been performed. 794 // +optional 795 DisableWait bool `json:"disableWait,omitempty"` 796 797 // DisableWaitForJobs disables waiting for jobs to complete after a Helm 798 // rollback has been performed. 799 // +optional 800 DisableWaitForJobs bool `json:"disableWaitForJobs,omitempty"` 801 802 // DisableHooks prevents hooks from running during the Helm rollback action. 803 // +optional 804 DisableHooks bool `json:"disableHooks,omitempty"` 805 806 // Recreate performs pod restarts for the resource if applicable. 807 // +optional 808 Recreate bool `json:"recreate,omitempty"` 809 810 // Force forces resource updates through a replacement strategy. 811 // +optional 812 Force bool `json:"force,omitempty"` 813 814 // CleanupOnFail allows deletion of new resources created during the Helm 815 // rollback action when it fails. 816 // +optional 817 CleanupOnFail bool `json:"cleanupOnFail,omitempty"` 818 } 819 820 // GetTimeout returns the configured timeout for the Helm rollback action, or 821 // the given default. 822 func (in Rollback) GetTimeout(defaultTimeout metav1.Duration) metav1.Duration { 823 if in.Timeout == nil { 824 return defaultTimeout 825 } 826 return *in.Timeout 827 } 828 829 // Uninstall holds the configuration for Helm uninstall actions for this 830 // HelmRelease. 831 type Uninstall struct { 832 // Timeout is the time to wait for any individual Kubernetes operation (like 833 // Jobs for hooks) during the performance of a Helm uninstall action. Defaults 834 // to 'HelmReleaseSpec.Timeout'. 835 // +kubebuilder:validation:Type=string 836 // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" 837 // +optional 838 Timeout *metav1.Duration `json:"timeout,omitempty"` 839 840 // DisableHooks prevents hooks from running during the Helm rollback action. 841 // +optional 842 DisableHooks bool `json:"disableHooks,omitempty"` 843 844 // KeepHistory tells Helm to remove all associated resources and mark the 845 // release as deleted, but retain the release history. 846 // +optional 847 KeepHistory bool `json:"keepHistory,omitempty"` 848 849 // DisableWait disables waiting for all the resources to be deleted after 850 // a Helm uninstall is performed. 851 // +optional 852 DisableWait bool `json:"disableWait,omitempty"` 853 854 // DeletionPropagation specifies the deletion propagation policy when 855 // a Helm uninstall is performed. 856 // +kubebuilder:default=background 857 // +kubebuilder:validation:Enum=background;foreground;orphan 858 // +optional 859 DeletionPropagation *string `json:"deletionPropagation,omitempty"` 860 } 861 862 // GetTimeout returns the configured timeout for the Helm uninstall action, or 863 // the given default. 864 func (in Uninstall) GetTimeout(defaultTimeout metav1.Duration) metav1.Duration { 865 if in.Timeout == nil { 866 return defaultTimeout 867 } 868 return *in.Timeout 869 } 870 871 // GetDeletionPropagation returns the configured deletion propagation policy 872 // for the Helm uninstall action, or 'background'. 873 func (in Uninstall) GetDeletionPropagation() string { 874 if in.DeletionPropagation == nil { 875 return "background" 876 } 877 return *in.DeletionPropagation 878 } 879 880 // HelmReleaseStatus defines the observed state of a HelmRelease. 881 type HelmReleaseStatus struct { 882 // ObservedGeneration is the last observed generation. 883 // +optional 884 ObservedGeneration int64 `json:"observedGeneration,omitempty"` 885 886 // ObservedPostRenderersDigest is the digest for the post-renderers of 887 // the last successful reconciliation attempt. 888 // +optional 889 ObservedPostRenderersDigest string `json:"observedPostRenderersDigest,omitempty"` 890 891 meta.ReconcileRequestStatus `json:",inline"` 892 893 // Conditions holds the conditions for the HelmRelease. 894 // +optional 895 Conditions []metav1.Condition `json:"conditions,omitempty"` 896 897 // LastAppliedRevision is the revision of the last successfully applied source. 898 // +optional 899 LastAppliedRevision string `json:"lastAppliedRevision,omitempty"` 900 901 // LastAttemptedRevision is the revision of the last reconciliation attempt. 902 // +optional 903 LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty"` 904 905 // LastAttemptedValuesChecksum is the SHA1 checksum of the values of the last 906 // reconciliation attempt. 907 // +optional 908 LastAttemptedValuesChecksum string `json:"lastAttemptedValuesChecksum,omitempty"` 909 910 // LastReleaseRevision is the revision of the last successful Helm release. 911 // +optional 912 LastReleaseRevision int `json:"lastReleaseRevision,omitempty"` 913 914 // HelmChart is the namespaced name of the HelmChart resource created by 915 // the controller for the HelmRelease. 916 // +optional 917 HelmChart string `json:"helmChart,omitempty"` 918 919 // Failures is the reconciliation failure count against the latest desired 920 // state. It is reset after a successful reconciliation. 921 // +optional 922 Failures int64 `json:"failures,omitempty"` 923 924 // InstallFailures is the install failure count against the latest desired 925 // state. It is reset after a successful reconciliation. 926 // +optional 927 InstallFailures int64 `json:"installFailures,omitempty"` 928 929 // UpgradeFailures is the upgrade failure count against the latest desired 930 // state. It is reset after a successful reconciliation. 931 // +optional 932 UpgradeFailures int64 `json:"upgradeFailures,omitempty"` 933 934 // StorageNamespace is the namespace of the Helm release storage for the 935 // current release. 936 // 937 // Note: this field is provisional to the v2beta2 API, and not actively used 938 // by v2beta1 HelmReleases. 939 // +optional 940 StorageNamespace string `json:"storageNamespace,omitempty"` 941 942 // History holds the history of Helm releases performed for this HelmRelease 943 // up to the last successfully completed release. 944 // 945 // Note: this field is provisional to the v2beta2 API, and not actively used 946 // by v2beta1 HelmReleases. 947 // +optional 948 History v2.Snapshots `json:"history,omitempty"` 949 950 // LastAttemptedGeneration is the last generation the controller attempted 951 // to reconcile. 952 // 953 // Note: this field is provisional to the v2beta2 API, and not actively used 954 // by v2beta1 HelmReleases. 955 // +optional 956 LastAttemptedGeneration int64 `json:"lastAttemptedGeneration,omitempty"` 957 958 // LastAttemptedConfigDigest is the digest for the config (better known as 959 // "values") of the last reconciliation attempt. 960 // 961 // Note: this field is provisional to the v2beta2 API, and not actively used 962 // by v2beta1 HelmReleases. 963 // +optional 964 LastAttemptedConfigDigest string `json:"lastAttemptedConfigDigest,omitempty"` 965 966 // LastAttemptedReleaseAction is the last release action performed for this 967 // HelmRelease. It is used to determine the active remediation strategy. 968 // 969 // Note: this field is provisional to the v2beta2 API, and not actively used 970 // by v2beta1 HelmReleases. 971 // +optional 972 LastAttemptedReleaseAction string `json:"lastAttemptedReleaseAction,omitempty"` 973 974 // LastHandledForceAt holds the value of the most recent force request 975 // value, so a change of the annotation value can be detected. 976 // 977 // Note: this field is provisional to the v2beta2 API, and not actively used 978 // by v2beta1 HelmReleases. 979 // +optional 980 LastHandledForceAt string `json:"lastHandledForceAt,omitempty"` 981 982 // LastHandledResetAt holds the value of the most recent reset request 983 // value, so a change of the annotation value can be detected. 984 // 985 // Note: this field is provisional to the v2beta2 API, and not actively used 986 // by v2beta1 HelmReleases. 987 // +optional 988 LastHandledResetAt string `json:"lastHandledResetAt,omitempty"` 989 } 990 991 // GetHelmChart returns the namespace and name of the HelmChart. 992 func (in HelmReleaseStatus) GetHelmChart() (string, string) { 993 if in.HelmChart == "" { 994 return "", "" 995 } 996 if split := strings.Split(in.HelmChart, string(types.Separator)); len(split) > 1 { 997 return split[0], split[1] 998 } 999 return "", "" 1000 } 1001 1002 // HelmReleaseProgressing resets any failures and registers progress toward 1003 // reconciling the given HelmRelease by setting the meta.ReadyCondition to 1004 // 'Unknown' for meta.ProgressingReason. 1005 func HelmReleaseProgressing(hr HelmRelease) HelmRelease { 1006 hr.Status.Conditions = []metav1.Condition{} 1007 newCondition := metav1.Condition{ 1008 Type: meta.ReadyCondition, 1009 Status: metav1.ConditionUnknown, 1010 Reason: meta.ProgressingReason, 1011 Message: "Reconciliation in progress", 1012 } 1013 apimeta.SetStatusCondition(hr.GetStatusConditions(), newCondition) 1014 resetFailureCounts(&hr) 1015 return hr 1016 } 1017 1018 // HelmReleaseNotReady registers a failed reconciliation of the given HelmRelease. 1019 func HelmReleaseNotReady(hr HelmRelease, reason, message string) HelmRelease { 1020 newCondition := metav1.Condition{ 1021 Type: meta.ReadyCondition, 1022 Status: metav1.ConditionFalse, 1023 Reason: reason, 1024 Message: message, 1025 } 1026 apimeta.SetStatusCondition(hr.GetStatusConditions(), newCondition) 1027 hr.Status.Failures++ 1028 return hr 1029 } 1030 1031 // HelmReleaseReady registers a successful reconciliation of the given HelmRelease. 1032 func HelmReleaseReady(hr HelmRelease) HelmRelease { 1033 newCondition := metav1.Condition{ 1034 Type: meta.ReadyCondition, 1035 Status: metav1.ConditionTrue, 1036 Reason: ReconciliationSucceededReason, 1037 Message: "Release reconciliation succeeded", 1038 } 1039 apimeta.SetStatusCondition(hr.GetStatusConditions(), newCondition) 1040 hr.Status.LastAppliedRevision = hr.Status.LastAttemptedRevision 1041 resetFailureCounts(&hr) 1042 return hr 1043 } 1044 1045 // HelmReleaseAttempted registers an attempt of the given HelmRelease with the given state. 1046 // and returns the modified HelmRelease and a boolean indicating a state change. 1047 // 1048 // Deprecated: in favor of HelmReleaseChanged and HelmReleaseRecordAttempt. 1049 func HelmReleaseAttempted(hr HelmRelease, revision string, releaseRevision int, valuesChecksum string) (HelmRelease, bool) { 1050 changed := hr.Status.LastAttemptedRevision != revision || 1051 hr.Status.LastReleaseRevision != releaseRevision || 1052 hr.Status.LastAttemptedValuesChecksum != valuesChecksum 1053 hr.Status.LastAttemptedRevision = revision 1054 hr.Status.LastReleaseRevision = releaseRevision 1055 hr.Status.LastAttemptedValuesChecksum = valuesChecksum 1056 1057 return hr, changed 1058 } 1059 1060 // HelmReleaseChanged returns if the HelmRelease has changed compared to the 1061 // provided values. 1062 func HelmReleaseChanged(hr HelmRelease, revision string, releaseRevision int, valuesChecksums ...string) bool { 1063 return hr.Status.LastAttemptedRevision != revision || 1064 hr.Status.LastReleaseRevision != releaseRevision || 1065 !inStringSlice(hr.Status.LastAttemptedValuesChecksum, valuesChecksums) 1066 } 1067 1068 // HelmReleaseRecordAttempt returns an attempt of the given HelmRelease with the 1069 // given state in the Status of the provided object. 1070 func HelmReleaseRecordAttempt(hr *HelmRelease, revision string, releaseRevision int, valuesChecksum string) { 1071 hr.Status.LastAttemptedRevision = revision 1072 hr.Status.LastReleaseRevision = releaseRevision 1073 hr.Status.LastAttemptedValuesChecksum = valuesChecksum 1074 } 1075 1076 func inStringSlice(str string, s []string) bool { 1077 for _, v := range s { 1078 if str == v { 1079 return true 1080 } 1081 } 1082 return false 1083 } 1084 1085 func resetFailureCounts(hr *HelmRelease) { 1086 hr.Status.Failures = 0 1087 hr.Status.InstallFailures = 0 1088 hr.Status.UpgradeFailures = 0 1089 } 1090 1091 const ( 1092 // SourceIndexKey is the key used for indexing HelmReleases based on 1093 // their sources. 1094 SourceIndexKey string = ".metadata.source" 1095 ) 1096 1097 // +genclient 1098 // +kubebuilder:object:root=true 1099 // +kubebuilder:resource:shortName=hr 1100 // +kubebuilder:subresource:status 1101 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="" 1102 // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="" 1103 // +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description="" 1104 // +kubebuilder:deprecatedversion:warning="v2beta1 HelmRelease is deprecated, upgrade to v2" 1105 1106 // HelmRelease is the Schema for the helmreleases API 1107 type HelmRelease struct { 1108 metav1.TypeMeta `json:",inline"` 1109 metav1.ObjectMeta `json:"metadata,omitempty"` 1110 1111 Spec HelmReleaseSpec `json:"spec,omitempty"` 1112 // +kubebuilder:default:={"observedGeneration":-1} 1113 Status HelmReleaseStatus `json:"status,omitempty"` 1114 } 1115 1116 // GetRequeueAfter returns the duration after which the HelmRelease 1117 // must be reconciled again. 1118 func (in HelmRelease) GetRequeueAfter() time.Duration { 1119 return in.Spec.Interval.Duration 1120 } 1121 1122 // GetValues unmarshals the raw values to a map[string]interface{} and returns 1123 // the result. 1124 func (in HelmRelease) GetValues() map[string]interface{} { 1125 var values map[string]interface{} 1126 if in.Spec.Values != nil { 1127 _ = json.Unmarshal(in.Spec.Values.Raw, &values) 1128 } 1129 return values 1130 } 1131 1132 // GetReleaseName returns the configured release name, or a composition of 1133 // '[TargetNamespace-]Name'. 1134 func (in HelmRelease) GetReleaseName() string { 1135 if in.Spec.ReleaseName != "" { 1136 return in.Spec.ReleaseName 1137 } 1138 if in.Spec.TargetNamespace != "" { 1139 return strings.Join([]string{in.Spec.TargetNamespace, in.Name}, "-") 1140 } 1141 return in.Name 1142 } 1143 1144 // GetReleaseNamespace returns the configured TargetNamespace, or the namespace 1145 // of the HelmRelease. 1146 func (in HelmRelease) GetReleaseNamespace() string { 1147 if in.Spec.TargetNamespace != "" { 1148 return in.Spec.TargetNamespace 1149 } 1150 return in.Namespace 1151 } 1152 1153 // GetStorageNamespace returns the configured StorageNamespace for helm, or the namespace 1154 // of the HelmRelease. 1155 func (in HelmRelease) GetStorageNamespace() string { 1156 if in.Spec.StorageNamespace != "" { 1157 return in.Spec.StorageNamespace 1158 } 1159 return in.Namespace 1160 } 1161 1162 // GetHelmChartName returns the name used by the controller for the HelmChart creation. 1163 func (in HelmRelease) GetHelmChartName() string { 1164 return strings.Join([]string{in.Namespace, in.Name}, "-") 1165 } 1166 1167 // GetTimeout returns the configured Timeout, or the default of 300s. 1168 func (in HelmRelease) GetTimeout() metav1.Duration { 1169 if in.Spec.Timeout == nil { 1170 return metav1.Duration{Duration: 300 * time.Second} 1171 } 1172 return *in.Spec.Timeout 1173 } 1174 1175 // GetMaxHistory returns the configured MaxHistory, or the default of 10. 1176 func (in HelmRelease) GetMaxHistory() int { 1177 if in.Spec.MaxHistory == nil { 1178 return 10 1179 } 1180 return *in.Spec.MaxHistory 1181 } 1182 1183 // UsePersistentClient returns the configured PersistentClient, or the default 1184 // of true. 1185 func (in HelmRelease) UsePersistentClient() bool { 1186 if in.Spec.PersistentClient == nil { 1187 return true 1188 } 1189 return *in.Spec.PersistentClient 1190 } 1191 1192 // GetDependsOn returns the list of dependencies across-namespaces. 1193 func (in HelmRelease) GetDependsOn() []meta.NamespacedObjectReference { 1194 return in.Spec.DependsOn 1195 } 1196 1197 // GetConditions returns the status conditions of the object. 1198 func (in HelmRelease) GetConditions() []metav1.Condition { 1199 return in.Status.Conditions 1200 } 1201 1202 // SetConditions sets the status conditions on the object. 1203 func (in *HelmRelease) SetConditions(conditions []metav1.Condition) { 1204 in.Status.Conditions = conditions 1205 } 1206 1207 // GetStatusConditions returns a pointer to the Status.Conditions slice. 1208 // Deprecated: use GetConditions instead. 1209 func (in *HelmRelease) GetStatusConditions() *[]metav1.Condition { 1210 return &in.Status.Conditions 1211 } 1212 1213 // +kubebuilder:object:root=true 1214 1215 // HelmReleaseList contains a list of HelmRelease objects. 1216 type HelmReleaseList struct { 1217 metav1.TypeMeta `json:",inline"` 1218 metav1.ListMeta `json:"metadata,omitempty"` 1219 Items []HelmRelease `json:"items"` 1220 } 1221 1222 func init() { 1223 SchemeBuilder.Register(&HelmRelease{}, &HelmReleaseList{}) 1224 } 1225