diff --git a/chart/helm-operator/templates/crd.yaml b/chart/helm-operator/templates/crd.yaml index 95e9dfc01..1865b130c 100644 --- a/chart/helm-operator/templates/crd.yaml +++ b/chart/helm-operator/templates/crd.yaml @@ -84,6 +84,15 @@ spec: enable: description: If set, will perform rollbacks for this release on upgrade failures type: boolean + retry: + description: If set, the upgrade of a rolled back release will be retried until + the maximum amount of retries is reached + type: boolean + maxRetries: + description: The maximum amount of retries that should be attempted for a rolled + back release if retries are enabled, defaults to 5, 0 equals infinite + type: integer + format: int64 force: description: If set, will force resource update through delete/recreate if needed type: boolean diff --git a/deploy/flux-helm-release-crd.yaml b/deploy/flux-helm-release-crd.yaml index 78387b098..40803566f 100644 --- a/deploy/flux-helm-release-crd.yaml +++ b/deploy/flux-helm-release-crd.yaml @@ -76,6 +76,15 @@ spec: enable: description: If set, will perform rollbacks for this release on upgrade failures type: boolean + retry: + description: If set, the upgrade of a rolled back release will be retried until + the maximum amount of retries is reached + type: boolean + maxRetries: + description: The maximum amount of retries that should be attempted for a rolled + back release if retries are enabled, defaults to 5, 0 equals infinite + type: integer + format: int64 force: description: If set, will force resource update through delete/recreate if needed type: boolean diff --git a/pkg/apis/helm.fluxcd.io/v1/types.go b/pkg/apis/helm.fluxcd.io/v1/types.go index a422cd59b..d3c25e97f 100644 --- a/pkg/apis/helm.fluxcd.io/v1/types.go +++ b/pkg/apis/helm.fluxcd.io/v1/types.go @@ -164,6 +164,8 @@ func (s RepoChartSource) CleanRepoURL() string { type Rollback struct { Enable bool `json:"enable,omitempty"` + Retry bool `json:"retry,omitempty"` + MaxRetries *int64 `json:"maxRetries,omitempty"` Force bool `json:"force,omitempty"` Recreate bool `json:"recreate,omitempty"` DisableHooks bool `json:"disableHooks,omitempty"` @@ -178,6 +180,13 @@ func (r Rollback) GetTimeout() time.Duration { return time.Duration(*r.Timeout) * time.Second } +func (r Rollback) GetMaxRetries() int64 { + if r.MaxRetries == nil { + return 5 + } + return *r.MaxRetries +} + // HelmReleaseSpec is the spec for a HelmRelease resource type HelmReleaseSpec struct { ChartSource `json:"chart"` @@ -268,6 +277,11 @@ type HelmReleaseStatus struct { // +optional Revision string `json:"revision,omitempty"` + // RollbackCount defines the amount of rollback attempts made, + // it is incremented after a rollback failure and reset after a + // successful upgrade or revision change. + RollbackCount int64 `json:"retryCount,omitempty"` + // Conditions contains observations of the resource's state, e.g., // has the chart which it refers to been fetched. // +optional diff --git a/pkg/apis/helm.fluxcd.io/v1/zz_generated.deepcopy.go b/pkg/apis/helm.fluxcd.io/v1/zz_generated.deepcopy.go index a16e5a0c8..2cbb6f58c 100644 --- a/pkg/apis/helm.fluxcd.io/v1/zz_generated.deepcopy.go +++ b/pkg/apis/helm.fluxcd.io/v1/zz_generated.deepcopy.go @@ -323,6 +323,11 @@ func (in *RepoChartSource) DeepCopy() *RepoChartSource { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Rollback) DeepCopyInto(out *Rollback) { *out = *in + if in.MaxRetries != nil { + in, out := &in.MaxRetries, &out.MaxRetries + *out = new(int64) + **out = **in + } if in.Timeout != nil { in, out := &in.Timeout, &out.Timeout *out = new(int64) diff --git a/pkg/install/generated_templates.gogen.go b/pkg/install/generated_templates.gogen.go index e3309d47d..f18fbe920 100644 --- a/pkg/install/generated_templates.gogen.go +++ b/pkg/install/generated_templates.gogen.go @@ -31,9 +31,9 @@ var templates = func() http.FileSystem { "/flux-helm-release-crd.yaml.tmpl": &vfsgen۰CompressedFileInfo{ name: "flux-helm-release-crd.yaml.tmpl", modTime: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), - uncompressedSize: 8246, + uncompressedSize: 8741, - compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x59\xeb\x6f\xdb\xc8\x11\xff\xee\xbf\x62\x9a\x16\xb0\x53\x58\xcc\xab\x28\x5a\x01\x87\xbb\xe0\x82\x6b\xd2\xe4\x2e\x86\x1d\xa7\x1f\x8c\x14\x18\x91\x43\x72\xaa\x7d\xb0\xfb\x90\xad\x16\xfd\xdf\x8b\x5d\x92\x92\x48\x51\x14\xed\xb8\x05\x8a\x74\xbf\xd8\xdc\xc7\xec\x6f\x7e\xf3\xd8\xd9\xd5\x6c\x36\x3b\xc1\x8a\x3f\x93\xb1\xac\xd5\x1c\xb0\x62\xba\x73\xa4\xc2\x97\x4d\x96\x7f\xb0\x09\xeb\x67\xab\x17\x0b\x72\xf8\xe2\x64\xc9\x2a\x9b\xc3\x8f\xde\x3a\x2d\x2f\xc9\x6a\x6f\x52\x7a\x43\x39\x2b\x76\xac\xd5\x89\x24\x87\x19\x3a\x9c\x9f\x00\x28\x94\x34\x87\x92\x84\x34\x24\x08\x2d\xd9\x24\x7c\x24\xb9\xf0\x77\x69\x96\xb0\x3e\xb1\x15\xa5\x61\x66\x61\xb4\xaf\xea\xa9\x3b\xa3\xb5\x04\x1b\x26\x00\xd4\xfb\xbe\x25\x21\x2f\x6b\x61\xb1\x57\xb0\x75\xef\xfb\x23\x1f\xd8\xba\x38\x5a\x09\x6f\x50\x74\x21\xc4\x01\x5b\x6a\xe3\x7e\xd9\x0a\x9f\x41\x69\x4e\x00\x6c\xaa\x2b\x9a\x43\x1c\xa8\x30\xa5\xec\x04\x00\xb3\x2c\x6a\x86\xe2\xc2\xb0\x72\x64\x7e\xd4\xc2\x4b\xb5\x59\xf8\xe7\xab\x8f\xbf\x5c\xa0\x2b\xe7\x90\x58\x87\xce\xdb\xa4\xd9\x29\x48\x89\x73\x5a\x22\x76\x71\x03\xb8\x75\xd8\xca\x3a\xc3\xaa\x38\x26\xea\x2a\x7e\x75\x84\x75\xba\x26\xc9\x4a\xb5\xaa\x35\xb1\x37\xdf\x9f\xfd\x90\x84\x35\xdf\x7d\xf7\xa4\x01\x95\x3d\x79\xfa\x25\x91\x64\x2d\x16\x5d\xd0\x3f\x77\xfa\xc6\x37\x6a\x6d\x9f\xa4\x86\x30\xec\xf4\x89\x25\x59\x87\xb2\xea\x88\x7c\xdd\x13\x97\xa1\x0b\x1d\xd6\x2f\x4c\xe3\x4f\x0d\xb9\x35\xf0\x39\xfc\xf3\x5f\x27\x00\xab\xd6\x3b\x57\x2f\xb6\x5f\x1b\x2b\xd4\x92\xe3\x50\x5c\x49\x66\x45\xd9\x1c\x9c\xf1\xed\x5e\xd6\x69\x83\x05\x6d\xfa\x56\x28\x38\x8b\x28\x6b\x19\xba\x22\xf5\xfa\xe2\xdd\xe7\x57\x57\x69\x49\x12\xe7\xcd\xb2\xca\xe8\x8a\x8c\xe3\x16\x53\x14\xd5\x78\x6d\xdb\x0c\xfd\xdd\xb3\x09\xfb\xdd\x9c\xa6\x25\x1a\x77\xfa\x65\x67\x74\x48\x42\x68\xc1\x29\xdb\x90\xeb\x0c\x00\x64\x64\x53\xc3\x55\x04\x07\x9f\x4a\x8a\xce\xdd\xea\x0c\xae\x64\x0b\x8d\x67\x80\x43\x53\x90\xb3\x09\xbc\xcb\x41\x69\x07\xd6\x57\x95\x60\xca\xce\x81\x1d\xdc\xb2\x10\x90\x51\x8e\x5e\x38\x70\xba\xb7\x0b\xc0\xea\x65\xd2\xeb\xdb\x33\xf0\x8e\x1e\xe8\x1c\x19\x35\x87\x27\x7f\x5d\xdd\xbc\x3c\x7f\xf5\xe5\x37\x4f\x3a\x33\x76\xdc\x7e\x9a\x3e\xad\x0a\xc1\x76\x23\xf8\x17\x04\x05\x29\x32\xe8\x28\x83\xc5\x1a\x30\xcf\xf9\x8e\x55\x01\xae\xa4\x3d\x8d\x54\x1b\xba\xe0\x74\x98\x00\xad\x4b\xd5\xbb\x3c\x48\xdb\x1b\x9c\xfd\xe3\xf9\xec\x8f\x5f\xce\x6e\x66\xcd\x7f\xbf\x6d\xbb\x9e\x7e\xdf\x23\x41\xe2\xdd\x5b\x0e\xae\xb6\x3e\xca\x81\xc4\x3b\x96\x5e\x82\xf2\x72\x41\x06\x74\xbe\x21\xc4\xd0\x8a\xa3\x77\x07\x25\x96\x44\xd5\x79\x6b\xc4\xd8\xf3\xe2\xf9\xa0\x1a\x21\x39\x15\x64\x7a\x63\xb9\x36\x12\x5d\x1c\x7d\xf5\xb2\x33\x56\x7b\xce\x26\xd7\xdd\xdf\x66\x71\xd9\x80\xe1\x02\xed\x5b\x3b\xb4\x36\x0c\xbd\x76\x9b\x12\xb7\x0d\xed\xbe\xa1\x6a\xd1\xff\x71\x6b\x39\x96\xa4\xbd\x1b\x55\x3d\xaa\xcd\xca\x3a\x14\x02\xb4\x01\x5f\x15\x06\x33\x6a\xd7\x02\x2b\xb0\x14\x72\xab\x7d\x98\x55\x7e\xff\xbb\x5e\x14\x59\x72\x9f\x51\xf8\x7e\xba\xe8\xc1\x7a\x97\x6f\x18\xaf\x29\x8e\x0b\x43\x56\xf3\x64\x41\xab\x98\x5e\x5a\xb0\x83\xc8\x16\x5a\x0b\x42\xd5\x19\xbb\x45\x1e\x67\xe3\x2f\xc8\x2e\xa0\x07\x54\x5b\x26\x34\xa4\x5a\x56\x82\x1c\xc1\x82\x72\x6d\x82\x6f\x9b\x65\x08\xd1\xd6\x5d\xd0\x82\xf5\x69\x4a\xd6\xe6\x5e\x4c\x46\x93\x6b\x93\xd2\x75\xbd\xcb\xfd\xc8\x88\x2b\x6b\xd3\x6d\x50\x96\x46\xfb\xa2\x84\x8c\x02\xd0\x67\x86\xe2\x21\x35\x9d\x1a\xa3\x85\x58\x60\xba\xec\x03\xa9\x57\xe8\xc5\xdf\x28\x75\x7d\xaf\x3c\x90\xf9\x43\x23\x85\x0b\xb1\xa7\xd5\xa0\x66\xe4\xce\x6b\xad\x2a\x32\xc1\x71\x36\x50\x6c\xb4\x44\xe7\x38\xd0\x5b\xb3\xe4\xc8\xc2\x1b\xea\xbb\xe5\xb8\x96\x1b\xda\xef\x87\xac\xe6\x7b\x13\xc2\xbe\x0a\x67\xfa\x21\xca\x81\x73\x50\x44\x59\x2c\xaf\xee\x07\xad\x15\x31\x84\x6e\x7c\x65\xc6\x36\x10\xfe\x56\xeb\xe5\x80\x35\x46\x59\x37\xb4\x22\xe5\xa0\x0c\x4b\x21\x37\x5a\x82\xf1\x4a\x05\xf7\xce\xbc\x89\x5e\xde\xd8\xe3\xde\xa0\x0e\xe4\x9f\x3d\x3c\xa1\x8e\xda\x49\x34\x21\xe2\x6e\xb7\x81\xb8\x06\x56\x19\xaf\x38\xf3\x28\xe0\xbd\x5f\x90\x51\xe4\x42\x12\xa8\xc2\xa1\xc9\x5a\x9d\x0f\xc8\x87\xce\x91\xf2\xea\xf9\xf3\x03\x59\x0c\x8e\x64\x32\x18\xcd\x66\x70\x20\xa7\x8c\x33\x1e\x75\xf3\xca\xb1\x88\x27\x83\x64\xd5\x3b\x28\x2f\x74\x66\xc3\x5f\x84\x37\x54\x09\xbd\x96\xa4\xfa\xb1\x57\x37\x34\x91\x37\x04\x43\x98\xad\x63\x41\xb9\x97\xa3\xea\xc3\xe7\x78\x9e\x82\x51\x73\xc6\xc4\xfb\x13\x0b\xba\x0a\x2e\xea\xc6\x93\xf7\x1b\xaa\x0c\xa5\xa1\xa0\xf9\x15\x5c\x5b\x6a\xb2\xf6\x4f\x46\xcb\xc4\xc6\xe5\xef\x69\x7d\x49\x79\x3c\x76\x08\xfb\x61\x52\x83\x40\x63\x70\xdd\x1b\x61\x47\x72\xc0\xbb\x47\x52\x54\xb7\x78\x0d\x47\x6f\xa7\x76\xad\xdb\x58\x1e\x6b\x8a\xfa\x03\x1e\xb6\xa3\x73\x28\x34\x82\xcd\x62\x1d\x10\x95\x3c\x07\xe9\xad\x0b\xb5\x01\xab\x4d\x79\xb0\x53\x39\x34\x85\x41\xff\xca\x37\xac\xdd\x40\x59\xb0\x65\x75\x38\x67\x3f\x16\x83\xe3\xf4\xa4\x5a\xe5\x5c\xfc\x8c\x55\x6d\xd3\x61\xa6\x46\xe5\xc3\x34\x2b\x1d\x87\x02\xa3\xd6\x82\x31\x8b\xd5\x5a\x48\xac\x1e\xc9\x68\x30\x5e\xcf\xb5\x6d\x49\x7b\x75\xf4\x01\xb0\xef\x69\xdd\x22\xda\x60\x0d\x99\xad\x20\x17\x3b\x9b\xc2\x28\xa4\xef\x6e\x35\x5d\x0f\x24\x6b\x94\x43\x01\x3f\x19\xa9\xae\xea\x47\x82\x89\x70\xdb\x7c\xb7\xcd\x36\x60\xc8\x19\xa6\x15\x8a\x96\xf3\x16\x32\x0b\x02\xb6\xa0\x34\x08\xad\x0a\x32\x20\x51\x65\x18\xee\x18\x47\x00\x1f\x3a\x76\xea\xa6\x0e\x55\xfe\x07\x20\x7f\xea\x94\xf5\xac\xe0\xb6\xe4\xb4\xec\x31\x4e\x77\x6c\xdd\xd0\x19\x32\x89\xc8\xdd\xe4\xf7\x3f\x1a\x28\x8f\x9a\xda\x26\x50\x06\x0f\x8d\x92\x1a\xe8\xff\x43\xe4\xbf\x14\x22\x0d\xdd\x5f\x17\x1f\x74\x17\x6e\xb9\x28\xae\x62\x99\xfd\x38\x41\xe2\x8d\x78\x70\x8c\x78\x33\xd5\x98\xd7\x97\x1f\xba\x36\xfb\xc6\xbc\x29\x3e\x0b\x86\xf2\xf0\x71\x8c\x56\xa1\x2b\x1f\x6c\xb5\xb0\x78\x22\x6b\x61\x2a\xdc\xb2\x2b\x9b\xa4\x11\x1f\x15\xa2\x2e\x70\x16\x6f\x42\x05\x3b\x30\x54\xe9\xa7\x70\x5b\x92\xe9\x18\x37\x50\x28\x74\xac\x72\xbf\x15\x3b\x6b\x45\x1f\x07\xcc\x3b\xeb\xbe\x10\x77\x0b\xc2\x01\x33\x76\xe7\xef\x1e\x8a\x47\x27\xef\x65\x88\xa3\x2b\x76\x3d\xb3\x37\x79\x75\xfc\x19\x2a\xd5\xca\x85\xcb\xb1\xce\x47\xe2\xfa\xa0\x6b\xc7\xbd\xfb\xf2\x07\x49\xec\x62\x2e\xd8\x9d\x9e\xc3\xa1\x28\x18\x8f\x80\x62\xf8\x36\xda\xd3\xeb\x4f\xec\x62\xce\xa2\xa4\x48\xc2\x92\x1f\x0a\x76\xa5\x5f\x24\xa9\x96\x73\x6d\x8a\x67\xc1\xe7\xef\x77\x1b\x69\x5b\x7b\x59\x0e\x91\xf3\xeb\xf8\x72\x9a\x51\xce\xaa\x7e\xdb\xfe\xf8\xfa\x6a\x60\xd1\xe1\x80\xed\x60\xbe\x08\xc1\xca\xca\x72\x56\xbf\xb6\xb6\xb1\x69\x39\xb8\x74\x13\xa0\x6d\xd9\xd1\x44\x31\x0f\x9f\x45\x47\xb4\x30\x87\x92\xd8\x1e\x87\x0b\x83\x2a\x2d\xbb\xe5\x84\x44\xeb\x06\xdf\x11\x8e\xd7\x87\x4b\xae\xde\x50\x75\x1d\x9f\x97\x26\x20\x68\x93\x41\xa6\xc9\x46\xaa\x8d\x57\x70\x9a\x51\x75\xda\x3e\x51\x9d\xa1\xb5\x5e\x52\xeb\x5d\x92\x94\xdb\x66\x2f\x14\xf5\xb3\x41\xee\x45\xce\x42\x50\xf6\x74\x04\xf4\x70\x4e\xe8\xfa\xed\xd6\x1a\xc1\x7d\x63\x79\x7a\x0e\xa7\xcd\xef\x3a\xf7\xf6\xe4\xad\xb4\x09\x54\x34\xaf\xf7\x1b\x6f\xb8\xbe\xfc\xf0\x75\xfe\xeb\x8d\x98\xea\xbf\x13\x5f\x08\x76\xdc\x52\x0d\xfd\x50\x30\x01\xde\x6a\xf8\xf7\xb4\xf1\xcd\x9a\x45\x5f\x47\x87\x25\xb9\x22\x33\x95\x91\xb8\xf1\x85\x17\xa2\x7e\x2d\x1a\xc6\xfb\xa8\x77\x94\xbe\xfd\x17\x68\x39\x05\xf4\xae\x84\xb3\x00\x99\x65\x25\xa2\xfb\x1f\xf2\xf2\x3d\x36\xfe\x1d\x00\x00\xff\xff\x98\xa1\x83\x87\x36\x20\x00\x00"), + compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x59\x6d\x6f\xdb\xc8\x11\xfe\xee\x5f\x31\x4d\x0b\x38\x29\x64\xc5\x49\xda\xa2\x15\x70\xb8\x0b\x2e\xb8\x26\x4d\xee\x62\xd8\x71\xfa\xc1\x48\x81\x11\x39\x24\xb7\xda\x17\xde\xbe\xc8\x56\x8b\xfe\xf7\x62\x76\x49\x49\xa4\x28\x4a\x76\xdc\x02\xc5\x95\x9f\x24\xee\xee\xec\x33\x33\xcf\xbc\xec\xf2\xec\xec\xec\x04\x6b\xf1\x99\xac\x13\x46\xcf\x00\x6b\x41\x77\x9e\x34\xff\x73\xd3\xc5\x1f\xdd\x54\x98\xe7\xcb\x17\x73\xf2\xf8\xe2\x64\x21\x74\x3e\x83\xef\x83\xf3\x46\x5d\x92\x33\xc1\x66\xf4\x86\x0a\xa1\x85\x17\x46\x9f\x28\xf2\x98\xa3\xc7\xd9\x09\x80\x46\x45\x33\xa8\x48\x2a\x4b\x92\xd0\x91\x9b\xf2\x9f\x69\x21\xc3\x5d\x96\x4f\x85\x39\x71\x35\x65\x3c\xb3\xb4\x26\xd4\x69\xea\xd6\x68\x92\xe0\x78\x02\x40\xda\xf7\x2d\x49\x75\x99\x84\xc5\xb7\x52\x38\xff\xbe\x3f\xf2\x41\x38\x1f\x47\x6b\x19\x2c\xca\x2e\x84\x38\xe0\x2a\x63\xfd\x4f\x1b\xe1\x67\x50\xd9\x13\x00\x97\x99\x9a\x66\x10\x07\x6a\xcc\x28\x3f\x01\xc0\x3c\x8f\x9a\xa1\xbc\xb0\x42\x7b\xb2\xdf\x1b\x19\x94\x5e\x2f\xfc\xcb\xd5\xc7\x9f\x2e\xd0\x57\x33\x98\x3a\x8f\x3e\xb8\x69\xb3\x13\x4b\x89\x73\x5a\x43\x6c\xe3\x06\xf0\x2b\xde\xca\x79\x2b\x74\x79\x48\xd4\x55\xfc\xd7\x11\xd6\x79\x75\x94\xac\xcc\xe8\xa4\x89\xbb\xf9\xf6\xe9\x77\x53\x5e\xf3\xcd\x37\x4f\x1a\x50\xf9\x93\x67\x5f\xa6\x8a\x9c\xc3\xb2\x0b\xfa\xc7\xce\xbb\xf1\x8d\x5a\xdf\x4f\x33\x4b\xc8\x3b\x7d\x12\x8a\x9c\x47\x55\x77\x44\xbe\xee\x89\xcb\xd1\xf3\x0b\x17\xe6\xb6\xe1\x53\x63\xdc\x04\x7c\x06\xff\xfc\xd7\x09\xc0\xb2\x65\xe7\xf2\xc5\xe6\xdf\xda\x0b\x49\x72\x1c\x8a\x2b\xc9\x2e\x29\x9f\x81\xb7\xa1\xdd\xcb\x79\x63\xb1\xa4\xf5\xbb\x25\x4a\x91\x47\x94\x49\x86\xa9\x49\xbf\xbe\x78\xf7\xf9\xd5\x55\x56\x91\xc2\x59\xb3\xac\xb6\xa6\x26\xeb\x45\x8b\x29\x8a\x6a\x58\xdb\x3e\x96\x7e\x0e\xc2\xf2\x7e\x37\xa7\x59\x85\xd6\x9f\x7e\xd9\x1a\x1d\x92\xc0\x0f\x93\xb2\x0d\xb9\xce\x00\x40\x4e\x2e\xb3\xa2\x8e\xe0\xe0\x53\x45\x91\xdc\xad\xce\xe0\x2b\xe1\xa0\x61\x06\x78\xb4\x25\x79\x37\x85\x77\x05\x68\xe3\xc1\x85\xba\x96\x82\xf2\x09\x08\x0f\xb7\x42\x4a\xc8\xa9\xc0\x20\x3d\x78\xd3\xdb\x05\x60\xf9\x72\xda\x7b\xb7\xe3\xe0\x2d\x3d\xd0\x7b\xb2\x7a\x06\x4f\xfe\xb6\xbc\x79\x39\x79\xf5\xe5\x37\x4f\x3a\x33\xb6\x68\x7f\x9c\x3e\xad\x0a\xec\xbb\x11\xfc\x73\x82\x92\x34\x59\xf4\x94\xc3\x7c\x05\x58\x14\xe2\x4e\xe8\x12\x7c\x45\x3b\x1a\xe9\x36\x74\xc1\x1b\x9e\x00\x2d\xa5\xd2\x2e\x0f\xd2\xf6\x06\xcf\xfe\x71\x7e\xf6\xa7\x2f\x4f\x6f\xce\x9a\x5f\xbf\x6d\x5f\x3d\xfb\xb6\x67\x04\x85\x77\x6f\x05\x53\x6d\x75\xd0\x06\x0a\xef\x84\x0a\x0a\x74\x50\x73\xb2\x60\x8a\xb5\x41\x2c\x2d\x45\x64\x37\x2b\xb1\x20\xaa\x27\xad\x13\xe3\x9b\x17\xe7\x83\x6a\x70\x72\x2a\xc9\xf6\xc6\x0a\x63\x15\xfa\x38\xfa\xea\x65\x67\x2c\x31\x67\x9d\xeb\xee\xef\xb3\xb8\x6c\xc0\x71\x6c\xf6\x8d\x1f\x5a\x1f\xf2\x5b\xb7\x49\x89\x9b\x07\xdd\xae\xa3\x92\xe8\xff\xb8\xb7\xbc\x50\x64\x82\x1f\x55\x3d\xaa\x2d\xb4\xf3\x28\x25\x18\x0b\xa1\x2e\x2d\xe6\xd4\xae\x05\xa1\xc1\x11\xe7\x56\xf7\x30\xaf\xfc\xe1\x77\xbd\x28\x72\xe4\x3f\xa3\x0c\xfd\x74\xd1\x83\xf5\xae\x58\x5b\x3c\x99\x38\x2e\xe4\xac\x16\xc8\x81\xd1\x31\xbd\xb4\x60\x07\x91\xcd\x8d\x91\x84\xba\x33\x76\x8b\x62\xdc\x1a\x7f\x45\xe1\x19\x3d\xa0\xde\x58\xc2\x40\x66\x54\x2d\xc9\x13\xcc\xa9\x30\x96\xb9\x6d\x17\x1c\xa2\x2d\x5d\xd0\x81\x0b\x59\x46\xce\x15\x41\x1e\x8d\xa6\x30\x36\xa3\xeb\xb4\xcb\xfd\x8c\x11\x57\x26\xd7\xad\x51\x56\xd6\x84\xb2\x82\x9c\x18\xe8\x73\x4b\xb1\x48\x1d\x6f\x1a\x6b\xa4\x9c\x63\xb6\xe8\x03\x49\x2b\xcc\xfc\xef\x94\xf9\x3e\x2b\xf7\x64\x7e\x7e\x48\xe3\x5c\xee\x68\x35\xa8\x19\xf9\x49\xd2\xaa\x26\xcb\xc4\x59\x43\x71\xd1\x13\x9d\x72\x60\x36\x6e\x29\x50\xc8\x60\xa9\x4f\xcb\x71\x2d\xa3\xa6\xe4\x77\xd3\xd7\x5e\x64\x1c\xbb\xed\x96\xa6\x00\x8c\xe8\x38\x53\x63\xb6\x58\xc3\x6a\x93\x00\x8b\x66\x27\x05\xed\x45\x9f\x07\x0d\xb4\xad\xcc\x88\xca\x04\xed\x53\x66\xe4\x75\x0e\xa2\xa6\x98\x55\xb1\x33\xbb\x9f\x56\x0a\xef\x2e\x93\x94\x83\xaa\x7d\x1a\xc5\xe0\x2b\xf4\xdc\x40\x06\x99\xb3\x4a\x9c\x79\x54\xcd\xb5\x29\x86\x45\xa3\xfe\xa0\x6e\x1d\x93\x88\x8d\x44\xb4\xd4\xf0\x21\xef\x66\xfa\xdf\x4f\xe0\x1c\xe8\xe7\x80\xd2\x81\xd0\xb1\xc7\xde\xcd\x9f\xe3\x99\x06\x46\xb3\x0d\xb4\x51\x76\x3f\x22\xa6\xf0\x5a\x67\xec\x50\x73\x0b\xb7\x2f\xc2\x58\x53\x4d\x94\x3f\xc0\x67\xad\x88\x21\x74\xe3\x2b\x73\xe1\xd8\x9e\x6f\x8d\x59\x1c\xf6\x77\x37\xc8\x2c\x2d\x49\x7b\xa8\x78\x29\x14\xd6\x28\xb0\x41\x6b\xce\x66\x79\xb0\x31\xa9\x35\xe1\x77\x6f\x50\x7b\xca\xcd\x0e\x1e\x6e\x9b\xb7\xea\x0a\x33\xe1\x76\x93\x77\x57\x20\x74\x2e\x96\x22\x0f\x28\xe1\x7d\x98\x93\xd5\xe4\x39\xe7\xd7\xdc\x23\x09\xa3\x27\x83\xe4\xdb\xe6\xd5\xab\xf3\xf3\x3d\x45\x0b\xbe\x9a\x4e\x43\x25\x64\xdc\xe2\x51\xb7\x98\x11\x52\xf4\x0b\xdd\xeb\x8b\x2e\x4c\xee\x52\x6e\x79\x43\xb5\x34\x2b\x45\xba\x9f\x6a\xd3\xc3\x81\x24\x34\x07\x21\x61\xbe\x8a\xe7\x87\x9d\x92\x94\x7a\x8d\xc3\x65\x09\x46\xdd\x19\xeb\xec\x0f\x42\xd2\x15\x53\xd4\x8f\xd7\xea\x37\x54\x5b\xca\xb8\x7f\xfd\x15\x5c\x3b\x6a\x8a\xf4\x0f\xd6\xa8\xa9\x8b\xcb\xdf\xd3\xea\x92\x8a\xd8\x65\x10\xf6\xc3\x24\x81\x40\x6b\x71\xd5\x1b\x11\x9e\xd4\x00\xbb\x47\x2a\x52\xf7\xac\xc2\x9d\x56\xe7\xa8\x92\x9e\xb1\xb2\xd5\x9c\xe1\xf6\x30\x6c\x4b\x67\xee\x2b\xd9\x67\xb1\xed\x8b\x4a\x4e\x40\x05\xe7\x39\x65\x0a\xbd\xee\x06\xb7\x1a\xc5\xa6\x0f\xec\x9f\xf0\x87\xb5\x1b\xe8\x02\x37\x56\x1d\x2e\xd1\x8f\x65\xc1\x71\xf3\x64\x46\x17\xa2\xfc\x11\xeb\xe4\xd3\x61\x4b\x8d\xca\x87\xe3\xbc\x74\x18\x0a\x8c\x7a\x0b\xc6\x3c\x96\xb4\x50\x58\x3f\x92\xd3\x60\xbc\x7d\x6f\x9f\x05\x0d\xf6\x1d\x03\x60\xdf\xd3\xaa\x45\xb4\xc6\xca\x99\xad\x24\x1f\x5f\x36\x7d\x30\xa7\xef\x6e\x49\x4d\x03\xd3\x15\xaa\xe1\xfe\xe3\x48\xa4\xa6\x4e\x77\x42\x47\xc2\x6d\xf3\xdd\x26\xdb\x34\xc5\x7f\x89\xb2\xb5\x79\x0b\x59\x48\xe2\x3e\x47\x1b\x90\x46\x97\x64\x41\xa1\xce\x91\x8f\x94\x07\x00\xef\x2b\x3b\xe9\xd1\xfb\x0e\x7a\x7b\x20\x7f\xea\x9c\xe2\x84\x86\xdb\x4a\x64\x55\xcf\xe2\x74\x27\x9c\x1f\xaa\x21\x47\x19\x72\x3b\xf9\xfd\x8f\x06\xca\xa3\xa6\xb6\x23\x4c\x06\x0f\x8d\x92\x04\xf4\xff\x21\xf2\x5f\x0a\x91\xc6\xdc\x5f\x17\x1f\x74\xe7\xc9\x6a\x94\x57\xb1\xcd\x7e\x9c\x20\x09\x56\x3e\x38\x46\x82\x3d\xd6\x99\xd7\x97\x1f\xba\x3e\xfb\x85\xb1\x29\xde\x02\x73\x7b\xf8\x38\x4e\xab\xd1\x57\x0f\xf6\x1a\x2f\x3e\xd2\x6a\x3c\x15\x6e\x85\xaf\x9a\xa4\x11\xef\x90\xa2\x2e\xf0\x34\x9e\x84\x4a\xe1\xc1\x52\x6d\x9e\xc1\x6d\x45\xb6\xe3\x5c\x36\xa1\x34\xb1\xcb\xfd\xa5\xf8\xd9\x68\xfa\x38\xe0\xde\xb3\xee\x07\x81\x6e\x43\x38\xe0\xc6\xee\xfc\xed\xa2\x78\x70\xf2\x4e\x86\x38\xb8\x62\x9b\x99\xbd\xc9\xcb\xc3\xb7\x8e\x99\xd1\x9e\xd2\x75\xc8\xfe\xb8\xde\x4b\xed\xb8\x77\x5f\xfe\xa0\x11\xbb\x98\x4b\xe1\x4f\x27\xb0\x2f\x0a\xc6\x23\xa0\x1c\x3e\x8d\xf6\xf4\xfa\xb3\xf0\x31\x67\xd1\xb4\x9c\xf2\x92\xef\x4a\xe1\xab\x30\x9f\x66\x46\xcd\x8c\x2d\x9f\x33\xe7\xef\x77\x1a\x69\x9f\xf6\xb0\xcc\x91\xf3\xeb\x78\x51\x9e\x53\x21\x74\xfa\x94\xf1\xf1\xf5\xd5\xc0\xa2\xfd\x01\xdb\xc1\x7c\xc1\xc1\x2a\xb4\x13\x79\xba\x5c\x6f\x63\xd3\x09\xa6\x74\x13\xa0\x6d\xdb\xd1\x44\xb1\x18\xae\x45\x07\xb4\xb0\xfb\x92\xd8\x8e\x0d\xe7\x16\x75\x56\x75\xdb\x09\x85\xce\x0f\xde\x23\x1c\xee\x0f\x17\xa2\x7e\x43\xf5\x75\xbc\x5e\x3a\x02\x41\x9b\x0c\x72\x43\x2e\x9a\xda\x06\x0d\xa7\x39\xd5\xa7\xed\x15\xd5\x53\x74\x2e\x28\x6a\xd9\xa5\x48\xfb\x4d\xf6\x42\x99\xae\x0d\x8a\x20\x0b\x21\x25\xe5\xcf\x46\x40\x0f\xe7\x84\x2e\x6f\x37\xde\x60\xfa\xc6\xf6\x74\x02\xa7\xcd\x67\xbc\x7b\x33\x79\x23\xed\x08\x53\x34\x1f\x6b\xd6\x6c\xb8\xbe\xfc\xf0\x75\xfc\x0d\x56\x1e\xcb\xdf\x23\x6f\x08\xb6\x68\xa9\x87\xbe\x0b\x1d\x01\x6f\x39\xfc\xf9\x74\x7c\xb3\x66\xd1\xd7\x99\xc3\x91\x5a\x92\x3d\xd6\x22\x71\xe3\x8b\x20\x65\xba\x2d\x1a\xc6\xfb\xa8\x67\x94\xbe\xff\xe7\xe8\x44\x06\x18\x7c\x05\x4f\x19\xb2\x50\xb5\x8c\xf4\xdf\xc7\xf2\x1d\x6b\xfc\x3b\x00\x00\xff\xff\x07\x32\x7b\x58\x25\x22\x00\x00"), }, "/helm-operator-deployment.yaml.tmpl": &vfsgen۰CompressedFileInfo{ name: "helm-operator-deployment.yaml.tmpl", diff --git a/pkg/install/templates/flux-helm-release-crd.yaml.tmpl b/pkg/install/templates/flux-helm-release-crd.yaml.tmpl index 78387b098..40803566f 100644 --- a/pkg/install/templates/flux-helm-release-crd.yaml.tmpl +++ b/pkg/install/templates/flux-helm-release-crd.yaml.tmpl @@ -76,6 +76,15 @@ spec: enable: description: If set, will perform rollbacks for this release on upgrade failures type: boolean + retry: + description: If set, the upgrade of a rolled back release will be retried until + the maximum amount of retries is reached + type: boolean + maxRetries: + description: The maximum amount of retries that should be attempted for a rolled + back release if retries are enabled, defaults to 5, 0 equals infinite + type: integer + format: int64 force: description: If set, will force resource update through delete/recreate if needed type: boolean diff --git a/pkg/release/release.go b/pkg/release/release.go index 21855f010..53702fd5f 100644 --- a/pkg/release/release.go +++ b/pkg/release/release.go @@ -178,13 +178,18 @@ func (r *Release) Sync(client helm.Client, hr *v1.HelmRelease) (rHr *v1.HelmRele return hr, ErrComposingValues } - if ok, err := shouldSync(logger, client, hr, curRel, chartPath, revision, composedValues, r.config.LogDiffs); !ok { + ok, diff, err := shouldSync(logger, client, hr, curRel, chartPath, revision, composedValues, r.config.LogDiffs) + if !ok { if err != nil { _ = status.SetCondition(r.helmReleaseClient.HelmReleases(hr.Namespace), hr, status.NewCondition( v1.HelmReleaseReleased, corev1.ConditionFalse, failReason, err.Error())) logger.Log("error", ErrShouldSync.Error(), "err", err.Error()) + return hr, ErrShouldSync } - return hr, ErrShouldSync + return hr, nil + } + if diff { + status.UnsetCondition(r.helmReleaseClient.HelmReleases(hr.Namespace), hr, v1.HelmReleaseRolledBack) } // `shouldSync` above has already validated the YAML output of our @@ -302,38 +307,40 @@ func (r *Release) Uninstall(client helm.Client, hr *v1.HelmRelease) { // with Helm. The cheapest checks which do not require a dry-run are // consulted first (e.g. is this our first sync, have we already seen // this revision of the resource); before running the dry-run release to -// determine if any undefined mutations have occurred. +// determine if any undefined mutations have occurred. It returns two +// booleans indicating if the release should be synced and if the reason +// it should happen is because of a diff, or an error. func shouldSync(logger log.Logger, client helm.Client, hr *v1.HelmRelease, curRel *helm.Release, - chartPath, revision string, values helm.Values, logDiffs bool) (bool, error) { + chartPath, revision string, values helm.Values, logDiffs bool) (bool, bool, error) { // Without valid YAML we will not get anywhere, return early. b, err := values.YAML() if err != nil { - return false, ErrComposingValues + return false, false, ErrComposingValues } // If there is no existing release, we should simply sync. if curRel == nil { logger.Log("info", "no existing release", "action", "install") - return true, nil + return true, false, nil } // If the release is not managed by our resource, we skip to avoid conflicts. if ok, resourceID := managedByHelmRelease(curRel, *hr); !ok { logger.Log("warning", "release appears to be managed by "+resourceID, "action", "skip") - return false, nil + return false, false, nil } // If the current state of the release does not allow us to safely upgrade, we skip. if s := curRel.Info.Status; !s.AllowsUpgrade() { logger.Log("warning", "unable to sync release with status "+s.String(), "action", "skip") - return false, nil + return false, false, nil } // If we have not processed this generation of the release, we should sync. if !status.HasSynced(*hr) { logger.Log("info", "release has not yet been processed", "action", "upgrade") - return true, nil + return true, true, nil } // Next, we perform a dry-run upgrade and compare the result against the @@ -342,16 +349,20 @@ func shouldSync(logger log.Logger, client helm.Client, hr *v1.HelmRelease, curRe logger.Log("info", "performing dry-run upgrade to see if release has diverged") desRel, err := client.UpgradeFromPath(chartPath, hr.GetReleaseName(), b, helm.UpgradeOptions{DryRun: true}) if err != nil { - return false, err + return false, false, err } var vDiff, cDiff string switch { case status.HasRolledBack(*hr): + if status.ShouldRetryUpgrade(*hr) { + logger.Log("info", "release has been rolled back", "rollbackCount", hr.Status.RollbackCount, "maxRetries", hr.Spec.Rollback.GetMaxRetries(), "action", "upgrade") + return true, false, nil + } logger.Log("info", "release has been rolled back, comparing dry-run output with latest failed release") rels, err := client.History(hr.GetReleaseName(), helm.HistoryOptions{Namespace: hr.GetTargetNamespace()}) if err != nil { - return false, err + return false, false, err } for _, r := range rels { if r.Info.Status == helm.StatusFailed { @@ -377,7 +388,8 @@ func shouldSync(logger log.Logger, client helm.Client, hr *v1.HelmRelease, curRe logger.Log("info", "no changes", "action", "skip") } - return vDiff != "" || cDiff != "", nil + diff := vDiff != "" || cDiff != "" + return diff, diff, nil } // compareRelease compares the values and charts of the two given diff --git a/pkg/status/conditions.go b/pkg/status/conditions.go index 9f22af932..c3997481d 100644 --- a/pkg/status/conditions.go +++ b/pkg/status/conditions.go @@ -1,19 +1,19 @@ package status import ( - "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/util/retry" - helmfluxv1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1" + "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1" v1client "github.com/fluxcd/helm-operator/pkg/client/clientset/versioned/typed/helm.fluxcd.io/v1" ) // NewCondition creates a new HelmReleaseCondition. -func NewCondition(conditionType helmfluxv1.HelmReleaseConditionType, status v1.ConditionStatus, - reason, message string) helmfluxv1.HelmReleaseCondition { +func NewCondition(conditionType v1.HelmReleaseConditionType, status corev1.ConditionStatus, + reason, message string) v1.HelmReleaseCondition { - return helmfluxv1.HelmReleaseCondition{ + return v1.HelmReleaseCondition{ Type: conditionType, Status: status, LastUpdateTime: metav1.Now(), @@ -23,9 +23,20 @@ func NewCondition(conditionType helmfluxv1.HelmReleaseConditionType, status v1.C } } +// GetCondition returns the condition with the given type. +func GetCondition(status v1.HelmReleaseStatus, conditionType v1.HelmReleaseConditionType) *v1.HelmReleaseCondition { + + for i := range status.Conditions { + c := status.Conditions[i] + if c.Type == conditionType { + return &c + } + } + return nil +} + // SetCondition updates the HelmRelease to include the given condition. -func SetCondition(client v1client.HelmReleaseInterface, hr *helmfluxv1.HelmRelease, - condition helmfluxv1.HelmReleaseCondition) error { +func SetCondition(client v1client.HelmReleaseInterface, hr *v1.HelmRelease, condition v1.HelmReleaseCondition) error { firstTry := true err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { @@ -45,6 +56,13 @@ func SetCondition(client v1client.HelmReleaseInterface, hr *helmfluxv1.HelmRelea newConditions := filterOutCondition(cHr.Status.Conditions, condition.Type) cHr.Status.Conditions = append(newConditions, condition) + switch { + case condition.Type == v1.HelmReleaseReleased && condition.Status == corev1.ConditionTrue: + cHr.Status.RollbackCount = 0 + case condition.Type == v1.HelmReleaseRolledBack && condition.Status == corev1.ConditionTrue: + cHr.Status.RollbackCount = hr.Status.RollbackCount + 1 + } + _, err = client.UpdateStatus(cHr) firstTry = false return @@ -52,25 +70,45 @@ func SetCondition(client v1client.HelmReleaseInterface, hr *helmfluxv1.HelmRelea return err } -// GetCondition returns the condition with the given type. -func GetCondition(status helmfluxv1.HelmReleaseStatus, - conditionType helmfluxv1.HelmReleaseConditionType) *helmfluxv1.HelmReleaseCondition { +// UnsetCondition updates the HelmRelease to exclude the given condition. +func UnsetCondition(client v1client.HelmReleaseInterface, + hr *v1.HelmRelease, conditionType v1.HelmReleaseConditionType) error { - for i := range status.Conditions { - c := status.Conditions[i] - if c.Type == conditionType { - return &c + firstTry := true + err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { + if !firstTry { + var getErr error + hr, getErr = client.Get(hr.Name, metav1.GetOptions{}) + if getErr != nil { + return getErr + } } - } - return nil + + if GetCondition(hr.Status, conditionType) == nil { + return + } + + cHr := hr.DeepCopy() + cHr.Status.Conditions = filterOutCondition(cHr.Status.Conditions, conditionType) + + switch { + case conditionType == v1.HelmReleaseRolledBack: + cHr.Status.RollbackCount = 0 + } + + _, err = client.UpdateStatus(cHr) + firstTry = false + return + }) + return err } // filterOutCondition returns a new slice of conditions without the // conditions of the given type. -func filterOutCondition(conditions []helmfluxv1.HelmReleaseCondition, - conditionType helmfluxv1.HelmReleaseConditionType) []helmfluxv1.HelmReleaseCondition { +func filterOutCondition(conditions []v1.HelmReleaseCondition, + conditionType v1.HelmReleaseConditionType) []v1.HelmReleaseCondition { - var newConditions []helmfluxv1.HelmReleaseCondition + var newConditions []v1.HelmReleaseCondition for _, c := range conditions { if c.Type == conditionType { continue diff --git a/pkg/status/status.go b/pkg/status/status.go index 713082d01..0c234ca8e 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -17,13 +17,13 @@ import ( "time" "github.com/go-kit/kit/log" - "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" kube "k8s.io/client-go/kubernetes" "k8s.io/client-go/util/retry" - helmfluxv1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1" + v1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1" ifclientset "github.com/fluxcd/helm-operator/pkg/client/clientset/versioned" v1client "github.com/fluxcd/helm-operator/pkg/client/clientset/versioned/typed/helm.fluxcd.io/v1" iflister "github.com/fluxcd/helm-operator/pkg/client/listers/helm.fluxcd.io/v1" @@ -89,7 +89,7 @@ bail: // SetReleaseStatus updates the status of the HelmRelease to the given // release name and/or release status. -func SetReleaseStatus(client v1client.HelmReleaseInterface, hr *helmfluxv1.HelmRelease, +func SetReleaseStatus(client v1client.HelmReleaseInterface, hr *v1.HelmRelease, releaseName, releaseStatus string) error { firstTry := true @@ -119,7 +119,7 @@ func SetReleaseStatus(client v1client.HelmReleaseInterface, hr *helmfluxv1.HelmR // SetReleaseRevision updates the revision in the status of the HelmRelease // to the given revision, and sets the current revision as the previous one. -func SetReleaseRevision(client v1client.HelmReleaseInterface, hr *helmfluxv1.HelmRelease, revision string) error { +func SetReleaseRevision(client v1client.HelmReleaseInterface, hr *v1.HelmRelease, revision string) error { firstTry := true err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { @@ -147,7 +147,7 @@ func SetReleaseRevision(client v1client.HelmReleaseInterface, hr *helmfluxv1.Hel // SetObservedGeneration updates the observed generation status of the // HelmRelease to the given generation. -func SetObservedGeneration(client v1client.HelmReleaseInterface, hr *helmfluxv1.HelmRelease, generation int64) error { +func SetObservedGeneration(client v1client.HelmReleaseInterface, hr *v1.HelmRelease, generation int64) error { firstTry := true err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { if !firstTry { @@ -174,21 +174,30 @@ func SetObservedGeneration(client v1client.HelmReleaseInterface, hr *helmfluxv1. // HasSynced returns if the HelmRelease has been processed by the // controller. -func HasSynced(hr helmfluxv1.HelmRelease) bool { +func HasSynced(hr v1.HelmRelease) bool { return hr.Status.ObservedGeneration >= hr.Generation } // HasRolledBack returns if the current generation of the HelmRelease // has been rolled back. -func HasRolledBack(hr helmfluxv1.HelmRelease) bool { +func HasRolledBack(hr v1.HelmRelease) bool { if !HasSynced(hr) { return false } - rolledBack := GetCondition(hr.Status, helmfluxv1.HelmReleaseRolledBack) + rolledBack := GetCondition(hr.Status, v1.HelmReleaseRolledBack) if rolledBack == nil { return false } - return rolledBack.Status == v1.ConditionTrue + return rolledBack.Status == corev1.ConditionTrue +} + +// ShouldRetryUpgrade returns if the upgrade of a rolled back release should +// be retried. +func ShouldRetryUpgrade(hr v1.HelmRelease) bool { + if !hr.Spec.Rollback.Retry { + return false + } + return hr.Spec.Rollback.GetMaxRetries() == 0 || hr.Status.RollbackCount <= hr.Spec.Rollback.GetMaxRetries() }