diff --git a/Dockerfile b/Dockerfile index a9322a2..26dae61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,6 @@ RUN go mod download # Copy the go source COPY main.go main.go COPY api/ api/ -COPY pkg/ pkg/ COPY controllers/ controllers/ # Build @@ -23,7 +22,7 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -gcflags "all=-N -l" -a -o ma # ______________________________________________________________________________________________________________________ # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details -FROM gcr.io/distroless/static:nonroot as prod +FROM gcr.io/distroless/static:nonroot WORKDIR / COPY --from=builder /workspace/manager . USER 65532:65532 diff --git a/Makefile b/Makefile index d00ed2b..4892bb6 100644 --- a/Makefile +++ b/Makefile @@ -108,6 +108,10 @@ vet: ## Run go vet against code. test: manifests generate fmt vet envtest ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out + +.PHONY: pretest +pretest: manifests generate fmt vet envtest ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" ##@ Build .PHONY: build @@ -120,11 +124,7 @@ run: manifests generate fmt vet ## Run a controller from your host. .PHONY: docker-build docker-build: test ## Build docker image with the manager. - docker build -t ${IMG} --target prod . - -.PHONY: docker-build-debug -docker-build-debug: test ## Build docker image with the manager. - docker build -t ${IMG} --target debug . + docker build -t ${IMG} .PHONY: docker-push docker-push: ## Push docker image with the manager. diff --git a/README.md b/README.md index 30d0497..1ca754e 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ To create a new CRD with the corresponding Go files: ```console operator-sdk create api --group core --version v1alpha1 --kind Application --resource=true --controller=true --namespaced=true ``` +## Prerequisite +This project is based on golang 1.17. Make sure your GoRoot is configured to 1.17. If any other version then you may face issues. ## Debugging Since this project is a Kubernetes operator triggered by resource creation, debugging can be done using a remote debugger. diff --git a/api/v1alpha1/application_types.go b/api/v1alpha1/application_types.go index 372fed2..ef2d0e7 100644 --- a/api/v1alpha1/application_types.go +++ b/api/v1alpha1/application_types.go @@ -16,12 +16,29 @@ package v1alpha1 import ( "github.com/fluxcd/helm-controller/api/v2beta1" + "github.com/fluxcd/pkg/apis/meta" + apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. +type Metadata struct { + Labels map[string]string `json:"labels,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` +} + +type ReleaseTemplate struct { + // Metadata to be applied to the resources created by the Application Controller + // +optional + Metadata `json:"metadata,omitempty"` + + // Spec to be applied to the Helm Release resource created by the Application Controller + // +required + Spec v2beta1.HelmReleaseSpec `json:"spec,omitempty"` +} + // ApplicationSpec defines the desired state of Application type ApplicationSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster @@ -36,9 +53,9 @@ type ApplicationSpec struct { // +optional PreRenderer PreRenderer `json:"preRenderer,omitempty"` - // Helm Release spec + // Template of Release metadata and spec needed for the resources created by the Application Controller // +required - Spec v2beta1.HelmReleaseSpec `json:"spec,omitempty"` + Template ReleaseTemplate `json:"template,omitempty"` } // ApplicationStatus defines the observed state of Application @@ -54,42 +71,18 @@ type ApplicationStatus struct { // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` - // LastAppliedRevision is the revision of the last successfully applied source. - // +optional - LastAppliedRevision string `json:"lastAppliedRevision,omitempty"` - - // LastAttemptedRevision is the revision of the last reconciliation attempt. - // +optional - LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty"` - - // LastAttemptedValuesChecksum is the SHA1 checksum of the values of the last - // reconciliation attempt. + // HelmReleaseResourceVersion is the helm release resource version // +optional - LastAttemptedValuesChecksum string `json:"lastAttemptedValuesChecksum,omitempty"` + HelmReleaseResourceVersion string `json:"helmReleaseResourceVersion,omitempty"` - // LastApplicationRevision is the revision of the last successful Application. + // ValuesResourceVersion is the resource version of the resource that contains the helm values // +optional - LastReleaseRevision int `json:"lastReleaseRevision,omitempty"` - - // HelmChart is the namespaced name of the HelmChart resource created by - // the operator. - // +optional - HelmChart string `json:"helmChart,omitempty"` + ValuesResourceVersion string `json:"valuesResourceVersion,omitempty"` // Failures is the reconciliation failure count against the latest desired // state. It is reset after a successful reconciliation. // +optional Failures int64 `json:"failures,omitempty"` - - // InstallFailures is the install failure count against the latest desired - // state. It is reset after a successful reconciliation. - // +optional - InstallFailures int64 `json:"installFailures,omitempty"` - - // UpgradeFailures is the upgrade failure count against the latest desired - // state. It is reset after a successful reconciliation. - // +optional - UpgradeFailures int64 `json:"upgradeFailures,omitempty"` } // +genclient @@ -105,9 +98,8 @@ type ApplicationStatus struct { type Application struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec ApplicationSpec `json:"spec,omitempty"` - Status ApplicationStatus `json:"status,omitempty"` + Spec ApplicationSpec `json:"spec,omitempty"` + Status ApplicationStatus `json:"status,omitempty"` } //+kubebuilder:object:root=true @@ -122,3 +114,24 @@ type ApplicationList struct { func init() { SchemeBuilder.Register(&Application{}, &ApplicationList{}) } + +func AppInProgressStatus(application *Application) { + application.Status.Conditions = []metav1.Condition{} + condition := metav1.Condition{ + Type: meta.ReadyCondition, + Status: metav1.ConditionUnknown, + Reason: meta.ProgressingReason, + Message: "Reconciliation in progress", + } + apimeta.SetStatusCondition(&application.Status.Conditions, condition) +} + +func AppErrorStatus(application *Application, error string) { + condition := metav1.Condition{ + Type: meta.ReadyCondition, + Status: metav1.ConditionFalse, + Reason: meta.FailedReason, + Message: error, + } + apimeta.SetStatusCondition(&application.Status.Conditions, condition) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index a0364a5..18e0dfb 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -94,7 +94,7 @@ func (in *ApplicationSpec) DeepCopyInto(out *ApplicationSpec) { } } out.PreRenderer = in.PreRenderer - in.Spec.DeepCopyInto(&out.Spec) + in.Template.DeepCopyInto(&out.Template) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSpec. @@ -129,6 +129,35 @@ func (in *ApplicationStatus) DeepCopy() *ApplicationStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Metadata) DeepCopyInto(out *Metadata) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Metadata. +func (in *Metadata) DeepCopy() *Metadata { + if in == nil { + return nil + } + out := new(Metadata) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PreRenderer) DeepCopyInto(out *PreRenderer) { *out = *in @@ -143,3 +172,20 @@ func (in *PreRenderer) DeepCopy() *PreRenderer { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReleaseTemplate) DeepCopyInto(out *ReleaseTemplate) { + *out = *in + in.Metadata.DeepCopyInto(&out.Metadata) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReleaseTemplate. +func (in *ReleaseTemplate) DeepCopy() *ReleaseTemplate { + if in == nil { + return nil + } + out := new(ReleaseTemplate) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/core.expediagroup.com_applications.yaml b/config/crd/bases/core.expediagroup.com_applications.yaml index ccaaac5..8aa905a 100644 --- a/config/crd/bases/core.expediagroup.com_applications.yaml +++ b/config/crd/bases/core.expediagroup.com_applications.yaml @@ -82,685 +82,726 @@ spec: minLength: 2 type: string type: object - spec: - description: Helm Release spec + template: + description: Release template of metadata and spec needed for the + resources created by the Application Controller properties: - chart: - description: Chart defines the template of the v1beta2.HelmChart - that should be created for this HelmRelease. + metadata: + description: Metadata to be applied to the resources created by + the Application Controller properties: - spec: - description: Spec holds the template for the v1beta2.HelmChartSpec - for this HelmRelease. + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + spec: + description: Spec to be applied to the Helm Release resource created + by the Application Controller + properties: + chart: + description: Chart defines the template of the v1beta2.HelmChart + that should be created for this HelmRelease. properties: - chart: - description: The name or path the Helm chart is available - at in the SourceRef. - type: string - interval: - description: Interval at which to check the v1beta2.Source - for updates. Defaults to 'HelmReleaseSpec.Interval'. - type: string - reconcileStrategy: - default: ChartVersion - description: Determines what enables the creation of a - new artifact. Valid values are ('ChartVersion', 'Revision'). - See the documentation of the values for an explanation - on their behavior. Defaults to ChartVersion when omitted. - enum: - - ChartVersion - - Revision - type: string - sourceRef: - description: The name and namespace of the v1beta2.Source - the chart is available at. + spec: + description: Spec holds the template for the v1beta2.HelmChartSpec + for this HelmRelease. properties: - apiVersion: - description: APIVersion of the referent. + chart: + description: The name or path the Helm chart is available + at in the SourceRef. + type: string + interval: + description: Interval at which to check the v1beta2.Source + for updates. Defaults to 'HelmReleaseSpec.Interval'. type: string - kind: - description: Kind of the referent. + reconcileStrategy: + default: ChartVersion + description: Determines what enables the creation + of a new artifact. Valid values are ('ChartVersion', + 'Revision'). See the documentation of the values + for an explanation on their behavior. Defaults to + ChartVersion when omitted. enum: - - HelmRepository - - GitRepository - - Bucket + - ChartVersion + - Revision type: string - name: - description: Name of the referent. - maxLength: 253 - minLength: 1 + sourceRef: + description: The name and namespace of the v1beta2.Source + the chart is available at. + properties: + apiVersion: + description: APIVersion of the referent. + type: string + kind: + description: Kind of the referent. + enum: + - HelmRepository + - GitRepository + - Bucket + type: string + name: + description: Name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace of the referent. + maxLength: 63 + minLength: 1 + type: string + required: + - name + type: object + valuesFile: + description: Alternative values file to use as the + default chart values, expected to be a relative + path in the SourceRef. Deprecated in favor of ValuesFiles, + for backwards compatibility the file defined here + is merged before the ValuesFiles items. Ignored + when omitted. type: string - namespace: - description: Namespace of the referent. - maxLength: 63 - minLength: 1 + valuesFiles: + description: Alternative list of values files to use + as the chart values (values.yaml is not included + by default), expected to be a relative path in the + SourceRef. Values files are merged in the order + of this list with the last file overriding the first. + Ignored when omitted. + items: + type: string + type: array + version: + default: '*' + description: Version semver expression, ignored for + charts from v1beta2.GitRepository and v1beta2.Bucket + sources. Defaults to latest when omitted. type: string required: - - name + - chart + - sourceRef type: object - valuesFile: - description: Alternative values file to use as the default - chart values, expected to be a relative path in the - SourceRef. Deprecated in favor of ValuesFiles, for backwards - compatibility the file defined here is merged before - the ValuesFiles items. Ignored when omitted. - type: string - valuesFiles: - description: Alternative list of values files to use as - the chart values (values.yaml is not included by default), - expected to be a relative path in the SourceRef. Values - files are merged in the order of this list with the - last file overriding the first. Ignored when omitted. - items: - type: string - type: array - version: - default: '*' - description: Version semver expression, ignored for charts - from v1beta2.GitRepository and v1beta2.Bucket sources. - Defaults to latest when omitted. - type: string required: - - chart - - sourceRef + - spec type: object - required: - - spec - type: object - dependsOn: - description: DependsOn may contain a meta.NamespacedObjectReference - slice with references to HelmRelease resources that must be - ready before this HelmRelease can be reconciled. - items: - description: NamespacedObjectReference contains enough information - to locate the referenced Kubernetes resource object in any - namespace. - properties: - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, when not specified - it acts as LocalObjectReference. - type: string - required: - - name - type: object - type: array - install: - description: Install holds the configuration for Helm install - actions for this HelmRelease. - properties: - crds: - description: "CRDs upgrade CRDs from the Helm Chart's crds - directory according to the CRD upgrade policy provided here. - Valid values are `Skip`, `Create` or `CreateReplace`. Default - is `Create` and if omitted CRDs are installed but not updated. - \n Skip: do neither install nor replace (update) any CRDs. - \n Create: new CRDs are created, existing CRDs are neither - updated nor deleted. \n CreateReplace: new CRDs are created, - existing CRDs are updated (replaced) but not deleted. \n - By default, CRDs are applied (installed) during Helm install - action. With this option users can opt-in to CRD replace - existing CRDs on Helm install actions, which is not (yet) - natively supported by Helm. https://helm.sh/docs/chart_best_practices/custom_resource_definitions." - enum: - - Skip - - Create - - CreateReplace - type: string - createNamespace: - description: CreateNamespace tells the Helm install action - to create the HelmReleaseSpec.TargetNamespace if it does - not exist yet. On uninstall, the namespace will not be garbage - collected. - type: boolean - disableHooks: - description: DisableHooks prevents hooks from running during - the Helm install action. - type: boolean - disableOpenAPIValidation: - description: DisableOpenAPIValidation prevents the Helm install - action from validating rendered templates against the Kubernetes - OpenAPI Schema. - type: boolean - disableWait: - description: DisableWait disables the waiting for resources - to be ready after a Helm install has been performed. - type: boolean - disableWaitForJobs: - description: DisableWaitForJobs disables waiting for jobs - to complete after a Helm install has been performed. - type: boolean - remediation: - description: Remediation holds the remediation configuration - for when the Helm install action for the HelmRelease fails. - The default is to not perform any action. + dependsOn: + description: DependsOn may contain a meta.NamespacedObjectReference + slice with references to HelmRelease resources that must + be ready before this HelmRelease can be reconciled. + items: + description: NamespacedObjectReference contains enough information + to locate the referenced Kubernetes resource object in + any namespace. + properties: + name: + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, when not specified + it acts as LocalObjectReference. + type: string + required: + - name + type: object + type: array + install: + description: Install holds the configuration for Helm install + actions for this HelmRelease. properties: - ignoreTestFailures: - description: IgnoreTestFailures tells the controller to - skip remediation when the Helm tests are run after an - install action but fail. Defaults to 'Test.IgnoreFailures'. + crds: + description: "CRDs upgrade CRDs from the Helm Chart's + crds directory according to the CRD upgrade policy provided + here. Valid values are `Skip`, `Create` or `CreateReplace`. + Default is `Create` and if omitted CRDs are installed + but not updated. \n Skip: do neither install nor replace + (update) any CRDs. \n Create: new CRDs are created, + existing CRDs are neither updated nor deleted. \n CreateReplace: + new CRDs are created, existing CRDs are updated (replaced) + but not deleted. \n By default, CRDs are applied (installed) + during Helm install action. With this option users can + opt-in to CRD replace existing CRDs on Helm install + actions, which is not (yet) natively supported by Helm. + https://helm.sh/docs/chart_best_practices/custom_resource_definitions." + enum: + - Skip + - Create + - CreateReplace + type: string + createNamespace: + description: CreateNamespace tells the Helm install action + to create the HelmReleaseSpec.TargetNamespace if it + does not exist yet. On uninstall, the namespace will + not be garbage collected. + type: boolean + disableHooks: + description: DisableHooks prevents hooks from running + during the Helm install action. + type: boolean + disableOpenAPIValidation: + description: DisableOpenAPIValidation prevents the Helm + install action from validating rendered templates against + the Kubernetes OpenAPI Schema. + type: boolean + disableWait: + description: DisableWait disables the waiting for resources + to be ready after a Helm install has been performed. + type: boolean + disableWaitForJobs: + description: DisableWaitForJobs disables waiting for jobs + to complete after a Helm install has been performed. + type: boolean + remediation: + description: Remediation holds the remediation configuration + for when the Helm install action for the HelmRelease + fails. The default is to not perform any action. + properties: + ignoreTestFailures: + description: IgnoreTestFailures tells the controller + to skip remediation when the Helm tests are run + after an install action but fail. Defaults to 'Test.IgnoreFailures'. + type: boolean + remediateLastFailure: + description: RemediateLastFailure tells the controller + to remediate the last failure, when no retries remain. + Defaults to 'false'. + type: boolean + retries: + description: Retries is the number of retries that + should be attempted on failures before bailing. + Remediation, using an uninstall, is performed between + each attempt. Defaults to '0', a negative integer + equals to unlimited retries. + type: integer + type: object + replace: + description: Replace tells the Helm install action to + re-use the 'ReleaseName', but only if that name is a + deleted release which remains in the history. type: boolean - remediateLastFailure: - description: RemediateLastFailure tells the controller - to remediate the last failure, when no retries remain. - Defaults to 'false'. + skipCRDs: + description: "SkipCRDs tells the Helm install action to + not install any CRDs. By default, CRDs are installed + if not already present. \n Deprecated use CRD policy + (`crds`) attribute with value `Skip` instead." type: boolean - retries: - description: Retries is the number of retries that should - be attempted on failures before bailing. Remediation, - using an uninstall, is performed between each attempt. - Defaults to '0', a negative integer equals to unlimited - retries. - type: integer + timeout: + description: Timeout is the time to wait for any individual + Kubernetes operation (like Jobs for hooks) during the + performance of a Helm install action. Defaults to 'HelmReleaseSpec.Timeout'. + type: string type: object - replace: - description: Replace tells the Helm install action to re-use - the 'ReleaseName', but only if that name is a deleted release - which remains in the history. - type: boolean - skipCRDs: - description: "SkipCRDs tells the Helm install action to not - install any CRDs. By default, CRDs are installed if not - already present. \n Deprecated use CRD policy (`crds`) attribute - with value `Skip` instead." - type: boolean - timeout: - description: Timeout is the time to wait for any individual - Kubernetes operation (like Jobs for hooks) during the performance - of a Helm install action. Defaults to 'HelmReleaseSpec.Timeout'. + interval: + description: Interval at which to reconcile the Helm release. type: string - type: object - interval: - description: Interval at which to reconcile the Helm release. - type: string - kubeConfig: - description: KubeConfig for reconciling the HelmRelease on a remote - cluster. When used in combination with HelmReleaseSpec.ServiceAccountName, - forces the controller to act on behalf of that Service Account - at the target cluster. If the --default-service-account flag - is set, its value will be used as a controller level fallback - for when HelmReleaseSpec.ServiceAccountName is empty. - properties: - secretRef: - description: SecretRef holds the name to a secret that contains - a key with the kubeconfig file as the value. If no key is - specified the key will default to 'value'. The secret must - be in the same namespace as the HelmRelease. It is recommended - that the kubeconfig is self-contained, and the secret is - regularly updated if credentials such as a cloud-access-token - expire. Cloud specific `cmd-path` auth helpers will not - function without adding binaries and credentials to the - Pod that is responsible for reconciling the HelmRelease. + kubeConfig: + description: KubeConfig for reconciling the HelmRelease on + a remote cluster. When used in combination with HelmReleaseSpec.ServiceAccountName, + forces the controller to act on behalf of that Service Account + at the target cluster. If the --default-service-account + flag is set, its value will be used as a controller level + fallback for when HelmReleaseSpec.ServiceAccountName is + empty. properties: - key: - description: Key in the Secret, when not specified an - implementation-specific default key is used. - type: string - name: - description: Name of the Secret. - type: string - required: - - name + secretRef: + description: SecretRef holds the name to a secret that + contains a key with the kubeconfig file as the value. + If no key is specified the key will default to 'value'. + The secret must be in the same namespace as the HelmRelease. + It is recommended that the kubeconfig is self-contained, + and the secret is regularly updated if credentials such + as a cloud-access-token expire. Cloud specific `cmd-path` + auth helpers will not function without adding binaries + and credentials to the Pod that is responsible for reconciling + the HelmRelease. + properties: + key: + description: Key in the Secret, when not specified + an implementation-specific default key is used. + type: string + name: + description: Name of the Secret. + type: string + required: + - name + type: object type: object - type: object - maxHistory: - description: MaxHistory is the number of revisions saved by Helm - for this HelmRelease. Use '0' for an unlimited number of revisions; - defaults to '10'. - type: integer - postRenderers: - description: PostRenderers holds an array of Helm PostRenderers, - which will be applied in order of their definition. - items: - description: PostRenderer contains a Helm PostRenderer specification. - properties: - kustomize: - description: Kustomization to apply as PostRenderer. + maxHistory: + description: MaxHistory is the number of revisions saved by + Helm for this HelmRelease. Use '0' for an unlimited number + of revisions; defaults to '10'. + type: integer + postRenderers: + description: PostRenderers holds an array of Helm PostRenderers, + which will be applied in order of their definition. + items: + description: PostRenderer contains a Helm PostRenderer specification. properties: - images: - description: Images is a list of (image name, new name, - new tag or digest) for changing image names, tags - or digests. This can also be achieved with a patch, - but this operator is simpler to specify. - items: - description: Image contains an image name, a new name, - a new tag or digest, which will replace the original - name and tag. - properties: - digest: - description: Digest is the value used to replace - the original image tag. If digest is present - NewTag value is ignored. - type: string - name: - description: Name is a tag-less image name. - type: string - newName: - description: NewName is the value used to replace - the original name. - type: string - newTag: - description: NewTag is the value used to replace - the original tag. - type: string - required: - - name - type: object - type: array - patches: - description: Strategic merge and JSON patches, defined - as inline YAML objects, capable of targeting objects - based on kind, label and annotation selectors. - items: - description: Patch contains an inline StrategicMerge - or JSON6902 patch, and the target the patch should - be applied to. - properties: - patch: - description: Patch contains an inline StrategicMerge - patch or an inline JSON6902 patch with an array - of operation objects. - type: string - target: - description: Target points to the resources that - the patch document should be applied to. + kustomize: + description: Kustomization to apply as PostRenderer. + properties: + images: + description: Images is a list of (image name, new + name, new tag or digest) for changing image names, + tags or digests. This can also be achieved with + a patch, but this operator is simpler to specify. + items: + description: Image contains an image name, a new + name, a new tag or digest, which will replace + the original name and tag. properties: - annotationSelector: - description: AnnotationSelector is a string - that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource annotations. - type: string - group: - description: Group is the API group to select - resources from. Together with Version and - Kind it is capable of unambiguously identifying - and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - kind: - description: Kind of the API Group to select - resources from. Together with Group and - Version it is capable of unambiguously identifying - and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - labelSelector: - description: LabelSelector is a string that - follows the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource labels. + digest: + description: Digest is the value used to replace + the original image tag. If digest is present + NewTag value is ignored. type: string name: - description: Name to match resources with. + description: Name is a tag-less image name. type: string - namespace: - description: Namespace to select resources - from. + newName: + description: NewName is the value used to + replace the original name. type: string - version: - description: Version of the API Group to select - resources from. Together with Group and - Kind it is capable of unambiguously identifying - and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + newTag: + description: NewTag is the value used to replace + the original tag. type: string + required: + - name type: object - type: object - type: array - patchesJson6902: - description: JSON 6902 patches, defined as inline YAML - objects. - items: - description: JSON6902Patch contains a JSON6902 patch - and the target the patch should be applied to. - properties: - patch: - description: Patch contains the JSON6902 patch - document with an array of operation objects. - items: - description: JSON6902 is a JSON6902 operation - object. https://datatracker.ietf.org/doc/html/rfc6902#section-4 - properties: - from: - description: From contains a JSON-pointer - value that references a location within - the target document where the operation - is performed. The meaning of the value - depends on the value of Op, and is NOT - taken into account by all operations. - type: string - op: - description: Op indicates the operation - to perform. Its value MUST be one of "add", - "remove", "replace", "move", "copy", or - "test". https://datatracker.ietf.org/doc/html/rfc6902#section-4 - enum: - - test - - remove - - add - - replace - - move - - copy - type: string - path: - description: Path contains the JSON-pointer - value that references a location within - the target document where the operation - is performed. The meaning of the value - depends on the value of Op. - type: string - value: - description: Value contains a valid JSON - structure. The meaning of the value depends - on the value of Op, and is NOT taken into - account by all operations. - x-kubernetes-preserve-unknown-fields: true - required: - - op - - path - type: object - type: array - target: - description: Target points to the resources that - the patch document should be applied to. + type: array + patches: + description: Strategic merge and JSON patches, defined + as inline YAML objects, capable of targeting objects + based on kind, label and annotation selectors. + items: + description: Patch contains an inline StrategicMerge + or JSON6902 patch, and the target the patch + should be applied to. properties: - annotationSelector: - description: AnnotationSelector is a string - that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource annotations. - type: string - group: - description: Group is the API group to select - resources from. Together with Version and - Kind it is capable of unambiguously identifying - and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - kind: - description: Kind of the API Group to select - resources from. Together with Group and - Version it is capable of unambiguously identifying - and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - labelSelector: - description: LabelSelector is a string that - follows the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource labels. - type: string - name: - description: Name to match resources with. - type: string - namespace: - description: Namespace to select resources - from. - type: string - version: - description: Version of the API Group to select - resources from. Together with Group and - Kind it is capable of unambiguously identifying - and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + patch: + description: Patch contains an inline StrategicMerge + patch or an inline JSON6902 patch with an + array of operation objects. type: string + target: + description: Target points to the resources + that the patch document should be applied + to. + properties: + annotationSelector: + description: AnnotationSelector is a string + that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches with the resource annotations. + type: string + group: + description: Group is the API group to + select resources from. Together with + Version and Kind it is capable of unambiguously + identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + kind: + description: Kind of the API Group to + select resources from. Together with + Group and Version it is capable of unambiguously + identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + labelSelector: + description: LabelSelector is a string + that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches with the resource labels. + type: string + name: + description: Name to match resources with. + type: string + namespace: + description: Namespace to select resources + from. + type: string + version: + description: Version of the API Group + to select resources from. Together with + Group and Kind it is capable of unambiguously + identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + type: object type: object - required: - - patch - - target - type: object - type: array - patchesStrategicMerge: - description: Strategic merge patches, defined as inline - YAML objects. - items: - x-kubernetes-preserve-unknown-fields: true - type: array + type: array + patchesJson6902: + description: JSON 6902 patches, defined as inline + YAML objects. + items: + description: JSON6902Patch contains a JSON6902 + patch and the target the patch should be applied + to. + properties: + patch: + description: Patch contains the JSON6902 patch + document with an array of operation objects. + items: + description: JSON6902 is a JSON6902 operation + object. https://datatracker.ietf.org/doc/html/rfc6902#section-4 + properties: + from: + description: From contains a JSON-pointer + value that references a location within + the target document where the operation + is performed. The meaning of the value + depends on the value of Op, and is + NOT taken into account by all operations. + type: string + op: + description: Op indicates the operation + to perform. Its value MUST be one + of "add", "remove", "replace", "move", + "copy", or "test". https://datatracker.ietf.org/doc/html/rfc6902#section-4 + enum: + - test + - remove + - add + - replace + - move + - copy + type: string + path: + description: Path contains the JSON-pointer + value that references a location within + the target document where the operation + is performed. The meaning of the value + depends on the value of Op. + type: string + value: + description: Value contains a valid + JSON structure. The meaning of the + value depends on the value of Op, + and is NOT taken into account by all + operations. + x-kubernetes-preserve-unknown-fields: true + required: + - op + - path + type: object + type: array + target: + description: Target points to the resources + that the patch document should be applied + to. + properties: + annotationSelector: + description: AnnotationSelector is a string + that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches with the resource annotations. + type: string + group: + description: Group is the API group to + select resources from. Together with + Version and Kind it is capable of unambiguously + identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + kind: + description: Kind of the API Group to + select resources from. Together with + Group and Version it is capable of unambiguously + identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + labelSelector: + description: LabelSelector is a string + that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches with the resource labels. + type: string + name: + description: Name to match resources with. + type: string + namespace: + description: Namespace to select resources + from. + type: string + version: + description: Version of the API Group + to select resources from. Together with + Group and Kind it is capable of unambiguously + identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + type: object + required: + - patch + - target + type: object + type: array + patchesStrategicMerge: + description: Strategic merge patches, defined as + inline YAML objects. + items: + x-kubernetes-preserve-unknown-fields: true + type: array + type: object type: object - type: object - type: array - releaseName: - description: ReleaseName used for the Helm release. Defaults to - a composition of '[TargetNamespace-]Name'. - maxLength: 53 - minLength: 1 - type: string - rollback: - description: Rollback holds the configuration for Helm rollback - actions for this HelmRelease. - properties: - cleanupOnFail: - description: CleanupOnFail allows deletion of new resources - created during the Helm rollback action when it fails. - type: boolean - disableHooks: - description: DisableHooks prevents hooks from running during - the Helm rollback action. - type: boolean - disableWait: - description: DisableWait disables the waiting for resources - to be ready after a Helm rollback has been performed. - type: boolean - disableWaitForJobs: - description: DisableWaitForJobs disables waiting for jobs - to complete after a Helm rollback has been performed. - type: boolean - force: - description: Force forces resource updates through a replacement - strategy. - type: boolean - recreate: - description: Recreate performs pod restarts for the resource - if applicable. - type: boolean - timeout: - description: Timeout is the time to wait for any individual - Kubernetes operation (like Jobs for hooks) during the performance - of a Helm rollback action. Defaults to 'HelmReleaseSpec.Timeout'. + type: array + releaseName: + description: ReleaseName used for the Helm release. Defaults + to a composition of '[TargetNamespace-]Name'. + maxLength: 53 + minLength: 1 type: string - type: object - serviceAccountName: - description: The name of the Kubernetes service account to impersonate - when reconciling this HelmRelease. - type: string - storageNamespace: - description: StorageNamespace used for the Helm storage. Defaults - to the namespace of the HelmRelease. - maxLength: 63 - minLength: 1 - type: string - suspend: - description: Suspend tells the controller to suspend reconciliation - for this HelmRelease, it does not apply to already started reconciliations. - Defaults to false. - type: boolean - targetNamespace: - description: TargetNamespace to target when performing operations - for the HelmRelease. Defaults to the namespace of the HelmRelease. - maxLength: 63 - minLength: 1 - type: string - test: - description: Test holds the configuration for Helm test actions - for this HelmRelease. - properties: - enable: - description: Enable enables Helm test actions for this HelmRelease - after an Helm install or upgrade action has been performed. - type: boolean - ignoreFailures: - description: IgnoreFailures tells the controller to skip remediation - when the Helm tests are run but fail. Can be overwritten - for tests run after install or upgrade actions in 'Install.IgnoreTestFailures' - and 'Upgrade.IgnoreTestFailures'. - type: boolean - timeout: - description: Timeout is the time to wait for any individual - Kubernetes operation during the performance of a Helm test - action. Defaults to 'HelmReleaseSpec.Timeout'. + rollback: + description: Rollback holds the configuration for Helm rollback + actions for this HelmRelease. + properties: + cleanupOnFail: + description: CleanupOnFail allows deletion of new resources + created during the Helm rollback action when it fails. + type: boolean + disableHooks: + description: DisableHooks prevents hooks from running + during the Helm rollback action. + type: boolean + disableWait: + description: DisableWait disables the waiting for resources + to be ready after a Helm rollback has been performed. + type: boolean + disableWaitForJobs: + description: DisableWaitForJobs disables waiting for jobs + to complete after a Helm rollback has been performed. + type: boolean + force: + description: Force forces resource updates through a replacement + strategy. + type: boolean + recreate: + description: Recreate performs pod restarts for the resource + if applicable. + type: boolean + timeout: + description: Timeout is the time to wait for any individual + Kubernetes operation (like Jobs for hooks) during the + performance of a Helm rollback action. Defaults to 'HelmReleaseSpec.Timeout'. + type: string + type: object + serviceAccountName: + description: The name of the Kubernetes service account to + impersonate when reconciling this HelmRelease. type: string - type: object - timeout: - description: Timeout is the time to wait for any individual Kubernetes - operation (like Jobs for hooks) during the performance of a - Helm action. Defaults to '5m0s'. - type: string - uninstall: - description: Uninstall holds the configuration for Helm uninstall - actions for this HelmRelease. - properties: - disableHooks: - description: DisableHooks prevents hooks from running during - the Helm rollback action. - type: boolean - disableWait: - description: DisableWait disables waiting for all the resources - to be deleted after a Helm uninstall is performed. - type: boolean - keepHistory: - description: KeepHistory tells Helm to remove all associated - resources and mark the release as deleted, but retain the - release history. - type: boolean - timeout: - description: Timeout is the time to wait for any individual - Kubernetes operation (like Jobs for hooks) during the performance - of a Helm uninstall action. Defaults to 'HelmReleaseSpec.Timeout'. + storageNamespace: + description: StorageNamespace used for the Helm storage. Defaults + to the namespace of the HelmRelease. + maxLength: 63 + minLength: 1 type: string - type: object - upgrade: - description: Upgrade holds the configuration for Helm upgrade - actions for this HelmRelease. - properties: - cleanupOnFail: - description: CleanupOnFail allows deletion of new resources - created during the Helm upgrade action when it fails. + suspend: + description: Suspend tells the controller to suspend reconciliation + for this HelmRelease, it does not apply to already started + reconciliations. Defaults to false. type: boolean - crds: - description: "CRDs upgrade CRDs from the Helm Chart's crds - directory according to the CRD upgrade policy provided here. - Valid values are `Skip`, `Create` or `CreateReplace`. Default - is `Skip` and if omitted CRDs are neither installed nor - upgraded. \n Skip: do neither install nor replace (update) - any CRDs. \n Create: new CRDs are created, existing CRDs - are neither updated nor deleted. \n CreateReplace: new CRDs - are created, existing CRDs are updated (replaced) but not - deleted. \n By default, CRDs are not applied during Helm - upgrade action. With this option users can opt-in to CRD - upgrade, which is not (yet) natively supported by Helm. - https://helm.sh/docs/chart_best_practices/custom_resource_definitions." - enum: - - Skip - - Create - - CreateReplace + targetNamespace: + description: TargetNamespace to target when performing operations + for the HelmRelease. Defaults to the namespace of the HelmRelease. + maxLength: 63 + minLength: 1 type: string - disableHooks: - description: DisableHooks prevents hooks from running during - the Helm upgrade action. - type: boolean - disableOpenAPIValidation: - description: DisableOpenAPIValidation prevents the Helm upgrade - action from validating rendered templates against the Kubernetes - OpenAPI Schema. - type: boolean - disableWait: - description: DisableWait disables the waiting for resources - to be ready after a Helm upgrade has been performed. - type: boolean - disableWaitForJobs: - description: DisableWaitForJobs disables waiting for jobs - to complete after a Helm upgrade has been performed. - type: boolean - force: - description: Force forces resource updates through a replacement - strategy. - type: boolean - preserveValues: - description: PreserveValues will make Helm reuse the last - release's values and merge in overrides from 'Values'. Setting - this flag makes the HelmRelease non-declarative. - type: boolean - remediation: - description: Remediation holds the remediation configuration - for when the Helm upgrade action for the HelmRelease fails. - The default is to not perform any action. + test: + description: Test holds the configuration for Helm test actions + for this HelmRelease. properties: - ignoreTestFailures: - description: IgnoreTestFailures tells the controller to - skip remediation when the Helm tests are run after an - upgrade action but fail. Defaults to 'Test.IgnoreFailures'. + enable: + description: Enable enables Helm test actions for this + HelmRelease after an Helm install or upgrade action + has been performed. type: boolean - remediateLastFailure: - description: RemediateLastFailure tells the controller - to remediate the last failure, when no retries remain. - Defaults to 'false' unless 'Retries' is greater than - 0. + ignoreFailures: + description: IgnoreFailures tells the controller to skip + remediation when the Helm tests are run but fail. Can + be overwritten for tests run after install or upgrade + actions in 'Install.IgnoreTestFailures' and 'Upgrade.IgnoreTestFailures'. type: boolean - retries: - description: Retries is the number of retries that should - be attempted on failures before bailing. Remediation, - using 'Strategy', is performed between each attempt. - Defaults to '0', a negative integer equals to unlimited - retries. - type: integer - strategy: - description: Strategy to use for failure remediation. - Defaults to 'rollback'. - enum: - - rollback - - uninstall + timeout: + description: Timeout is the time to wait for any individual + Kubernetes operation during the performance of a Helm + test action. Defaults to 'HelmReleaseSpec.Timeout'. type: string type: object timeout: description: Timeout is the time to wait for any individual Kubernetes operation (like Jobs for hooks) during the performance - of a Helm upgrade action. Defaults to 'HelmReleaseSpec.Timeout'. + of a Helm action. Defaults to '5m0s'. type: string + uninstall: + description: Uninstall holds the configuration for Helm uninstall + actions for this HelmRelease. + properties: + disableHooks: + description: DisableHooks prevents hooks from running + during the Helm rollback action. + type: boolean + disableWait: + description: DisableWait disables waiting for all the + resources to be deleted after a Helm uninstall is performed. + type: boolean + keepHistory: + description: KeepHistory tells Helm to remove all associated + resources and mark the release as deleted, but retain + the release history. + type: boolean + timeout: + description: Timeout is the time to wait for any individual + Kubernetes operation (like Jobs for hooks) during the + performance of a Helm uninstall action. Defaults to + 'HelmReleaseSpec.Timeout'. + type: string + type: object + upgrade: + description: Upgrade holds the configuration for Helm upgrade + actions for this HelmRelease. + properties: + cleanupOnFail: + description: CleanupOnFail allows deletion of new resources + created during the Helm upgrade action when it fails. + type: boolean + crds: + description: "CRDs upgrade CRDs from the Helm Chart's + crds directory according to the CRD upgrade policy provided + here. Valid values are `Skip`, `Create` or `CreateReplace`. + Default is `Skip` and if omitted CRDs are neither installed + nor upgraded. \n Skip: do neither install nor replace + (update) any CRDs. \n Create: new CRDs are created, + existing CRDs are neither updated nor deleted. \n CreateReplace: + new CRDs are created, existing CRDs are updated (replaced) + but not deleted. \n By default, CRDs are not applied + during Helm upgrade action. With this option users can + opt-in to CRD upgrade, which is not (yet) natively supported + by Helm. https://helm.sh/docs/chart_best_practices/custom_resource_definitions." + enum: + - Skip + - Create + - CreateReplace + type: string + disableHooks: + description: DisableHooks prevents hooks from running + during the Helm upgrade action. + type: boolean + disableOpenAPIValidation: + description: DisableOpenAPIValidation prevents the Helm + upgrade action from validating rendered templates against + the Kubernetes OpenAPI Schema. + type: boolean + disableWait: + description: DisableWait disables the waiting for resources + to be ready after a Helm upgrade has been performed. + type: boolean + disableWaitForJobs: + description: DisableWaitForJobs disables waiting for jobs + to complete after a Helm upgrade has been performed. + type: boolean + force: + description: Force forces resource updates through a replacement + strategy. + type: boolean + preserveValues: + description: PreserveValues will make Helm reuse the last + release's values and merge in overrides from 'Values'. + Setting this flag makes the HelmRelease non-declarative. + type: boolean + remediation: + description: Remediation holds the remediation configuration + for when the Helm upgrade action for the HelmRelease + fails. The default is to not perform any action. + properties: + ignoreTestFailures: + description: IgnoreTestFailures tells the controller + to skip remediation when the Helm tests are run + after an upgrade action but fail. Defaults to 'Test.IgnoreFailures'. + type: boolean + remediateLastFailure: + description: RemediateLastFailure tells the controller + to remediate the last failure, when no retries remain. + Defaults to 'false' unless 'Retries' is greater + than 0. + type: boolean + retries: + description: Retries is the number of retries that + should be attempted on failures before bailing. + Remediation, using 'Strategy', is performed between + each attempt. Defaults to '0', a negative integer + equals to unlimited retries. + type: integer + strategy: + description: Strategy to use for failure remediation. + Defaults to 'rollback'. + enum: + - rollback + - uninstall + type: string + type: object + timeout: + description: Timeout is the time to wait for any individual + Kubernetes operation (like Jobs for hooks) during the + performance of a Helm upgrade action. Defaults to 'HelmReleaseSpec.Timeout'. + type: string + type: object + values: + description: Values holds the values for this Helm release. + x-kubernetes-preserve-unknown-fields: true + valuesFrom: + description: ValuesFrom holds references to resources containing + Helm values for this HelmRelease, and information about + how they should be merged. + items: + description: ValuesReference contains a reference to a resource + containing Helm values, and optionally the key they can + be found at. + properties: + kind: + description: Kind of the values referent, valid values + are ('Secret', 'ConfigMap'). + enum: + - Secret + - ConfigMap + type: string + name: + description: Name of the values referent. Should reside + in the same namespace as the referring resource. + maxLength: 253 + minLength: 1 + type: string + optional: + description: Optional marks this ValuesReference as + optional. When set, a not found error for the values + reference is ignored, but any ValuesKey, TargetPath + or transient error will still result in a reconciliation + failure. + type: boolean + targetPath: + description: TargetPath is the YAML dot notation path + the value should be merged at. When set, the ValuesKey + is expected to be a single flat value. Defaults to + 'None', which results in the values getting merged + at the root. + type: string + valuesKey: + description: ValuesKey is the data key where the values.yaml + or a specific value can be found at. Defaults to 'values.yaml'. + type: string + required: + - kind + - name + type: object + type: array + required: + - chart + - interval type: object - values: - description: Values holds the values for this Helm release. - x-kubernetes-preserve-unknown-fields: true - valuesFrom: - description: ValuesFrom holds references to resources containing - Helm values for this HelmRelease, and information about how - they should be merged. - items: - description: ValuesReference contains a reference to a resource - containing Helm values, and optionally the key they can be - found at. - properties: - kind: - description: Kind of the values referent, valid values are - ('Secret', 'ConfigMap'). - enum: - - Secret - - ConfigMap - type: string - name: - description: Name of the values referent. Should reside - in the same namespace as the referring resource. - maxLength: 253 - minLength: 1 - type: string - optional: - description: Optional marks this ValuesReference as optional. - When set, a not found error for the values reference is - ignored, but any ValuesKey, TargetPath or transient error - will still result in a reconciliation failure. - type: boolean - targetPath: - description: TargetPath is the YAML dot notation path the - value should be merged at. When set, the ValuesKey is - expected to be a single flat value. Defaults to 'None', - which results in the values getting merged at the root. - type: string - valuesKey: - description: ValuesKey is the data key where the values.yaml - or a specific value can be found at. Defaults to 'values.yaml'. - type: string - required: - - kind - - name - type: object - type: array - required: - - chart - - interval type: object type: object status: @@ -840,40 +881,18 @@ spec: the latest desired state. It is reset after a successful reconciliation. format: int64 type: integer - helmChart: - description: HelmChart is the namespaced name of the HelmChart resource - created by the operator. - type: string - installFailures: - description: InstallFailures is the install failure count against - the latest desired state. It is reset after a successful reconciliation. - format: int64 - type: integer - lastAppliedRevision: - description: LastAppliedRevision is the revision of the last successfully - applied source. - type: string - lastAttemptedRevision: - description: LastAttemptedRevision is the revision of the last reconciliation - attempt. + helmReleaseResourceVersion: + description: HelmReleaseResourceVersion is the helm release resource + version type: string - lastAttemptedValuesChecksum: - description: LastAttemptedValuesChecksum is the SHA1 checksum of the - values of the last reconciliation attempt. - type: string - lastReleaseRevision: - description: LastApplicationRevision is the revision of the last successful - Application. - type: integer observedGeneration: description: ObservedGeneration is the last observed generation. format: int64 type: integer - upgradeFailures: - description: UpgradeFailures is the upgrade failure count against - the latest desired state. It is reset after a successful reconciliation. - format: int64 - type: integer + valuesResourceVersion: + description: ValuesResourceVersion is the resource version of the + resource that contains the helm values + type: string type: object type: object served: true diff --git a/config/debug/default/kustomization.yaml b/config/debug/default/kustomization.yaml deleted file mode 100644 index a258cba..0000000 --- a/config/debug/default/kustomization.yaml +++ /dev/null @@ -1,74 +0,0 @@ -# Adds namespace to all resources. -namespace: overwhelm-system - -# Value of this field is prepended to the -# names of all resources, e.g. a deployment named -# "wordpress" becomes "alices-wordpress". -# Note that it should also match with the prefix (text before '-') of the namespace -# field above. -namePrefix: overwhelm- - -# Labels to add to all resources and selectors. -#commonLabels: -# someName: someValue - -bases: -- ../../crd -- ../../rbac -- ../../debug/manager -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in -# crd/kustomization.yaml -#- ../webhook -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. -#- ../certmanager -# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. -#- ../prometheus - -patchesStrategicMerge: -# Protect the /metrics endpoint by putting it behind auth. -# If you want your controller-manager to expose the /metrics -# endpoint w/o any authn/z, please comment the following line. -- manager_auth_proxy_patch.yaml - -# Mount the controller config file for loading manager configurations -# through a ComponentConfig type -#- manager_config_patch.yaml - -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in -# crd/kustomization.yaml -#- manager_webhook_patch.yaml - -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. -# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. -# 'CERTMANAGER' needs to be enabled to use ca injection -#- webhookcainjection_patch.yaml - -# the following config is for teaching kustomize how to do var substitution -vars: -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. -#- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR -# objref: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert # this name should match the one in certificate.yaml -# fieldref: -# fieldpath: metadata.namespace -#- name: CERTIFICATE_NAME -# objref: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert # this name should match the one in certificate.yaml -#- name: SERVICE_NAMESPACE # namespace of the service -# objref: -# kind: Service -# version: v1 -# name: webhook-service -# fieldref: -# fieldpath: metadata.namespace -#- name: SERVICE_NAME -# objref: -# kind: Service -# version: v1 -# name: webhook-service diff --git a/config/debug/default/manager_auth_proxy_patch.yaml b/config/debug/default/manager_auth_proxy_patch.yaml deleted file mode 100644 index 5ff7b92..0000000 --- a/config/debug/default/manager_auth_proxy_patch.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# This patch inject a sidecar container which is a HTTP proxy for the -# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. -apiVersion: apps/v1 -kind: Deployment -metadata: - name: controller-manager - namespace: system -spec: - template: - spec: - containers: - - name: kube-rbac-proxy - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 - args: - - "--secure-listen-address=0.0.0.0:8443" - - "--upstream=http://127.0.0.1:8080/" - - "--logtostderr=true" - - "--v=0" - ports: - - containerPort: 8443 - protocol: TCP - name: https - resources: - limits: - cpu: 500m - memory: 128Mi - requests: - cpu: 5m - memory: 64Mi - - name: manager - diff --git a/config/debug/default/manager_config_patch.yaml b/config/debug/default/manager_config_patch.yaml deleted file mode 100644 index 6c40015..0000000 --- a/config/debug/default/manager_config_patch.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: controller-manager - namespace: system -spec: - template: - spec: - containers: - - name: manager - args: - - "--config=controller_manager_config.yaml" - volumeMounts: - - name: manager-config - mountPath: /controller_manager_config.yaml - subPath: controller_manager_config.yaml - volumes: - - name: manager-config - configMap: - name: manager-config diff --git a/config/debug/manager/controller_manager_config.yaml b/config/debug/manager/controller_manager_config.yaml deleted file mode 100644 index d010f1a..0000000 --- a/config/debug/manager/controller_manager_config.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 -kind: ControllerManagerConfig -health: - healthProbeBindAddress: :8081 -metrics: - bindAddress: 127.0.0.1:8080 -webhook: - port: 9443 -leaderElection: - leaderElect: true - resourceName: e04e01ad.expediagroup.com diff --git a/config/debug/manager/kustomization.yaml b/config/debug/manager/kustomization.yaml deleted file mode 100644 index 2e78169..0000000 --- a/config/debug/manager/kustomization.yaml +++ /dev/null @@ -1,16 +0,0 @@ -resources: -- manager.yaml - -generatorOptions: - disableNameSuffixHash: true - -configMapGenerator: -- files: - - controller_manager_config.yaml - name: manager-config -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -images: -- name: controller - newName: kumo-docker-release-local.artylab.expedia.biz/library/app-operator - newTag: ab49a71e6e79e00707381a93097b7c3e6632b8bf diff --git a/config/debug/manager/manager.yaml b/config/debug/manager/manager.yaml deleted file mode 100644 index 2d3e7f9..0000000 --- a/config/debug/manager/manager.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - labels: - control-plane: controller-manager - name: system ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: controller-manager - namespace: system - labels: - control-plane: controller-manager -spec: - selector: - matchLabels: - control-plane: controller-manager - replicas: 1 - template: - metadata: - annotations: - kubectl.kubernetes.io/default-container: manager - labels: - control-plane: controller-manager - spec: - securityContext: - runAsNonRoot: true - containers: - - image: controller:latest - name: manager - securityContext: - allowPrivilegeEscalation: false - # TODO(user): Configure the resources accordingly based on the project requirements. - # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - resources: - limits: - cpu: 500m - memory: 128Mi - requests: - cpu: 10m - memory: 64Mi - serviceAccountName: controller-manager - terminationGracePeriodSeconds: 10 diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index da62b8f..3d660de 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -13,4 +13,4 @@ kind: Kustomization images: - name: controller newName: expediagroup.com/overwhelm - newTag: 0e2a8a0f0f0e44722918ca1214eebddfad627fbe + newTag: 791fe0cabd2751aaee2b430f56e0aa9112cf455f diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 883d2e7..cf11cec 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -27,7 +27,11 @@ spec: securityContext: runAsNonRoot: true containers: - - image: controller:latest + - command: + - /manager + args: + - --leader-elect + image: controller:latest name: manager securityContext: allowPrivilegeEscalation: false diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index f8086c0..f1d1938 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -43,3 +43,23 @@ rules: - get - patch - update +- apiGroups: + - helm.toolkit.fluxcd.io + resources: + - helmreleases + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - helm.toolkit.fluxcd.io + resources: + - helmreleases/status + verbs: + - get + - patch + - update diff --git a/config/samples/core_v1alpha1_application.yaml b/config/samples/core_v1alpha1_application.yaml index bbe7cfa..a2ce42f 100644 --- a/config/samples/core_v1alpha1_application.yaml +++ b/config/samples/core_v1alpha1_application.yaml @@ -5,22 +5,30 @@ kind: Application metadata: name: application-sample namespace: app-namespace + labels: + test: 'yes' spec: - spec: - interval: 10m - chart: - spec: - chart: some-chart-name - version: '0.0.1' - sourceRef: - name: public-helm-virtual - kind: HelmRepository + template: + metadata: + annotations: + ann: 'yes' + labels: + test: 'no' + spec: + interval: 4m + chart: + spec: + chart: some-chart-name + version: '0.0.1' + sourceRef: + name: public-helm-virtual + kind: HelmRepository data: values.yaml: |- deployment : hello-world - account : {{ .cluster.account }} - region : {{ .cluster.region }} - environment : {{ .egdata.environment }} +# account : {{ .cluster.account }} +# region : {{ .cluster.region }} +# environment : {{ .egdata.environment }} diff --git a/config/samples/hr_app-sample.yaml b/config/samples/hr_app-sample.yaml new file mode 100644 index 0000000..5881f26 --- /dev/null +++ b/config/samples/hr_app-sample.yaml @@ -0,0 +1,29 @@ +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: application-sample + namespace: app-namespace + ownerReferences: + - apiVersion: core.expediagroup.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: Application + name: application-sample + uid: 4513161f-bce3-4792-8e04-d74676a1a2e7 +spec: + interval: 3m + chart: + spec: + chart: sample-chart + version: 1.0.0 + sourceRef: + kind: HelmRepository + name: sample-chart + interval: 1m + upgrade: + remediation: + remediateLastFailure: true + test: + enable: false +status: + observedGeneration: 1 diff --git a/config/samples/hr_crd.yaml b/config/samples/hr_crd.yaml new file mode 100644 index 0000000..bb1b7d0 --- /dev/null +++ b/config/samples/hr_crd.yaml @@ -0,0 +1,844 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: helmreleases.helm.toolkit.fluxcd.io +spec: + group: helm.toolkit.fluxcd.io + names: + kind: HelmRelease + listKind: HelmReleaseList + plural: helmreleases + shortNames: + - hr + singular: helmrelease + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + name: v2beta1 + schema: + openAPIV3Schema: + description: HelmRelease is the Schema for the helmreleases API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: HelmReleaseSpec defines the desired state of a Helm release. + properties: + chart: + description: Chart defines the template of the v1beta2.HelmChart that + should be created for this HelmRelease. + properties: + spec: + description: Spec holds the template for the v1beta2.HelmChartSpec + for this HelmRelease. + properties: + chart: + description: The name or path the Helm chart is available + at in the SourceRef. + type: string + interval: + description: Interval at which to check the v1beta2.Source + for updates. Defaults to 'HelmReleaseSpec.Interval'. + type: string + reconcileStrategy: + default: ChartVersion + description: Determines what enables the creation of a new + artifact. Valid values are ('ChartVersion', 'Revision'). + See the documentation of the values for an explanation on + their behavior. Defaults to ChartVersion when omitted. + enum: + - ChartVersion + - Revision + type: string + sourceRef: + description: The name and namespace of the v1beta2.Source + the chart is available at. + properties: + apiVersion: + description: APIVersion of the referent. + type: string + kind: + description: Kind of the referent. + enum: + - HelmRepository + - GitRepository + - Bucket + type: string + name: + description: Name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace of the referent. + maxLength: 63 + minLength: 1 + type: string + required: + - name + type: object + valuesFile: + description: Alternative values file to use as the default + chart values, expected to be a relative path in the SourceRef. + Deprecated in favor of ValuesFiles, for backwards compatibility + the file defined here is merged before the ValuesFiles items. + Ignored when omitted. + type: string + valuesFiles: + description: Alternative list of values files to use as the + chart values (values.yaml is not included by default), expected + to be a relative path in the SourceRef. Values files are + merged in the order of this list with the last file overriding + the first. Ignored when omitted. + items: + type: string + type: array + version: + default: '*' + description: Version semver expression, ignored for charts + from v1beta2.GitRepository and v1beta2.Bucket sources. Defaults + to latest when omitted. + type: string + required: + - chart + - sourceRef + type: object + required: + - spec + type: object + dependsOn: + description: DependsOn may contain a meta.NamespacedObjectReference + slice with references to HelmRelease resources that must be ready + before this HelmRelease can be reconciled. + items: + description: NamespacedObjectReference contains enough information + to locate the referenced Kubernetes resource object in any namespace. + properties: + name: + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, when not specified it + acts as LocalObjectReference. + type: string + required: + - name + type: object + type: array + install: + description: Install holds the configuration for Helm install actions + for this HelmRelease. + properties: + crds: + description: "CRDs upgrade CRDs from the Helm Chart's crds directory + according to the CRD upgrade policy provided here. Valid values + are `Skip`, `Create` or `CreateReplace`. Default is `Create` + and if omitted CRDs are installed but not updated. \n Skip: + do neither install nor replace (update) any CRDs. \n Create: + new CRDs are created, existing CRDs are neither updated nor + deleted. \n CreateReplace: new CRDs are created, existing CRDs + are updated (replaced) but not deleted. \n By default, CRDs + are applied (installed) during Helm install action. With this + option users can opt-in to CRD replace existing CRDs on Helm + install actions, which is not (yet) natively supported by Helm. + https://helm.sh/docs/chart_best_practices/custom_resource_definitions." + enum: + - Skip + - Create + - CreateReplace + type: string + createNamespace: + description: CreateNamespace tells the Helm install action to + create the HelmReleaseSpec.TargetNamespace if it does not exist + yet. On uninstall, the namespace will not be garbage collected. + type: boolean + disableHooks: + description: DisableHooks prevents hooks from running during the + Helm install action. + type: boolean + disableOpenAPIValidation: + description: DisableOpenAPIValidation prevents the Helm install + action from validating rendered templates against the Kubernetes + OpenAPI Schema. + type: boolean + disableWait: + description: DisableWait disables the waiting for resources to + be ready after a Helm install has been performed. + type: boolean + disableWaitForJobs: + description: DisableWaitForJobs disables waiting for jobs to complete + after a Helm install has been performed. + type: boolean + remediation: + description: Remediation holds the remediation configuration for + when the Helm install action for the HelmRelease fails. The + default is to not perform any action. + properties: + ignoreTestFailures: + description: IgnoreTestFailures tells the controller to skip + remediation when the Helm tests are run after an install + action but fail. Defaults to 'Test.IgnoreFailures'. + type: boolean + remediateLastFailure: + description: RemediateLastFailure tells the controller to + remediate the last failure, when no retries remain. Defaults + to 'false'. + type: boolean + retries: + description: Retries is the number of retries that should + be attempted on failures before bailing. Remediation, using + an uninstall, is performed between each attempt. Defaults + to '0', a negative integer equals to unlimited retries. + type: integer + type: object + replace: + description: Replace tells the Helm install action to re-use the + 'ReleaseName', but only if that name is a deleted release which + remains in the history. + type: boolean + skipCRDs: + description: "SkipCRDs tells the Helm install action to not install + any CRDs. By default, CRDs are installed if not already present. + \n Deprecated use CRD policy (`crds`) attribute with value `Skip` + instead." + type: boolean + timeout: + description: Timeout is the time to wait for any individual Kubernetes + operation (like Jobs for hooks) during the performance of a + Helm install action. Defaults to 'HelmReleaseSpec.Timeout'. + type: string + type: object + interval: + description: Interval at which to reconcile the Helm release. + type: string + kubeConfig: + description: KubeConfig for reconciling the HelmRelease on a remote + cluster. When used in combination with HelmReleaseSpec.ServiceAccountName, + forces the controller to act on behalf of that Service Account at + the target cluster. If the --default-service-account flag is set, + its value will be used as a controller level fallback for when HelmReleaseSpec.ServiceAccountName + is empty. + properties: + secretRef: + description: SecretRef holds the name to a secret that contains + a key with the kubeconfig file as the value. If no key is specified + the key will default to 'value'. The secret must be in the same + namespace as the HelmRelease. It is recommended that the kubeconfig + is self-contained, and the secret is regularly updated if credentials + such as a cloud-access-token expire. Cloud specific `cmd-path` + auth helpers will not function without adding binaries and credentials + to the Pod that is responsible for reconciling the HelmRelease. + properties: + key: + description: Key in the Secret, when not specified an implementation-specific + default key is used. + type: string + name: + description: Name of the Secret. + type: string + required: + - name + type: object + type: object + maxHistory: + description: MaxHistory is the number of revisions saved by Helm for + this HelmRelease. Use '0' for an unlimited number of revisions; + defaults to '10'. + type: integer + postRenderers: + description: PostRenderers holds an array of Helm PostRenderers, which + will be applied in order of their definition. + items: + description: PostRenderer contains a Helm PostRenderer specification. + properties: + kustomize: + description: Kustomization to apply as PostRenderer. + properties: + images: + description: Images is a list of (image name, new name, + new tag or digest) for changing image names, tags or digests. + This can also be achieved with a patch, but this operator + is simpler to specify. + items: + description: Image contains an image name, a new name, + a new tag or digest, which will replace the original + name and tag. + properties: + digest: + description: Digest is the value used to replace the + original image tag. If digest is present NewTag + value is ignored. + type: string + name: + description: Name is a tag-less image name. + type: string + newName: + description: NewName is the value used to replace + the original name. + type: string + newTag: + description: NewTag is the value used to replace the + original tag. + type: string + required: + - name + type: object + type: array + patches: + description: Strategic merge and JSON patches, defined as + inline YAML objects, capable of targeting objects based + on kind, label and annotation selectors. + items: + description: Patch contains an inline StrategicMerge or + JSON6902 patch, and the target the patch should be applied + to. + properties: + patch: + description: Patch contains an inline StrategicMerge + patch or an inline JSON6902 patch with an array + of operation objects. + type: string + target: + description: Target points to the resources that the + patch document should be applied to. + properties: + annotationSelector: + description: AnnotationSelector is a string that + follows the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches with the resource annotations. + type: string + group: + description: Group is the API group to select + resources from. Together with Version and Kind + it is capable of unambiguously identifying and/or + selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + kind: + description: Kind of the API Group to select resources + from. Together with Group and Version it is + capable of unambiguously identifying and/or + selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + labelSelector: + description: LabelSelector is a string that follows + the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches with the resource labels. + type: string + name: + description: Name to match resources with. + type: string + namespace: + description: Namespace to select resources from. + type: string + version: + description: Version of the API Group to select + resources from. Together with Group and Kind + it is capable of unambiguously identifying and/or + selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + type: object + type: object + type: array + patchesJson6902: + description: JSON 6902 patches, defined as inline YAML objects. + items: + description: JSON6902Patch contains a JSON6902 patch and + the target the patch should be applied to. + properties: + patch: + description: Patch contains the JSON6902 patch document + with an array of operation objects. + items: + description: JSON6902 is a JSON6902 operation object. + https://datatracker.ietf.org/doc/html/rfc6902#section-4 + properties: + from: + description: From contains a JSON-pointer value + that references a location within the target + document where the operation is performed. + The meaning of the value depends on the value + of Op, and is NOT taken into account by all + operations. + type: string + op: + description: Op indicates the operation to perform. + Its value MUST be one of "add", "remove", + "replace", "move", "copy", or "test". https://datatracker.ietf.org/doc/html/rfc6902#section-4 + enum: + - test + - remove + - add + - replace + - move + - copy + type: string + path: + description: Path contains the JSON-pointer + value that references a location within the + target document where the operation is performed. + The meaning of the value depends on the value + of Op. + type: string + value: + description: Value contains a valid JSON structure. + The meaning of the value depends on the value + of Op, and is NOT taken into account by all + operations. + x-kubernetes-preserve-unknown-fields: true + required: + - op + - path + type: object + type: array + target: + description: Target points to the resources that the + patch document should be applied to. + properties: + annotationSelector: + description: AnnotationSelector is a string that + follows the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches with the resource annotations. + type: string + group: + description: Group is the API group to select + resources from. Together with Version and Kind + it is capable of unambiguously identifying and/or + selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + kind: + description: Kind of the API Group to select resources + from. Together with Group and Version it is + capable of unambiguously identifying and/or + selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + labelSelector: + description: LabelSelector is a string that follows + the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches with the resource labels. + type: string + name: + description: Name to match resources with. + type: string + namespace: + description: Namespace to select resources from. + type: string + version: + description: Version of the API Group to select + resources from. Together with Group and Kind + it is capable of unambiguously identifying and/or + selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + type: object + required: + - patch + - target + type: object + type: array + patchesStrategicMerge: + description: Strategic merge patches, defined as inline + YAML objects. + items: + x-kubernetes-preserve-unknown-fields: true + type: array + type: object + type: object + type: array + releaseName: + description: ReleaseName used for the Helm release. Defaults to a + composition of '[TargetNamespace-]Name'. + maxLength: 53 + minLength: 1 + type: string + rollback: + description: Rollback holds the configuration for Helm rollback actions + for this HelmRelease. + properties: + cleanupOnFail: + description: CleanupOnFail allows deletion of new resources created + during the Helm rollback action when it fails. + type: boolean + disableHooks: + description: DisableHooks prevents hooks from running during the + Helm rollback action. + type: boolean + disableWait: + description: DisableWait disables the waiting for resources to + be ready after a Helm rollback has been performed. + type: boolean + disableWaitForJobs: + description: DisableWaitForJobs disables waiting for jobs to complete + after a Helm rollback has been performed. + type: boolean + force: + description: Force forces resource updates through a replacement + strategy. + type: boolean + recreate: + description: Recreate performs pod restarts for the resource if + applicable. + type: boolean + timeout: + description: Timeout is the time to wait for any individual Kubernetes + operation (like Jobs for hooks) during the performance of a + Helm rollback action. Defaults to 'HelmReleaseSpec.Timeout'. + type: string + type: object + serviceAccountName: + description: The name of the Kubernetes service account to impersonate + when reconciling this HelmRelease. + type: string + storageNamespace: + description: StorageNamespace used for the Helm storage. Defaults + to the namespace of the HelmRelease. + maxLength: 63 + minLength: 1 + type: string + suspend: + description: Suspend tells the controller to suspend reconciliation + for this HelmRelease, it does not apply to already started reconciliations. + Defaults to false. + type: boolean + targetNamespace: + description: TargetNamespace to target when performing operations + for the HelmRelease. Defaults to the namespace of the HelmRelease. + maxLength: 63 + minLength: 1 + type: string + test: + description: Test holds the configuration for Helm test actions for + this HelmRelease. + properties: + enable: + description: Enable enables Helm test actions for this HelmRelease + after an Helm install or upgrade action has been performed. + type: boolean + ignoreFailures: + description: IgnoreFailures tells the controller to skip remediation + when the Helm tests are run but fail. Can be overwritten for + tests run after install or upgrade actions in 'Install.IgnoreTestFailures' + and 'Upgrade.IgnoreTestFailures'. + type: boolean + timeout: + description: Timeout is the time to wait for any individual Kubernetes + operation during the performance of a Helm test action. Defaults + to 'HelmReleaseSpec.Timeout'. + type: string + type: object + timeout: + description: Timeout is the time to wait for any individual Kubernetes + operation (like Jobs for hooks) during the performance of a Helm + action. Defaults to '5m0s'. + type: string + uninstall: + description: Uninstall holds the configuration for Helm uninstall + actions for this HelmRelease. + properties: + disableHooks: + description: DisableHooks prevents hooks from running during the + Helm rollback action. + type: boolean + disableWait: + description: DisableWait disables waiting for all the resources + to be deleted after a Helm uninstall is performed. + type: boolean + keepHistory: + description: KeepHistory tells Helm to remove all associated resources + and mark the release as deleted, but retain the release history. + type: boolean + timeout: + description: Timeout is the time to wait for any individual Kubernetes + operation (like Jobs for hooks) during the performance of a + Helm uninstall action. Defaults to 'HelmReleaseSpec.Timeout'. + type: string + type: object + upgrade: + description: Upgrade holds the configuration for Helm upgrade actions + for this HelmRelease. + properties: + cleanupOnFail: + description: CleanupOnFail allows deletion of new resources created + during the Helm upgrade action when it fails. + type: boolean + crds: + description: "CRDs upgrade CRDs from the Helm Chart's crds directory + according to the CRD upgrade policy provided here. Valid values + are `Skip`, `Create` or `CreateReplace`. Default is `Skip` and + if omitted CRDs are neither installed nor upgraded. \n Skip: + do neither install nor replace (update) any CRDs. \n Create: + new CRDs are created, existing CRDs are neither updated nor + deleted. \n CreateReplace: new CRDs are created, existing CRDs + are updated (replaced) but not deleted. \n By default, CRDs + are not applied during Helm upgrade action. With this option + users can opt-in to CRD upgrade, which is not (yet) natively + supported by Helm. https://helm.sh/docs/chart_best_practices/custom_resource_definitions." + enum: + - Skip + - Create + - CreateReplace + type: string + disableHooks: + description: DisableHooks prevents hooks from running during the + Helm upgrade action. + type: boolean + disableOpenAPIValidation: + description: DisableOpenAPIValidation prevents the Helm upgrade + action from validating rendered templates against the Kubernetes + OpenAPI Schema. + type: boolean + disableWait: + description: DisableWait disables the waiting for resources to + be ready after a Helm upgrade has been performed. + type: boolean + disableWaitForJobs: + description: DisableWaitForJobs disables waiting for jobs to complete + after a Helm upgrade has been performed. + type: boolean + force: + description: Force forces resource updates through a replacement + strategy. + type: boolean + preserveValues: + description: PreserveValues will make Helm reuse the last release's + values and merge in overrides from 'Values'. Setting this flag + makes the HelmRelease non-declarative. + type: boolean + remediation: + description: Remediation holds the remediation configuration for + when the Helm upgrade action for the HelmRelease fails. The + default is to not perform any action. + properties: + ignoreTestFailures: + description: IgnoreTestFailures tells the controller to skip + remediation when the Helm tests are run after an upgrade + action but fail. Defaults to 'Test.IgnoreFailures'. + type: boolean + remediateLastFailure: + description: RemediateLastFailure tells the controller to + remediate the last failure, when no retries remain. Defaults + to 'false' unless 'Retries' is greater than 0. + type: boolean + retries: + description: Retries is the number of retries that should + be attempted on failures before bailing. Remediation, using + 'Strategy', is performed between each attempt. Defaults + to '0', a negative integer equals to unlimited retries. + type: integer + strategy: + description: Strategy to use for failure remediation. Defaults + to 'rollback'. + enum: + - rollback + - uninstall + type: string + type: object + timeout: + description: Timeout is the time to wait for any individual Kubernetes + operation (like Jobs for hooks) during the performance of a + Helm upgrade action. Defaults to 'HelmReleaseSpec.Timeout'. + type: string + type: object + values: + description: Values holds the values for this Helm release. + x-kubernetes-preserve-unknown-fields: true + valuesFrom: + description: ValuesFrom holds references to resources containing Helm + values for this HelmRelease, and information about how they should + be merged. + items: + description: ValuesReference contains a reference to a resource + containing Helm values, and optionally the key they can be found + at. + properties: + kind: + description: Kind of the values referent, valid values are ('Secret', + 'ConfigMap'). + enum: + - Secret + - ConfigMap + type: string + name: + description: Name of the values referent. Should reside in the + same namespace as the referring resource. + maxLength: 253 + minLength: 1 + type: string + optional: + description: Optional marks this ValuesReference as optional. + When set, a not found error for the values reference is ignored, + but any ValuesKey, TargetPath or transient error will still + result in a reconciliation failure. + type: boolean + targetPath: + description: TargetPath is the YAML dot notation path the value + should be merged at. When set, the ValuesKey is expected to + be a single flat value. Defaults to 'None', which results + in the values getting merged at the root. + type: string + valuesKey: + description: ValuesKey is the data key where the values.yaml + or a specific value can be found at. Defaults to 'values.yaml'. + type: string + required: + - kind + - name + type: object + type: array + required: + - chart + - interval + type: object + status: + default: + observedGeneration: -1 + description: HelmReleaseStatus defines the observed state of a HelmRelease. + properties: + conditions: + description: Conditions holds the conditions for the HelmRelease. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // +listMapKey=type + \ Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + failures: + description: Failures is the reconciliation failure count against + the latest desired state. It is reset after a successful reconciliation. + format: int64 + type: integer + helmChart: + description: HelmChart is the namespaced name of the HelmChart resource + created by the controller for the HelmRelease. + type: string + installFailures: + description: InstallFailures is the install failure count against + the latest desired state. It is reset after a successful reconciliation. + format: int64 + type: integer + lastAppliedRevision: + description: LastAppliedRevision is the revision of the last successfully + applied source. + type: string + lastAttemptedRevision: + description: LastAttemptedRevision is the revision of the last reconciliation + attempt. + type: string + lastAttemptedValuesChecksum: + description: LastAttemptedValuesChecksum is the SHA1 checksum of the + values of the last reconciliation attempt. + type: string + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string + lastReleaseRevision: + description: LastReleaseRevision is the revision of the last successful + Helm release. + type: integer + observedGeneration: + description: ObservedGeneration is the last observed generation. + format: int64 + type: integer + upgradeFailures: + description: UpgradeFailures is the upgrade failure count against + the latest desired state. It is reset after a successful reconciliation. + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/controllers/application_controller.go b/controllers/application_controller.go index 3094178..024af93 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -20,10 +20,12 @@ import ( "errors" "regexp" "text/template" + "time" - corev1alpha1 "github.com/ExpediaGroup/overwhelm/api/v1alpha1" + "github.com/fluxcd/helm-controller/api/v2beta1" "github.com/go-logr/logr" - v1 "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -31,17 +33,22 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + + v1 "github.com/ExpediaGroup/overwhelm/api/v1alpha1" ) // ApplicationReconciler reconciles an Application object type ApplicationReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + RequeueInterval time.Duration + Retries int64 } const ( FinalizerName = "overwhelm.expediagroup.com/finalizer" ManagedBy = "app.kubernetes.io/managed-by" + Application ) var log logr.Logger @@ -50,6 +57,8 @@ var log logr.Logger //+kubebuilder:rbac:groups=core.expediagroup.com,resources=applications/status,verbs=get;update;patch //+kubebuilder:rbac:groups=core.expediagroup.com,resources=applications/finalizers,verbs=update //+kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases/status,verbs=get;update;patch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -62,27 +71,57 @@ var log logr.Logger // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.0/pkg/reconcile func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log = ctrllog.FromContext(ctx) - // name of our custom finalizer - - application := &corev1alpha1.Application{} - if err := r.Get(ctx, req.NamespacedName, application); err != nil { - log.Error(err, "Error reading application object") + application := &v1.Application{} + var err error + log.Info("Starting to read application") + if err = r.Get(ctx, req.NamespacedName, application); err != nil { + if !apierrors.IsNotFound(err) { + log.Error(err, "Error reading application object") + return ctrl.Result{}, err + } + return ctrl.Result{}, nil } if application.ObjectMeta.DeletionTimestamp.IsZero() { if !controllerutil.ContainsFinalizer(application, FinalizerName) { + patch := client.MergeFrom(application.DeepCopy()) controllerutil.AddFinalizer(application, FinalizerName) - if err := r.Update(ctx, application); err != nil { + if err = r.Patch(ctx, application, patch); err != nil { + log.Error(err, "Error adding a finalizer") return ctrl.Result{}, err } - if err := r.CreateOrUpdateResources(application, ctx); err != nil { - return ctrl.Result{}, err + } + // New Application and Application update are identified by gen and observed gen mismatch + if application.Status.ObservedGeneration != application.Generation { + application.Status.ObservedGeneration = application.Generation + application.Status.ValuesResourceVersion = "" + application.Status.HelmReleaseResourceVersion = "" + v1.AppInProgressStatus(application) + if err = r.patchStatus(ctx, application); err != nil { + return ctrl.Result{RequeueAfter: r.RequeueInterval}, err } } + // Create or Update resources (configmap and HR) if not already installed + if err = r.CreateOrUpdateResources(application, ctx); err != nil { + v1.AppErrorStatus(application, err.Error()) + if err1 := r.patchStatus(ctx, application); err1 != nil { + return ctrl.Result{RequeueAfter: r.RequeueInterval}, err1 + } + return ctrl.Result{}, err + } + + // At this point the Helm Release can be reconciled + err = r.reconcileHRStatus(application, ctx) + if err1 := r.patchStatus(ctx, application); err1 != nil { + log.Error(err1, "Error updating application status") + return ctrl.Result{}, err1 + } + return ctrl.Result{}, err } else { if controllerutil.ContainsFinalizer(application, FinalizerName) { - //Add any pre delete actions here. + // Pre Delete actions go here. + patch := client.MergeFrom(application.DeepCopy()) controllerutil.RemoveFinalizer(application, FinalizerName) - if err := r.Update(ctx, application); err != nil { + if err = r.Patch(ctx, application, patch); err != nil { return ctrl.Result{}, err } } @@ -90,55 +129,110 @@ func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, nil } +func (r *ApplicationReconciler) patchStatus(ctx context.Context, application *v1.Application) error { + key := client.ObjectKeyFromObject(application) + latest := &v1.Application{} + if err := r.Get(ctx, key, latest); err != nil { + return err + } + return r.Status().Patch(ctx, application, client.MergeFrom(latest)) +} + +func (r *ApplicationReconciler) reconcileHRStatus(application *v1.Application, ctx context.Context) error { + hr := v2beta1.HelmRelease{} + err := r.Get(ctx, types.NamespacedName{ + Namespace: application.Namespace, + Name: application.Name, + }, &hr) + if err == nil && hr.ResourceVersion != application.Status.HelmReleaseResourceVersion { + err = errors.New("HelmRelease not updated") + } + + if err != nil { + if application.Status.Failures == r.Retries { + if apierrors.IsNotFound(err) { + v1.AppErrorStatus(application, "Helm Release not created") + } else { + v1.AppErrorStatus(application, err.Error()) + } + return nil + } + application.Status.Failures++ + return err + } + application.Status.Failures = 0 + if hr.Status.ObservedGeneration != hr.Generation { + v1.AppErrorStatus(application, "updated Helm Release status not available") + return errors.New("HelmRelease status is not current") + } + application.Status.Conditions = hr.GetConditions() + return nil +} + // SetupWithManager sets up the controller with the Manager. func (r *ApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&corev1alpha1.Application{}). - Owns(&v1.ConfigMap{}). + For(&v1.Application{}). + Owns(&corev1.ConfigMap{}). + Owns(&v2beta1.HelmRelease{}). Complete(r) } -func (r *ApplicationReconciler) createOrUpdateConfigMap(application *corev1alpha1.Application, ctx context.Context) error { - if err := r.renderValues(application); err != nil { - log.Error(err, "error rendering values", "values", application.Spec.Data) - return err +func (r *ApplicationReconciler) createOrUpdateConfigMap(application *v1.Application, ctx context.Context) error { + currentCM := corev1.ConfigMap{} + currentCMError := r.Get(ctx, types.NamespacedName{ + Namespace: application.Namespace, + Name: application.Name, + }, ¤tCM) + + if currentCMError == nil && currentCM.ResourceVersion == application.Status.ValuesResourceVersion { + return nil } - cm := &v1.ConfigMap{ + + newCM := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: application.Name, Namespace: application.Namespace, - Labels: application.Labels, - Annotations: application.Annotations, + Labels: application.Spec.Template.Labels, + Annotations: application.Spec.Template.Annotations, }, Data: application.Spec.Data, } - if cm.Labels == nil { - cm.Labels = make(map[string]string) + if newCM.Labels == nil { + newCM.Labels = make(map[string]string) } - cm.Labels[ManagedBy] = "overwhelm" - if err := ctrl.SetControllerReference(application, cm, r.Scheme); err != nil { + newCM.Labels[ManagedBy] = "overwhelm" + if err := ctrl.SetControllerReference(application, newCM, r.Scheme); err != nil { return err } - currentCM := v1.ConfigMap{} - if err := r.Get(ctx, types.NamespacedName{ - Namespace: application.Namespace, - Name: application.Name, - }, ¤tCM); err != nil { - if err := r.Create(ctx, cm); err != nil { - log.Error(err, "error creating the configmap", "cm", cm) - return err - } - } else { - log.Info("updating configmap") - if err := r.Update(ctx, cm); err != nil { - log.Error(err, "error updating the configmap", "cm", cm) - return err + + if err := r.renderValues(application); err != nil { + log.Error(err, "error rendering values", "values", application.Spec.Data) + return err + } + + if currentCMError != nil { + if apierrors.IsNotFound(currentCMError) { + if err := r.Create(ctx, newCM); err != nil { + log.Error(err, "error creating the configmap", "configMap", newCM) + return err + } + application.Status.ValuesResourceVersion = newCM.ResourceVersion + return nil } + log.Error(currentCMError, "error retrieving current configmap if exists", "configMap", newCM) + return currentCMError } + + if err := r.Update(ctx, newCM); err != nil { + log.Error(err, "error updating the configmap", "configMap", newCM) + return err + } + application.Status.ValuesResourceVersion = newCM.ResourceVersion return nil } -func (r *ApplicationReconciler) renderValues(application *corev1alpha1.Application) error { +func (r *ApplicationReconciler) renderValues(application *v1.Application) error { values := application.Spec.Data leftDelimiter := "{{" rightDelimiter := "}}" @@ -150,6 +244,7 @@ func (r *ApplicationReconciler) renderValues(application *corev1alpha1.Applicati } // when delimiters are specified but only partially if preRenderer.LeftDelimiter == "" || preRenderer.RightDelimiter == "" { + // when custom delimiters are specified but only partially if preRenderer.LeftDelimiter != "" || preRenderer.RightDelimiter != "" { return errors.New("application preRenderer has partial delimiter information") } @@ -181,9 +276,52 @@ func isDelimValid(delim string) bool { return len(delim) == 2 && !r.MatchString(delim) } -func (r *ApplicationReconciler) CreateOrUpdateResources(application *corev1alpha1.Application, ctx context.Context) error { +func (r *ApplicationReconciler) CreateOrUpdateResources(application *v1.Application, ctx context.Context) error { if err := r.createOrUpdateConfigMap(application, ctx); err != nil { return err } + return r.createOrUpdateHelmRelease(application, ctx) +} + +func (r *ApplicationReconciler) createOrUpdateHelmRelease(application *v1.Application, ctx context.Context) error { + + newHR := &v2beta1.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: application.Name, + Namespace: application.Namespace, + Labels: application.Spec.Template.Labels, + Annotations: application.Spec.Template.Annotations, + }, + Spec: application.Spec.Template.Spec, + } + if err := ctrl.SetControllerReference(application, newHR, r.Scheme); err != nil { + return err + } + currentHR := &v2beta1.HelmRelease{} + if err := r.Get(ctx, types.NamespacedName{ + Namespace: application.Namespace, + Name: application.Name, + }, currentHR); err != nil { + if apierrors.IsNotFound(err) { + if err = r.Create(ctx, newHR); err != nil { + log.Error(err, "error creating the HelmRelease Resource") + return err + } + application.Status.HelmReleaseResourceVersion = newHR.ResourceVersion + return nil + } + log.Error(err, "error checking if current HelmRelease exists") + return err + } + if application.Status.HelmReleaseResourceVersion == "" || currentHR.ResourceVersion != application.Status.HelmReleaseResourceVersion { + newHR.ResourceVersion = currentHR.ResourceVersion + + if err := r.Update(ctx, newHR); err != nil { + log.Error(err, "error updating the HelmRelease Resource") + return err + } + application.Status.HelmReleaseResourceVersion = newHR.ResourceVersion + } + return nil } diff --git a/controllers/application_controller_test.go b/controllers/application_controller_test.go index a06d59c..6898e29 100644 --- a/controllers/application_controller_test.go +++ b/controllers/application_controller_test.go @@ -12,6 +12,7 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" ) var ctx context.Context @@ -28,19 +29,24 @@ var application = &v1alpha1.Application{ }, }, Spec: v1alpha1.ApplicationSpec{ - Spec: v2beta1.HelmReleaseSpec{ - Chart: v2beta1.HelmChartTemplate{ - Spec: v2beta1.HelmChartTemplateSpec{ - Chart: "good-chart", - Version: "0.0.1", - SourceRef: v2beta1.CrossNamespaceObjectReference{ - Kind: "HelmRepository", - Name: "public-helm-virtual", - }, - }}, - Interval: metav1.Duration{Duration: time.Millisecond * 250}, - ReleaseName: "hr-test", - Timeout: &metav1.Duration{Duration: time.Millisecond * 10}, + Template: v1alpha1.ReleaseTemplate{ + Metadata: v1alpha1.Metadata{ + Labels: map[string]string{"test-temp-label": "ok"}, + }, + Spec: v2beta1.HelmReleaseSpec{ + Chart: v2beta1.HelmChartTemplate{ + Spec: v2beta1.HelmChartTemplateSpec{ + Chart: "good-chart", + Version: "0.0.1", + SourceRef: v2beta1.CrossNamespaceObjectReference{ + Kind: "HelmRepository", + Name: "public-helm-virtual", + }, + }}, + Interval: metav1.Duration{Duration: time.Millisecond * 250}, + ReleaseName: "hr-test", + Timeout: &metav1.Duration{Duration: time.Millisecond * 10}, + }, }, Data: map[string]string{"values.yaml": "deployment : hello-world \naccount : {{ .cluster.account }}\nregion : {{ .cluster.region }}\nenvironment : {{ .egdata.environment }}"}, }, @@ -59,15 +65,19 @@ func LoadTestPrerenderData() { } func cmEquals(key client.ObjectKey, expectedCM *v1.ConfigMap) func() error { + var log = ctrllog.FromContext(ctx) + log.Info("cmKeys", "keys", key) return func() error { actual := &v1.ConfigMap{} + if err := k8sClient.Get(ctx, key, actual); err != nil { + log.Error(err, "error getting configMap") return err } - if Expect(actual.Data).Should(Equal(expectedCM.Data)) && Expect(actual.Labels).Should(Equal(expectedCM.Labels)) && - Expect(actual.OwnerReferences).Should(Not(BeNil())) { + Expect(actual.OwnerReferences).Should(Not(BeNil())) && + Expect(actual.OwnerReferences[0].UID).Should(Equal(expectedCM.OwnerReferences[0].UID)) { return nil } return errors.New("actual cm not equal to expected cm") @@ -79,26 +89,80 @@ var _ = Describe("Application controller", func() { LoadTestPrerenderData() Context("When creating an Application resource", func() { + It("Should Deploy Successfully", func() { + By("Creating a new ConfigMap and rendering it with default delimiter", func() { a := application.DeepCopy() a.Name = "a-app" Expect(k8sClient.Create(ctx, a)).Should(Succeed()) + key := client.ObjectKey{Name: a.Name, Namespace: a.Namespace} expected := &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: a.Name, Namespace: a.Namespace, - Labels: a.Labels, + Labels: map[string]string{"test-temp-label": "ok", "app.kubernetes.io/managed-by": "overwhelm"}, Annotations: a.Annotations, + OwnerReferences: []metav1.OwnerReference{{ + UID: a.GetUID(), + }}, }, + Data: map[string]string{"values.yaml": "deployment : hello-world \naccount : 1234\nregion : us-west-2\nenvironment : test"}, } - expected.Labels["app.kubernetes.io/managed-by"] = "overwhelm" + Eventually(cmEquals(key, expected), time.Second*5, time.Millisecond*500).Should(BeNil()) + hr := &v2beta1.HelmRelease{} + Eventually( + func(ctx context.Context, key client.ObjectKey, hr *v2beta1.HelmRelease) func() error { + return func() error { + if err := k8sClient.Get(ctx, key, hr); err != nil { + return err + } + if hr.OwnerReferences == nil || hr.OwnerReferences[0].UID != a.GetUID() { + return errors.New("HelmRelease has owner reference or has incorrect owner reference") + } + return nil + } + }(ctx, key, hr), time.Second*5, time.Second*2).Should(BeNil()) + }) + By("Updating Application Status with HR Status", func() { + a := application.DeepCopy() + a.Name = "a-app" + key := client.ObjectKey{Name: a.Name, Namespace: a.Namespace} + hr := &v2beta1.HelmRelease{} Eventually( - cmEquals(client.ObjectKey{Name: a.Name, Namespace: a.Namespace}, expected), - time.Second*5, time.Millisecond*500).Should(BeNil()) + func(ctx context.Context, key client.ObjectKey, hr *v2beta1.HelmRelease) func() error { + return func() error { + return k8sClient.Get(ctx, key, hr) + } + }(ctx, key, hr), time.Second*5, time.Second*2).Should(BeNil()) + hr.Status.ObservedGeneration = 1 + conditions := []metav1.Condition{{ + Type: "READY", + Status: metav1.ConditionStatus(v1.ConditionTrue), + ObservedGeneration: 1, + LastTransitionTime: metav1.NewTime(time.Now()), + Message: "Helm Release Reconciled", + Reason: "Ready", + }} + hr.SetConditions(conditions) + Expect(k8sClient.Status().Update(ctx, hr)).Should(BeNil()) + app := &v1alpha1.Application{} + Eventually( + func(ctx context.Context, key client.ObjectKey, app *v1alpha1.Application) func() error { + return func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Name: "a-app", Namespace: application.Namespace}, app); err != nil { + return err + } + if app.Status.Conditions[0].Status != metav1.ConditionStatus(v1.ConditionTrue) { + return errors.New("app status not updated with hr status") + } + return nil + } + }(ctx, key, app), time.Second*5, time.Second*2).Should(BeNil()) }) + By("Creating a new ConfigMap and rendering it with custom delimiter", func() { b := application.DeepCopy() b.Name = "b-app" @@ -109,37 +173,79 @@ var _ = Describe("Application controller", func() { } b.Spec.Data = map[string]string{"values.yaml": "deployment : hello-world \naccount : {{ .cluster.account }}\nregion : <% .cluster.region %>\nenvironment : {{ .egdata.environment }}"} Expect(k8sClient.Create(ctx, b)).Should(Succeed()) + key := client.ObjectKey{Name: b.Name, Namespace: b.Namespace} expected := &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: b.Name, - Namespace: b.Namespace, - Labels: b.Labels, - Annotations: b.Annotations, + Name: b.Name, + Namespace: b.Namespace, + Labels: map[string]string{"test-temp-label": "ok", "app.kubernetes.io/managed-by": "overwhelm"}, + OwnerReferences: []metav1.OwnerReference{{ + UID: b.GetUID(), + }}, }, Data: map[string]string{"values.yaml": "deployment : hello-world \naccount : {{ .cluster.account }}\nregion : us-west-2\nenvironment : {{ .egdata.environment }}"}, } - expected.Labels["app.kubernetes.io/managed-by"] = "overwhelm" + Eventually(cmEquals(key, expected), time.Second*5, time.Millisecond*500).Should(BeNil()) + }) + + By("Updating HelmRelease and configmap resources when application is updated", func() { + a := application.DeepCopy() + a.Name = "a-app" + key := client.ObjectKey{Name: a.Name, Namespace: a.Namespace} + currentApp := &v1alpha1.Application{} + Expect(k8sClient.Get(ctx, key, currentApp)).Should(BeNil()) + a.ResourceVersion = currentApp.ResourceVersion + a.Spec.Template.Spec.Interval = metav1.Duration{Duration: time.Millisecond * 500} + a.Spec.Data = map[string]string{"values.yaml": "deployment : hello-world-is-updated \naccount : 1234\nregion : us-west-2\nenvironment : test"} + + expected := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: a.Name, + Namespace: a.Namespace, + Labels: map[string]string{"test-temp-label": "ok", "app.kubernetes.io/managed-by": "overwhelm"}, + Annotations: a.Annotations, + OwnerReferences: []metav1.OwnerReference{{ + UID: currentApp.GetUID(), + }}, + }, + Data: map[string]string{"values.yaml": "deployment : hello-world-is-updated \naccount : 1234\nregion : us-west-2\nenvironment : test"}, + } + Expect(k8sClient.Update(ctx, a)).Should(Succeed()) + hr := &v2beta1.HelmRelease{} Eventually( - cmEquals(client.ObjectKey{Name: b.Name, Namespace: b.Namespace}, expected), - time.Second*5, time.Millisecond*500).Should(BeNil()) + func(ctx context.Context, key client.ObjectKey, hr *v2beta1.HelmRelease) func() error { + return func() error { + if err := k8sClient.Get(ctx, key, hr); err != nil { + return err + } + if hr.Generation != 2 || hr.Status.ObservedGeneration != 1 { + return errors.New("HelmRelease generations not updated") + } + if hr.OwnerReferences == nil || hr.OwnerReferences[0].UID != a.GetUID() { + return errors.New("HelmRelease has owner reference or has incorrect owner reference") + } + return nil + } + }(ctx, key, hr), time.Second*5, time.Second*2).Should(BeNil()) + Eventually(cmEquals(key, expected), time.Second*5, time.Millisecond*500).Should(BeNil()) }) }) - }) + It("Should Fail Resource Creation", func() { + By("having missing rendering keys in values", func() { c := application.DeepCopy() c.Name = "c-app" c.Spec.Data = map[string]string{"values.yaml": "deployment : hello-world \naccount : {{ .cluster.someKey }}\nregion : <% .cluster.region %>\nenvironment : {{ .egdata.environment }}"} Expect(k8sClient.Create(ctx, c)).Should(Succeed()) expected := &v1.ConfigMap{} - - Eventually( - cmEquals(client.ObjectKey{Name: c.Name, Namespace: c.Namespace}, expected), - time.Second*5, time.Millisecond*500).Should(Not(BeNil())) + key := client.ObjectKey{Name: c.Name, Namespace: c.Namespace} + Eventually(cmEquals(key, expected), time.Second*5, time.Millisecond*500).Should(Not(BeNil())) }) + By("having missing custom rendering keys in values", func() { d := application.DeepCopy() d.Name = "d-app" @@ -151,10 +257,8 @@ var _ = Describe("Application controller", func() { d.Spec.Data = map[string]string{"values.yaml": "deployment : hello-world \naccount : <% .cluster.someKey %>\nregion : <% .cluster.region %>\nenvironment : {{ .egdata.environment }}"} Expect(k8sClient.Create(ctx, d)).Should(Succeed()) expected := &v1.ConfigMap{} - - Eventually( - cmEquals(client.ObjectKey{Name: d.Name, Namespace: d.Namespace}, expected), - time.Second*5, time.Millisecond*500).Should(Not(BeNil())) + key := client.ObjectKey{Name: d.Name, Namespace: d.Namespace} + Eventually(cmEquals(key, expected), time.Second*5, time.Millisecond*500).Should(Not(BeNil())) }) }) }) diff --git a/controllers/suite_test.go b/controllers/suite_test.go index b7f1409..cdccc17 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -15,14 +15,15 @@ package controllers import ( + "context" "path/filepath" "testing" "time" corev1alpha1 "github.com/ExpediaGroup/overwhelm/api/v1alpha1" + "github.com/fluxcd/helm-controller/api/v2beta1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/onsi/gomega/gexec" "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -38,6 +39,7 @@ import ( var k8sClient client.Client var testEnv *envtest.Environment +var cancel context.CancelFunc func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) @@ -49,47 +51,52 @@ func TestAPIs(t *testing.T) { var _ = BeforeSuite(func() { logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + ctx, cancel = context.WithCancel(context.TODO()) By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases"), + filepath.Join("..", "config", "samples", "hr_crd.yaml")}, ErrorIfCRDPathMissing: true, } cfg, err := testEnv.Start() + Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + }) + Expect(err).ToNot(HaveOccurred()) - err = corev1alpha1.AddToScheme(scheme.Scheme) + err = v2beta1.AddToScheme(k8sManager.GetScheme()) + Expect(err).NotTo(HaveOccurred()) + err = corev1alpha1.AddToScheme(k8sManager.GetScheme()) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + k8sClient, err = client.New(cfg, client.Options{Scheme: k8sManager.GetScheme()}) Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) - k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ - Scheme: scheme.Scheme, - }) - Expect(err).ToNot(HaveOccurred()) - err = (&ApplicationReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + RequeueInterval: 3 * time.Second, + Retries: int64(3), }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) go func() { defer GinkgoRecover() - err = k8sManager.Start(ctrl.SetupSignalHandler()) + err = k8sManager.Start(ctx) Expect(err).ToNot(HaveOccurred(), "failed to run manager") - gexec.KillAndWait(4 * time.Second) - - // Teardown the test environment once controller is fnished. - // Otherwise from Kubernetes 1.21+, teardon timeouts waiting on - // kube-apiserver to return - err := testEnv.Stop() - Expect(err).ToNot(HaveOccurred()) }() }, 60) + +var _ = AfterSuite(func() { + cancel() + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).ToNot(HaveOccurred()) +}) diff --git a/go.mod b/go.mod index c3d3abf..5fd7390 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,11 @@ go 1.17 require ( github.com/fluxcd/helm-controller/api v0.21.0 - github.com/fluxcd/pkg/apis/kustomize v0.3.3 + github.com/fluxcd/pkg/apis/meta v0.13.0 + github.com/go-logr/logr v1.2.2 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.17.0 - k8s.io/apiextensions-apiserver v0.23.6 + k8s.io/api v0.23.6 k8s.io/apimachinery v0.23.6 k8s.io/client-go v0.23.6 sigs.k8s.io/controller-runtime v0.11.2 @@ -25,10 +26,9 @@ require ( github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect - github.com/fluxcd/pkg/apis/meta v0.13.0 // indirect + github.com/fluxcd/pkg/apis/kustomize v0.3.3 // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect - github.com/go-logr/logr v1.2.2 // indirect github.com/go-logr/zapr v1.2.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -66,7 +66,7 @@ require ( gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/api v0.23.6 // indirect + k8s.io/apiextensions-apiserver v0.23.6 // indirect k8s.io/component-base v0.23.6 // indirect k8s.io/klog/v2 v2.30.0 // indirect k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect diff --git a/main.go b/main.go index 5722913..67e43e1 100644 --- a/main.go +++ b/main.go @@ -16,20 +16,21 @@ package main import ( "flag" "os" + "time" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" + corev1alpha1 "github.com/ExpediaGroup/overwhelm/api/v1alpha1" + "github.com/ExpediaGroup/overwhelm/controllers" + "github.com/fluxcd/helm-controller/api/v2beta1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" - - corev1alpha1 "github.com/ExpediaGroup/overwhelm/api/v1alpha1" - "github.com/ExpediaGroup/overwhelm/controllers" //+kubebuilder:scaffold:imports ) @@ -41,6 +42,7 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(v2beta1.AddToScheme(scheme)) utilruntime.Must(corev1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } @@ -49,9 +51,13 @@ func main() { var metricsAddr string var enableLeaderElection bool var probeAddr string + var requeueInterval time.Duration + var retries int flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.DurationVar(&requeueInterval, "reconcile-requeue-interval", 3*time.Second, "Standard requeue interval for reconcile errors") + flag.IntVar(&retries, "retries", 3, "Number of retries when a resource is not found before giving up") flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") @@ -81,8 +87,10 @@ func main() { go controllers.LoadPreRenderData() if err = (&controllers.ApplicationReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + RequeueInterval: requeueInterval, + Retries: int64(retries), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Application") os.Exit(1)