From a9a1cc9bdfbacdcb757748a4f55ddef113ae0b9a Mon Sep 17 00:00:00 2001 From: chengleqi Date: Thu, 14 Jul 2022 14:29:42 +0800 Subject: [PATCH] Extend gitops.kubesphere.io/Application CRD and support basic operation on FluxCD HelmRelease and Kustomization --- cmd/controller/app/controllers.go | 11 +- .../gitops.kubesphere.io_applications.yaml | 996 ++++++++++++++++++ config/rbac/role.yaml | 27 + .../fluxcd-application-helmrelease.yaml | 48 + .../fluxcd-application-helmtemplate.yaml | 25 + .../fluxcd-application-kustomization.yaml | 22 + controllers/fluxcd/application-controller.go | 426 ++++++++ .../fluxcd/application-controller_test.go | 683 ++++++++++++ controllers/fluxcd/constants.go | 29 + pkg/api/gitops/v1alpha1/application.go | 685 +++++++++++- pkg/api/gitops/v1alpha1/constants.go | 15 + .../gitops/v1alpha1/zz_generated.deepcopy.go | 768 ++++++++++++++ 12 files changed, 3732 insertions(+), 3 deletions(-) create mode 100644 config/samples/gitops/fluxcd-application-helmrelease.yaml create mode 100644 config/samples/gitops/fluxcd-application-helmtemplate.yaml create mode 100644 config/samples/gitops/fluxcd-application-kustomization.yaml create mode 100644 controllers/fluxcd/application-controller.go create mode 100644 controllers/fluxcd/application-controller_test.go create mode 100644 controllers/fluxcd/constants.go diff --git a/cmd/controller/app/controllers.go b/cmd/controller/app/controllers.go index 67106431..8af90375 100644 --- a/cmd/controller/app/controllers.go +++ b/cmd/controller/app/controllers.go @@ -129,6 +129,10 @@ func getAllControllers(mgr manager.Manager, client k8s.Client, informerFactory i JenkinsClient: jenkinsCore, } + fluxcdApplicationReconciler := &fluxcd.ApplicationReconciler{ + Client: mgr.GetClient(), + } + return map[string]func(mgr manager.Manager) error{ gitRepoReconcilers.GetName(): func(mgr manager.Manager) error { return gitRepoReconcilers.SetupWithManager(mgr) @@ -207,8 +211,11 @@ func getAllControllers(mgr manager.Manager, client k8s.Client, informerFactory i argcdImageUpdaterReconciler.GetGroupName() + "-image-updater": func(mgr manager.Manager) error { return argcdImageUpdaterReconciler.SetupWithManager(mgr) }, - "fluxcd": func(mgr manager.Manager) error { - return fluxcdGitRepoReconciler.SetupWithManager(mgr) + fluxcdApplicationReconciler.GetGroupName(): func(mgr manager.Manager) (err error) { + if err = fluxcdGitRepoReconciler.SetupWithManager(mgr); err != nil { + return + } + return fluxcdApplicationReconciler.SetupWithManager(mgr) }, } } diff --git a/config/crd/bases/gitops.kubesphere.io_applications.yaml b/config/crd/bases/gitops.kubesphere.io_applications.yaml index 38e1dd0c..a438c643 100644 --- a/config/crd/bases/gitops.kubesphere.io_applications.yaml +++ b/config/crd/bases/gitops.kubesphere.io_applications.yaml @@ -868,12 +868,1008 @@ spec: - source type: object type: object + fluxApp: + properties: + spec: + properties: + config: + properties: + helmRelease: + description: HelmRelease for FluxCD HelmRelease + 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: + 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. + 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 + type: object + deploy: + description: HelmReleaseConfig stand for multi-clusters + and multi-targetNamespace config + items: + properties: + 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 + destination: + description: Destination stand for the destination + of the helmrelease + properties: + kubeConfig: + description: The KubeConfig for reconciling + the Kustomization on a remote cluster. + When used in combination with KustomizationSpec.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 KustomizationSpec.ServiceAccountName + is empty. + properties: + secretRef: + description: SecretRef holds the name + of a secret that contains a key with + the kubeconfig file as the value. + If no key is set, the key will default + to 'value'. The secret must be in + the same namespace as the Kustomization. + 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 Kustomization. + 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 + targetNamespace: + description: TargetNamespace to target when + performing operations for the HelmRelease. + Defaults to the namespace of the HelmRelease. + type: string + type: object + 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." + 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. + 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: The interval at which to reconcile + the Kustomization. + type: string + 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: + 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 + 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'. + 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. + 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 + 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." + 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'. + 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: + properties: + kind: + description: Kind of the values referent, + valid values are ('Secret', 'ConfigMap'). + type: string + name: + description: Name of the values referent. + Should reside in the same namespace + as the referring resource. + 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: + - destination + - interval + type: object + type: array + template: + description: Template ref a HelmTemplate that has + been saved before + type: string + required: + - deploy + type: object + kustomization: + description: Kustomization for FluxCD Kustomization + items: + description: KustomizationSpec defines the configuration + to calculate the desired state from a Source using + Kustomize. + properties: + decryption: + description: Decrypt Kubernetes secrets before applying + them on the cluster. + properties: + provider: + description: Provider is the name of the decryption + engine. + type: string + secretRef: + description: The secret name containing the + private OpenPGP keys used for decryption. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + required: + - provider + type: object + dependsOn: + description: DependsOn may contain a meta.NamespacedObjectReference + slice with references to Kustomization resources + that must be ready before this Kustomization 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 + destination: + description: Destination stand for the destination + of the kustomization + properties: + kubeConfig: + description: The KubeConfig for reconciling + the Kustomization on a remote cluster. When + used in combination with KustomizationSpec.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 KustomizationSpec.ServiceAccountName + is empty. + properties: + secretRef: + description: SecretRef holds the name of + a secret that contains a key with the + kubeconfig file as the value. If no key + is set, the key will default to 'value'. + The secret must be in the same namespace + as the Kustomization. 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 Kustomization. + 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 + targetNamespace: + description: TargetNamespace to target when + performing operations for the HelmRelease. + Defaults to the namespace of the HelmRelease. + type: string + type: object + force: + description: Force instructs the controller to recreate + resources when patching fails due to an immutable + field change. + type: boolean + healthChecks: + description: A list of resources to be included + in the health assessment. + items: + description: NamespacedObjectKindReference contains + enough information to locate the typed referenced + Kubernetes resource object in any namespace. + properties: + apiVersion: + description: API version of the referent, + if not specified the Kubernetes preferred + version will be used. + type: string + kind: + description: Kind of the referent. + type: string + name: + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, when + not specified it acts as LocalObjectReference. + type: string + required: + - kind + - name + type: object + type: array + 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 + interval: + description: The interval at which to reconcile + the Kustomization. + type: string + 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 + path: + description: Path to the directory containing the + kustomization.yaml file, or the set of plain YAMLs + a kustomization.yaml should be generated for. + Defaults to 'None', which translates to the root + path of the SourceRef. + type: string + postBuild: + description: PostBuild describes which actions to + perform on the YAML manifest generated by building + the kustomize overlay. + properties: + substitute: + additionalProperties: + type: string + description: Substitute holds a map of key/value + pairs. The variables defined in your YAML + manifests that match any of the keys defined + in the map will be substituted with the set + value. Includes support for bash string replacement + functions e.g. ${var:=default}, ${var:position} + and ${var/substring/replacement}. + type: object + substituteFrom: + description: SubstituteFrom holds references + to ConfigMaps and Secrets containing the variables + and their values to be substituted in the + YAML manifests. The ConfigMap and the Secret + data keys represent the var names and they + must match the vars declared in the manifests + for the substitution to happen. + items: + description: SubstituteReference contains + a reference to a resource containing the + variables name and value. + properties: + kind: + description: Kind of the values referent, + valid values are ('Secret', 'ConfigMap'). + type: string + name: + description: Name of the values referent. + Should reside in the same namespace + as the referring resource. + type: string + optional: + description: Optional indicates whether + the referenced resource must exist, + or whether to tolerate its absence. + If true and the referenced resource + is absent, proceed as if the resource + was present but empty, without any variables + defined. + type: boolean + required: + - kind + - name + type: object + type: array + type: object + prune: + description: Prune enables garbage collection. + type: boolean + retryInterval: + description: The interval at which to retry a previously + failed reconciliation. When not specified, the + controller uses the KustomizationSpec.Interval + value to retry failures. + type: string + serviceAccountName: + description: The name of the Kubernetes service + account to impersonate when reconciling this Kustomization. + type: string + suspend: + description: This flag tells the controller to suspend + subsequent kustomize executions, it does not apply + to already started executions. Defaults to false. + type: boolean + timeout: + description: Timeout for validation, apply and health + checking operations. Defaults to 'Interval' duration. + type: string + wait: + description: Wait instructs the controller to check + the health of all the reconciled resources. When + enabled, the HealthChecks are ignored. Defaults + to false. + type: boolean + required: + - destination + - interval + - prune + type: object + type: array + type: object + source: + properties: + sourceRef: + description: CrossNamespaceObjectReference contains enough + information to let you locate the typed referenced object + at cluster level. + 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. + type: string + namespace: + description: Namespace of the referent. + type: string + required: + - name + type: object + required: + - sourceRef + type: object + required: + - config + type: object + type: object + kind: + type: string + required: + - kind type: object status: description: ApplicationStatus represents the status of the Application properties: argoApp: type: string + fluxApp: + type: string + kind: + type: string + required: + - kind type: object type: object served: true diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index cebf48ef..535f9c11 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -316,6 +316,26 @@ rules: - get - list - watch +- apiGroups: + - helm.toolkit.fluxcd.io + resources: + - helmreleases + verbs: + - create + - delete + - get + - list + - update +- apiGroups: + - kustomize.toolkit.fluxcd.io + resources: + - kustomizations + verbs: + - create + - delete + - get + - list + - update - apiGroups: - source.toolkit.fluxcd.io resources: @@ -326,3 +346,10 @@ rules: - get - list - update +- apiGroups: + - source.toolkit.fluxcd.io/v1beta2 + resources: + - helmcharts + verbs: + - get + - list diff --git a/config/samples/gitops/fluxcd-application-helmrelease.yaml b/config/samples/gitops/fluxcd-application-helmrelease.yaml new file mode 100644 index 00000000..fd9f964b --- /dev/null +++ b/config/samples/gitops/fluxcd-application-helmrelease.yaml @@ -0,0 +1,48 @@ +apiVersion: gitops.kubesphere.io/v1alpha1 +kind: Application +metadata: + name: chengleqi-test-helm + namespace: my-devops-projecthmhx2 + labels: + gitops.kubesphere.io/save-helm-template: "true" +spec: + kind: fluxcd + fluxApp: + spec: + source: + sourceRef: + kind: GitRepository + name: fluxcd-github-repo + namespace: my-devops-projecthmhx2 + config: + helmRelease: + chart: + interval: 5m0s + chart: ./helm-chart + version: "0.1.0" + valuesFiles: + - ./helm-chart/values.yaml + - ./helm-chart/aliyun-values.yaml + reconcileStrategy: Revision + template: + deploy: + - destination: + kubeConfig: + targetNamespace: helm-app + interval: 1m0s + upgrade: + remediation: + remediateLastFailure: true + force: true + install: + createNamespace: true + - destination: + kubeConfig: + targetNamespace: another-helm-app + interval: 1m0s + upgrade: + remediation: + remediateLastFailure: true + force: true + install: + createNamespace: true \ No newline at end of file diff --git a/config/samples/gitops/fluxcd-application-helmtemplate.yaml b/config/samples/gitops/fluxcd-application-helmtemplate.yaml new file mode 100644 index 00000000..64a5b520 --- /dev/null +++ b/config/samples/gitops/fluxcd-application-helmtemplate.yaml @@ -0,0 +1,25 @@ +apiVersion: gitops.kubesphere.io/v1alpha1 +kind: Application +metadata: + name: chengleqi-test-template + namespace: my-devops-projecthmhx2 +spec: + kind: fluxcd + fluxApp: + spec: + source: + config: + helmRelease: + chart: + template: my-devops-projecthmhx2-chengleqi-test-helm + deploy: + - destination: + kubeConfig: + targetNamespace: template-app + interval: 1m0s + upgrade: + remediation: + remediateLastFailure: true + force: true + install: + createNamespace: true diff --git a/config/samples/gitops/fluxcd-application-kustomization.yaml b/config/samples/gitops/fluxcd-application-kustomization.yaml new file mode 100644 index 00000000..8931031c --- /dev/null +++ b/config/samples/gitops/fluxcd-application-kustomization.yaml @@ -0,0 +1,22 @@ +apiVersion: gitops.kubesphere.io/v1alpha1 +kind: Application +metadata: + name: chengleqi-test-kus + namespace: my-devops-projecthmhx2 +spec: + kind: fluxcd + fluxApp: + spec: + source: + sourceRef: + kind: GitRepository + name: fluxcd-gitee-repo + namespace: my-devops-projecthmhx2 + config: + kustomization: + - destination: + kubeConfig: + targetNamespace: default + interval: 8m0s + prune: true + path: "nginx" \ No newline at end of file diff --git a/controllers/fluxcd/application-controller.go b/controllers/fluxcd/application-controller.go new file mode 100644 index 00000000..3d508441 --- /dev/null +++ b/controllers/fluxcd/application-controller.go @@ -0,0 +1,426 @@ +/* +Copyright 2022 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fluxcd + +import ( + "context" + "fmt" + "github.com/go-logr/logr" + 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/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + "kubesphere.io/devops/controllers/argocd" + "kubesphere.io/devops/pkg/api/gitops/v1alpha1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +//+kubebuilder:rbac:groups=gitops.kubesphere.io,resources=applications,verbs=watch;get;list +//+kubebuilder:rbac:groups="",resources=events,verbs=create;patch +//+kubebuilder:rbac:groups="kustomize.toolkit.fluxcd.io",resources=kustomizations,verbs=get;list;create;update;delete +//+kubebuilder:rbac:groups="helm.toolkit.fluxcd.io",resources=helmreleases,verbs=get;list;create;update;delete +//+kubebuilder:rbac:groups="source.toolkit.fluxcd.io/v1beta2",resources=helmcharts,verbs=get;list + +// ApplicationReconciler is the reconciler of the FluxCD HelmRelease and FluxCD Kustomization +type ApplicationReconciler struct { + client.Client + log logr.Logger + recorder record.EventRecorder +} + +// Reconcile sync the Application with underlying FluxCD HelmRelease and FluxCD Kustomization CRD +func (r *ApplicationReconciler) Reconcile(req ctrl.Request) (result ctrl.Result, err error) { + ctx := context.Background() + r.log.Info(fmt.Sprintf("start to reconcile application: %s", req.String())) + + app := &v1alpha1.Application{} + // only reconcile fluxcd application + app.Spec.Kind = v1alpha1.FluxCD + + if err = r.Get(ctx, req.NamespacedName, app); err != nil { + err = client.IgnoreNotFound(err) + return + } + + if app.Spec.FluxApp != nil { + err = r.reconcileApp(app) + } + return +} + +func (r *ApplicationReconciler) reconcileApp(app *v1alpha1.Application) (err error) { + if appType := isHelmOrKustomize(app); appType == HelmRelease { + return r.reconcileHelm(app) + } else if appType == Kustomization { + return r.reconcileKustomization(app) + } + return +} + +func (r *ApplicationReconciler) reconcileHelm(app *v1alpha1.Application) (err error) { + ctx := context.Background() + + if wantSaveHelmTemplate(app) && app.Spec.FluxApp.Spec.Config.HelmRelease.Chart != nil { + if err = r.saveTemplate(ctx, app); err != nil { + return + } + } + + var helmChart *unstructured.Unstructured + if helmTemplateName := app.Spec.FluxApp.Spec.Config.HelmRelease.Template; helmTemplateName != "" { + // use template + helmTemplateNS := app.GetNamespace() + helmTemplateList := createBareFluxHelmTemplateObjectList() + + if err = r.List(ctx, helmTemplateList, client.InNamespace(helmTemplateNS), client.MatchingLabels{ + v1alpha1.HelmTemplateName: helmTemplateName, + }); err != nil { + return + } + // there is a helmtemplate that user specified + helmChart = &helmTemplateList.Items[0] + } else { + helmChart = createUnstructuredFluxHelmTemplate(app) + } + + if err = r.reconcileHelmReleaseList(app, helmChart); err != nil { + return + } + + return +} + +func (r *ApplicationReconciler) reconcileHelmReleaseList(app *v1alpha1.Application, helmChart *unstructured.Unstructured) (err error) { + ctx := context.Background() + + fluxApp := app.Spec.FluxApp.DeepCopy() + appNS, appName := app.GetNamespace(), app.GetName() + + fluxHelmReleaseList := createBareFluxHelmReleaseListObject() + if err = r.List(ctx, fluxHelmReleaseList, client.InNamespace(appNS), client.MatchingLabels{ + "app.kubernetes.io/managed-by": getHelmTemplateName(appNS, appName), + }); err != nil { + return + } + + hrMap := make(map[string]*unstructured.Unstructured, len(fluxHelmReleaseList.Items)) + for _, fluxHelmRelease := range fluxHelmReleaseList.Items { + hrMap[fluxHelmRelease.GetName()] = fluxHelmRelease.DeepCopy() + } + + helmReleaseNum := len(fluxApp.Spec.Config.HelmRelease.Deploy) + for i := 0; i < helmReleaseNum; i++ { + deploy := fluxApp.Spec.Config.HelmRelease.Deploy[i] + helmReleaseName := getHelmReleaseName(deploy.Destination.TargetNamespace) + if hr, ok := hrMap[helmReleaseName]; !ok { + // there is no matching helmRelease + // create + if err = r.createHelmRelease(ctx, app, helmChart, deploy); err != nil { + return + } + } else { + // there is a matching helmRelease + // update the helmRelease + if err = r.updateHelmRelease(ctx, hr, helmChart, deploy); err != nil { + return + } + } + } + return +} + +func (r *ApplicationReconciler) createHelmRelease(ctx context.Context, app *v1alpha1.Application, helmChart *unstructured.Unstructured, deploy *v1alpha1.Deploy) (err error) { + appNS, appName := app.GetNamespace(), app.GetName() + hrNS, hrName := appNS, getHelmReleaseName(deploy.Destination.TargetNamespace) + + newFluxHelmRelease := createBareFluxHelmReleaseObject() + setFluxHelmReleaseFields(newFluxHelmRelease, helmChart, deploy) + newFluxHelmRelease.SetNamespace(hrNS) + newFluxHelmRelease.SetName(hrName) + newFluxHelmRelease.SetLabels(map[string]string{ + "app.kubernetes.io/managed-by": getHelmTemplateName(appNS, appName), + }) + newFluxHelmRelease.SetOwnerReferences([]metav1.OwnerReference{ + { + APIVersion: "gitops.kubesphere.io/v1alpha1", + Kind: "Application", + Name: appName, + UID: app.GetUID(), + }, + }) + if err = r.Create(ctx, newFluxHelmRelease); err != nil { + return + } + r.log.Info("Create FluxCD HelmRelease", "name", newFluxHelmRelease.GetName()) + r.recorder.Eventf(app, corev1.EventTypeNormal, "Created", "Created HelmRelease %s", newFluxHelmRelease.GetName()) + return +} + +func (r *ApplicationReconciler) updateHelmRelease(ctx context.Context, hr, helmChart *unstructured.Unstructured, deploy *v1alpha1.Deploy) (err error) { + setFluxHelmReleaseFields(hr, helmChart, deploy) + if err = r.Update(ctx, hr); err != nil { + return + } + return +} + +func setFluxHelmReleaseFields(hr, helmTemplate *unstructured.Unstructured, deploy *v1alpha1.Deploy) { + helmTemplateSpec, _, _ := unstructured.NestedFieldNoCopy(helmTemplate.Object, "spec") + _ = argocd.SetNestedField(hr.Object, helmTemplateSpec, "spec", "chart", "spec") + + configs, _ := argocd.InterfaceToMap(*deploy) + for k, config := range configs { + if k != "destination" { + _ = unstructured.SetNestedField(hr.Object, config, "spec", k) + } + } + + _ = argocd.SetNestedField(hr.Object, deploy.Destination.KubeConfig, "spec", "kubeConfig") + _ = unstructured.SetNestedField(hr.Object, deploy.Destination.TargetNamespace, "spec", "targetNamespace") +} + +func (r *ApplicationReconciler) reconcileKustomization(app *v1alpha1.Application) (err error) { + ctx := context.Background() + + fluxApp := app.Spec.FluxApp.DeepCopy() + appNS, appName := app.GetNamespace(), app.GetName() + + kusList := createBareFluxKustomizationListObject() + if err = r.List(ctx, kusList, client.InNamespace(appNS), client.MatchingLabels{ + "app.kubernetes.io/managed-by": getKusManagerName(appNS, appName), + }); err != nil { + return + } + + kusMap := make(map[string]*unstructured.Unstructured, len(kusList.Items)) + for _, kus := range kusList.Items { + kusMap[kus.GetName()] = kus.DeepCopy() + } + + kusNum := len(app.Spec.FluxApp.Spec.Config.Kustomization) + for i := 0; i < kusNum; i++ { + kusDeploy := fluxApp.Spec.Config.Kustomization[i] + kusName := getKustomizationName(kusDeploy.Destination.TargetNamespace) + + if kus, ok := kusMap[kusName]; !ok { + // not found + // create + err = r.createKustomization(ctx, app, kusDeploy) + } else { + // found + // update this kus + err = r.updateKustomization(ctx, kus, app, kusDeploy) + } + } + return +} + +func (r *ApplicationReconciler) createKustomization(ctx context.Context, app *v1alpha1.Application, deploy *v1alpha1.KustomizationSpec) (err error) { + appNS, appName := app.GetNamespace(), app.GetName() + kusNS, kusName := appNS, getKustomizationName(deploy.Destination.TargetNamespace) + + kus := createBareFluxKustomizationObject() + setFluxKustomizationFields(kus, app, deploy) + kus.SetNamespace(kusNS) + kus.SetName(kusName) + kus.SetOwnerReferences([]metav1.OwnerReference{ + { + APIVersion: "gitops.kubesphere.io/v1alpha1", + Kind: "Application", + Name: appName, + UID: app.GetUID(), + }, + }) + kus.SetLabels(map[string]string{ + "app.kubernetes.io/managed-by": getKusManagerName(appNS, appName), + }) + err = r.Create(ctx, kus) + return +} + +func (r *ApplicationReconciler) updateKustomization(ctx context.Context, kus *unstructured.Unstructured, app *v1alpha1.Application, deploy *v1alpha1.KustomizationSpec) (err error) { + setFluxKustomizationFields(kus, app, deploy) + err = r.Update(ctx, kus) + return +} + +func setFluxKustomizationFields(kus *unstructured.Unstructured, app *v1alpha1.Application, deploy *v1alpha1.KustomizationSpec) { + _ = argocd.SetNestedField(kus.Object, app.Spec.FluxApp.Spec.Source.SourceRef, "spec", "sourceRef") + + configs, _ := argocd.InterfaceToMap(*deploy) + for k, config := range configs { + if k != "destination" { + _ = unstructured.SetNestedField(kus.Object, config, "spec", k) + } + } + _ = argocd.SetNestedField(kus.Object, deploy.Destination.KubeConfig, "spec", "kubeConfig") + _ = unstructured.SetNestedField(kus.Object, deploy.Destination.TargetNamespace, "spec", "targetNamespace") +} + +func isHelmOrKustomize(app *v1alpha1.Application) AppType { + if app.Spec.FluxApp.Spec.Config.HelmRelease != nil { + return HelmRelease + } + return Kustomization +} + +// GetGroupName returns the group name +func (r *ApplicationReconciler) GetGroupName() string { + return controllerGroupName +} + +// GetName returns the name of this reconciler +func (r *ApplicationReconciler) GetName() string { + return "FluxApplicationReconciler" +} + +func getHelmReleaseName(targetNamespace string) string { + return targetNamespace +} + +func getKustomizationName(targetNamespace string) string { + return targetNamespace +} + +func getHelmTemplateName(ns, name string) string { + return ns + "-" + name +} + +func getKusManagerName(ns, name string) string { + return ns + "-" + name +} + +func (r *ApplicationReconciler) saveTemplate(ctx context.Context, app *v1alpha1.Application) (err error) { + helmTemplate := createBareFluxHelmTemplateObject() + + appNS, appName := app.GetNamespace(), app.GetName() + helmTemplateNS, helmTemplateName := appNS, getHelmTemplateName(appNS, appName) + + if err = r.Get(ctx, types.NamespacedName{Namespace: helmTemplateNS, Name: helmTemplateName}, helmTemplate); err != nil { + if !apierrors.IsNotFound(err) { + return + } + // not found + // create + newFluxHelmTemplate := createUnstructuredFluxHelmTemplate(app) + newFluxHelmTemplate.SetName(helmTemplateName) + newFluxHelmTemplate.SetNamespace(helmTemplateNS) + newFluxHelmTemplate.SetLabels(map[string]string{ + v1alpha1.HelmTemplateName: helmTemplateName, + }) + if err = r.Create(ctx, newFluxHelmTemplate); err != nil { + return + } + r.log.Info("Create HelmTemplate", "name", newFluxHelmTemplate.GetName()) + r.recorder.Eventf(app, corev1.EventTypeNormal, "Created", "Created HelmTemplate %s", newFluxHelmTemplate.GetName()) + } + return +} + +func wantSaveHelmTemplate(app *v1alpha1.Application) bool { + if v, ok := app.GetLabels()[v1alpha1.SaveTemplateLabelKey]; ok { + return v == "true" + } + return false +} + +func createUnstructuredFluxHelmTemplate(app *v1alpha1.Application) (newFluxHelmTemplate *unstructured.Unstructured) { + newFluxHelmTemplate = createBareFluxHelmTemplateObject() + newFluxHelmTemplate.SetLabels(map[string]string{ + "app.kubernetes.io/managed-by": v1alpha1.GroupName, + }) + + _ = argocd.SetNestedField(newFluxHelmTemplate.Object, app.Spec.FluxApp.Spec.Config.HelmRelease.Chart, "spec") + _ = argocd.SetNestedField(newFluxHelmTemplate.Object, app.Spec.FluxApp.Spec.Source.SourceRef, "spec", "sourceRef") + return +} + +func createBareFluxHelmTemplateObject() *unstructured.Unstructured { + fluxHelmChart := &unstructured.Unstructured{} + fluxHelmChart.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "source.toolkit.fluxcd.io", + Version: "v1beta2", + Kind: "HelmChart", + }) + return fluxHelmChart +} + +func createBareFluxHelmTemplateObjectList() *unstructured.UnstructuredList { + fluxHelmTemplateList := &unstructured.UnstructuredList{} + fluxHelmTemplateList.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "source.toolkit.fluxcd.io", + Version: "v1beta2", + Kind: "HelmChartList", + }) + return fluxHelmTemplateList +} + +func createBareFluxHelmReleaseObject() *unstructured.Unstructured { + fluxHelmRelease := &unstructured.Unstructured{} + fluxHelmRelease.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "helm.toolkit.fluxcd.io", + Version: "v2beta1", + Kind: "HelmRelease", + }) + return fluxHelmRelease +} + +func createBareFluxHelmReleaseListObject() *unstructured.UnstructuredList { + fluxHelmReleaseList := &unstructured.UnstructuredList{} + fluxHelmReleaseList.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "helm.toolkit.fluxcd.io", + Version: "v2beta1", + Kind: "HelmReleaseList", + }) + return fluxHelmReleaseList +} + +func createBareFluxKustomizationObject() *unstructured.Unstructured { + fluxKustomization := &unstructured.Unstructured{} + fluxKustomization.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "kustomize.toolkit.fluxcd.io", + Version: "v1beta2", + Kind: "Kustomization", + }) + return fluxKustomization +} + +func createBareFluxKustomizationListObject() *unstructured.UnstructuredList { + fluxHelmReleaseList := &unstructured.UnstructuredList{} + fluxHelmReleaseList.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "kustomize.toolkit.fluxcd.io", + Version: "v1beta2", + Kind: "KustomizationList", + }) + return fluxHelmReleaseList +} + +// SetupWithManager setups the reconciler with a manager +// setup the logger, recorder +func (r *ApplicationReconciler) SetupWithManager(mgr manager.Manager) error { + r.log = ctrl.Log.WithName(r.GetName()) + r.recorder = mgr.GetEventRecorderFor(r.GetName()) + + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.Application{}). + Complete(r) +} diff --git a/controllers/fluxcd/application-controller_test.go b/controllers/fluxcd/application-controller_test.go new file mode 100644 index 00000000..daaa9ef2 --- /dev/null +++ b/controllers/fluxcd/application-controller_test.go @@ -0,0 +1,683 @@ +/* +Copyright 2022 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fluxcd + +import ( + "context" + "github.com/stretchr/testify/assert" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + apischema "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + "kubesphere.io/devops/pkg/api/gitops/v1alpha1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/log" + "testing" + "time" +) + +func TestApplicationReconciler_Reconcile(t *testing.T) { + schema, err := v1alpha1.SchemeBuilder.Register().Build() + assert.Nil(t, err) + + argoApp := &v1alpha1.Application{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-app", + Namespace: "fake-ns", + }, + Spec: v1alpha1.ApplicationSpec{ + Kind: v1alpha1.ArgoCD, + }, + } + + fluxApp := argoApp.DeepCopy() + fluxApp.Spec.Kind = v1alpha1.FluxCD + + type fields struct { + Client client.Client + } + type args struct { + req ctrl.Request + } + + tests := []struct { + name string + fields fields + args args + wantResult ctrl.Result + wantErr assert.ErrorAssertionFunc + }{ + { + name: "not found an application", + fields: fields{ + Client: fake.NewFakeClientWithScheme(schema), + }, + args: args{ + req: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "fake-app", + Name: "fake-ns", + }, + }, + }, + wantErr: assert.NoError, + }, + { + name: "found an argocd application", + fields: fields{ + Client: fake.NewFakeClientWithScheme(schema, argoApp.DeepCopy()), + }, + args: args{ + req: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "fake-app", + Name: "fake-ns", + }, + }, + }, + wantErr: assert.NoError, + }, + { + name: "found a fluxcd application", + fields: fields{ + Client: fake.NewFakeClientWithScheme(schema, fluxApp.DeepCopy()), + }, + args: args{ + req: ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "fake-ns", + Name: "fake-app", + }, + }, + }, + wantErr: assert.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &ApplicationReconciler{ + Client: tt.fields.Client, + log: log.NullLogger{}, + recorder: &record.FakeRecorder{}, + } + gotResult, err := r.Reconcile(tt.args.req) + if tt.wantErr(t, err) { + assert.Equal(t, tt.wantResult, gotResult) + } + }) + } +} + +func TestApplicationReconciler_reconcileApp(t *testing.T) { + schema, err := v1alpha1.SchemeBuilder.Register().Build() + assert.Nil(t, err) + + schema.AddKnownTypeWithName(apischema.GroupVersionKind{ + Group: "helm.toolkit.fluxcd.io", + Version: "v2beta1", + Kind: "HelmReleaseList", + }, &unstructured.UnstructuredList{}) + + schema.AddKnownTypeWithName(apischema.GroupVersionKind{ + Group: "kustomize.toolkit.fluxcd.io", + Version: "v1beta2", + Kind: "KustomizationList", + }, &unstructured.UnstructuredList{}) + + schema.AddKnownTypeWithName(apischema.GroupVersionKind{ + Group: "source.toolkit.fluxcd.io", + Version: "v1beta2", + Kind: "HelmChartList", + }, &unstructured.UnstructuredList{}) + + helmApp := &v1alpha1.Application{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "gitops.kubesphere.io/v1alpha1", + Kind: "Application", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-app", + Namespace: "fake-ns", + }, + Spec: v1alpha1.ApplicationSpec{ + Kind: "fluxcd", + FluxApp: &v1alpha1.FluxApplication{ + Spec: v1alpha1.FluxApplicationSpec{ + Source: &v1alpha1.FluxApplicationSource{ + SourceRef: v1alpha1.CrossNamespaceObjectReference{ + APIVersion: "source.toolkit.fluxcd.io/v1beta2", + Kind: "GitRepository", + Name: "fake-repo", + Namespace: "fake-ns", + }, + }, + Config: &v1alpha1.FluxApplicationConfig{ + HelmRelease: &v1alpha1.HelmReleaseSpec{ + Chart: &v1alpha1.HelmChartTemplateSpec{ + Chart: "./helm-chart", + Version: "0.1.0", + Interval: &metav1.Duration{ + Duration: time.Minute, + }, + ReconcileStrategy: "Revision", + ValuesFiles: []string{ + "./helm-chart/values.yaml", + }, + }, + Deploy: []*v1alpha1.Deploy{ + { + Destination: v1alpha1.FluxApplicationDestination{ + KubeConfig: &v1alpha1.KubeConfig{ + SecretRef: v1alpha1.SecretKeyReference{ + Name: "aliyun-kubeconfig", + Key: "kubeconfig", + }, + }, + TargetNamespace: "fake-targetNamespace", + }, + }, + { + Destination: v1alpha1.FluxApplicationDestination{ + KubeConfig: &v1alpha1.KubeConfig{ + SecretRef: v1alpha1.SecretKeyReference{ + Name: "tencentcloud-kubeconfig", + Key: "kubeconfig", + }, + }, + TargetNamespace: "another-fake-targetNamespace", + }, + }, + }, + }, + }, + }, + }, + }, + } + + helmAppWithLabel := helmApp.DeepCopy() + helmAppWithLabel.SetLabels(map[string]string{ + v1alpha1.SaveTemplateLabelKey: "true", + }) + + helmAppWithUpdate := helmApp.DeepCopy() + helmAppWithUpdate.Spec.FluxApp.Spec.Config.HelmRelease.Deploy[0].ValuesFrom = []v1alpha1.ValuesReference{ + { + Kind: "ConfigMap", + Name: "fake-cm", + ValuesKey: "fake-key", + }, + } + + fluxHelmChart := createUnstructuredFluxHelmTemplate(helmApp) + fluxHelmChart.SetNamespace(helmApp.GetNamespace()) + fluxHelmChart.SetName(getHelmTemplateName(helmApp.GetNamespace(), helmApp.GetName())) + fluxHelmChart.SetLabels(map[string]string{ + v1alpha1.HelmTemplateName: getHelmTemplateName(helmApp.GetNamespace(), helmApp.GetName()), + }) + + fluxHR := createBareFluxHelmReleaseObject() + helmDeploy := helmApp.Spec.FluxApp.Spec.Config.HelmRelease.Deploy[0] + setFluxHelmReleaseFields(fluxHR, fluxHelmChart, helmDeploy) + fluxHR.SetNamespace(helmApp.GetNamespace()) + fluxHR.SetName(getHelmReleaseName(helmDeploy.Destination.TargetNamespace)) + fluxHR.SetLabels(map[string]string{ + "app.kubernetes.io/managed-by": getHelmTemplateName(helmApp.GetNamespace(), helmApp.GetName()), + }) + + helmAppWithTemplate := helmApp.DeepCopy() + helmAppWithTemplate.Spec.FluxApp.Spec.Source = nil + helmAppWithTemplate.Spec.FluxApp.Spec.Config.HelmRelease.Chart = nil + helmAppWithTemplate.Spec.FluxApp.Spec.Config.HelmRelease.Template = "fake-ns-fake-app" + + kusApp := &v1alpha1.Application{ + Spec: v1alpha1.ApplicationSpec{ + Kind: "fluxcd", + FluxApp: &v1alpha1.FluxApplication{ + Spec: v1alpha1.FluxApplicationSpec{ + Source: &v1alpha1.FluxApplicationSource{ + SourceRef: v1alpha1.CrossNamespaceObjectReference{ + APIVersion: "source.toolkit.fluxcd.io/v1beta2", + Kind: "GitRepository", + Name: "fake-repo", + Namespace: "fake-ns", + }, + }, + Config: &v1alpha1.FluxApplicationConfig{ + Kustomization: []*v1alpha1.KustomizationSpec{ + { + Destination: v1alpha1.FluxApplicationDestination{ + KubeConfig: &v1alpha1.KubeConfig{ + SecretRef: v1alpha1.SecretKeyReference{ + Name: "aliyun-kubeconfig", + Key: "kubeconfig", + }, + }, + TargetNamespace: "fake-targetNamespace", + }, + Path: "kustomization", + Prune: true, + }, + { + Destination: v1alpha1.FluxApplicationDestination{ + KubeConfig: &v1alpha1.KubeConfig{ + SecretRef: v1alpha1.SecretKeyReference{ + Name: "tencentcloud-kubeconfig", + Key: "kubeconfig", + }, + }, + TargetNamespace: "another-fake-targetNamespace", + }, + Path: "kustomization", + Prune: true, + }, + }, + }, + }, + }, + }, + } + + fluxKus := createBareFluxKustomizationObject() + KusDeploy := kusApp.Spec.FluxApp.Spec.Config.Kustomization[0] + setFluxKustomizationFields(fluxKus, kusApp, KusDeploy) + fluxKus.SetNamespace(kusApp.GetNamespace()) + fluxKus.SetName(getKustomizationName(KusDeploy.Destination.TargetNamespace)) + fluxKus.SetLabels(map[string]string{ + "app.kubernetes.io/managed-by": getKusManagerName(kusApp.GetNamespace(), kusApp.GetName()), + }) + + kusAppWithUpdate := kusApp.DeepCopy() + kusAppWithUpdate.Spec.FluxApp.Spec.Config.Kustomization[0].Path = "another-kustomization" + + type fields struct { + Client client.Client + } + type args struct { + app *v1alpha1.Application + } + + tests := []struct { + name string + fields fields + args args + verify func(t *testing.T, Client client.Client, err error) + }{ + { + name: "create a Multi-Clusters FluxApp(HelmRelease) without saving Template", + fields: fields{ + Client: fake.NewFakeClientWithScheme(schema), + }, + args: args{ + app: helmApp.DeepCopy(), + }, + verify: func(t *testing.T, Client client.Client, err error) { + ctx := context.Background() + assert.Nil(t, err) + + fluxHRList := createBareFluxHelmReleaseListObject() + appNS, appName := helmApp.GetNamespace(), helmApp.GetName() + + err = Client.List(ctx, fluxHRList, client.InNamespace(appNS), client.MatchingLabels{ + "app.kubernetes.io/managed-by": getHelmTemplateName(appNS, appName), + }) + assert.Nil(t, err) + assert.Equal(t, 2, len(fluxHRList.Items)) + + for _, hr := range fluxHRList.Items { + // same settings + chart, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "chart") + assert.Equal(t, "./helm-chart", chart) + chartVersion, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "version") + assert.Equal(t, "0.1.0", chartVersion) + chartInterval, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "interval") + assert.Equal(t, "1m0s", chartInterval) + chartReconcileStrategy, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "reconcileStrategy") + assert.Equal(t, "Revision", chartReconcileStrategy) + chartValueFiles, _, _ := unstructured.NestedStringSlice(hr.Object, "spec", "chart", "spec", "valuesFiles") + assert.Equal(t, 1, len(chartValueFiles)) + assert.Equal(t, "./helm-chart/values.yaml", chartValueFiles[0]) + switch hr.GetName() { + case "fake-targetNamespace": + kubeconfigName, _, _ := unstructured.NestedString(hr.Object, "spec", "kubeConfig", "secretRef", "name") + assert.Equal(t, "aliyun-kubeconfig", kubeconfigName) + kubeconfigKey, _, _ := unstructured.NestedString(hr.Object, "spec", "kubeConfig", "secretRef", "key") + assert.Equal(t, "kubeconfig", kubeconfigKey) + case "another-fake-targetNamespace": + kubeconfigName, _, _ := unstructured.NestedString(hr.Object, "spec", "kubeConfig", "secretRef", "name") + assert.Equal(t, "tencentcloud-kubeconfig", kubeconfigName) + kubeconfigKey, _, _ := unstructured.NestedString(hr.Object, "spec", "kubeConfig", "secretRef", "key") + assert.Equal(t, "kubeconfig", kubeconfigKey) + } + } + + fluxChart := createBareFluxHelmTemplateObject() + err = Client.Get(ctx, types.NamespacedName{Namespace: appNS, Name: getHelmTemplateName(appNS, appName)}, fluxChart) + assert.True(t, apierrors.IsNotFound(err)) + }, + }, + { + name: "create a Multi-Clusters FluxApp(HelmRelease) and save Template", + fields: fields{ + Client: fake.NewFakeClientWithScheme(schema), + }, + args: args{ + app: helmAppWithLabel.DeepCopy(), + }, + verify: func(t *testing.T, Client client.Client, err error) { + assert.Nil(t, err) + ctx := context.Background() + + fluxHRList := createBareFluxHelmReleaseListObject() + appNS, appName := helmApp.GetNamespace(), helmApp.GetName() + + err = Client.List(ctx, fluxHRList, client.InNamespace(appNS), client.MatchingLabels{ + "app.kubernetes.io/managed-by": getHelmTemplateName(appNS, appName), + }) + assert.Nil(t, err) + assert.Equal(t, 2, len(fluxHRList.Items)) + + for _, hr := range fluxHRList.Items { + // same settings + sourceAPIVersion, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "sourceRef", "apiVersion") + assert.Equal(t, "source.toolkit.fluxcd.io/v1beta2", sourceAPIVersion) + sourceKind, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "sourceRef", "kind") + assert.Equal(t, "GitRepository", sourceKind) + sourceName, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "sourceRef", "name") + assert.Equal(t, "fake-repo", sourceName) + sourceNS, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "sourceRef", "namespace") + assert.Equal(t, "fake-ns", sourceNS) + chart, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "chart") + assert.Equal(t, "./helm-chart", chart) + chartVersion, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "version") + assert.Equal(t, "0.1.0", chartVersion) + chartInterval, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "interval") + assert.Equal(t, "1m0s", chartInterval) + chartReconcileStrategy, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "reconcileStrategy") + assert.Equal(t, "Revision", chartReconcileStrategy) + chartValueFiles, _, _ := unstructured.NestedStringSlice(hr.Object, "spec", "chart", "spec", "valuesFiles") + assert.Equal(t, 1, len(chartValueFiles)) + assert.Equal(t, "./helm-chart/values.yaml", chartValueFiles[0]) + switch hr.GetName() { + case "fake-targetNamespace": + kubeconfigName, _, _ := unstructured.NestedString(hr.Object, "spec", "kubeConfig", "secretRef", "name") + assert.Equal(t, "aliyun-kubeconfig", kubeconfigName) + kubeconfigKey, _, _ := unstructured.NestedString(hr.Object, "spec", "kubeConfig", "secretRef", "key") + assert.Equal(t, "kubeconfig", kubeconfigKey) + case "another-fake-targetNamespace": + kubeconfigName, _, _ := unstructured.NestedString(hr.Object, "spec", "kubeConfig", "secretRef", "name") + assert.Equal(t, "tencentcloud-kubeconfig", kubeconfigName) + kubeconfigKey, _, _ := unstructured.NestedString(hr.Object, "spec", "kubeConfig", "secretRef", "key") + assert.Equal(t, "kubeconfig", kubeconfigKey) + } + } + + fluxChart := createBareFluxHelmTemplateObject() + err = Client.Get(ctx, types.NamespacedName{Namespace: appNS, Name: getHelmTemplateName(appNS, appName)}, fluxChart) + assert.Nil(t, err) + + sourceAPIVersion, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "sourceRef", "apiVersion") + assert.Equal(t, "source.toolkit.fluxcd.io/v1beta2", sourceAPIVersion) + sourceKind, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "sourceRef", "kind") + assert.Equal(t, "GitRepository", sourceKind) + sourceName, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "sourceRef", "name") + assert.Equal(t, "fake-repo", sourceName) + sourceNS, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "sourceRef", "namespace") + assert.Equal(t, "fake-ns", sourceNS) + chart, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "chart") + assert.Equal(t, "./helm-chart", chart) + chartVersion, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "version") + assert.Equal(t, "0.1.0", chartVersion) + chartInterval, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "interval") + assert.Equal(t, "1m0s", chartInterval) + chartReconcileStrategy, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "reconcileStrategy") + assert.Equal(t, "Revision", chartReconcileStrategy) + chartValueFiles, _, _ := unstructured.NestedStringSlice(fluxChart.Object, "spec", "valuesFiles") + assert.Equal(t, 1, len(chartValueFiles)) + assert.Equal(t, "./helm-chart/values.yaml", chartValueFiles[0]) + }, + }, + { + name: "update the Multi-Clusters FluxApp(HelmRelease)", + fields: fields{ + Client: fake.NewFakeClientWithScheme(schema, fluxHR.DeepCopy()), + }, + args: args{ + app: helmAppWithUpdate.DeepCopy(), + }, + verify: func(t *testing.T, Client client.Client, err error) { + assert.Nil(t, err) + ctx := context.Background() + + fluxHRList := createBareFluxHelmReleaseListObject() + appNS, appName := helmApp.GetNamespace(), helmApp.GetName() + + err = Client.List(ctx, fluxHRList, client.InNamespace(appNS), client.MatchingLabels{ + "app.kubernetes.io/managed-by": getHelmTemplateName(appNS, appName), + }) + assert.Nil(t, err) + assert.Equal(t, 2, len(fluxHRList.Items)) + + for _, hr := range fluxHRList.Items { + if hr.GetName() == "fake-targetNamespace" { + valuesFromSlice, _, _ := unstructured.NestedSlice(hr.Object, "spec", "valuesFrom") + valuesFrom := valuesFromSlice[0].(map[string]interface{}) + assert.Equal(t, "ConfigMap", valuesFrom["kind"].(string)) + assert.Equal(t, "fake-cm", valuesFrom["name"].(string)) + assert.Equal(t, "fake-key", valuesFrom["valuesKey"].(string)) + } else if hr.GetName() == "another-fake-targetNamespace" { + valuesFromSlice, _, _ := unstructured.NestedSlice(hr.Object, "spec", "valuesFrom") + assert.Nil(t, valuesFromSlice) + } + } + }, + }, + { + name: "create a Multi-Clusters FluxApp(HelmRelease) by using a Template", + fields: fields{ + Client: fake.NewFakeClientWithScheme(schema, fluxHelmChart.DeepCopy()), + }, + args: args{ + app: helmAppWithTemplate.DeepCopy(), + }, + verify: func(t *testing.T, Client client.Client, err error) { + assert.Nil(t, err) + + ctx := context.Background() + + fluxHRList := createBareFluxHelmReleaseListObject() + appNS, appName := helmApp.GetNamespace(), helmApp.GetName() + + err = Client.List(ctx, fluxHRList, client.InNamespace(appNS), client.MatchingLabels{ + "app.kubernetes.io/managed-by": getHelmTemplateName(appNS, appName), + }) + assert.Nil(t, err) + assert.Equal(t, 2, len(fluxHRList.Items)) + + for _, hr := range fluxHRList.Items { + // same settings + sourceAPIVersion, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "sourceRef", "apiVersion") + assert.Equal(t, "source.toolkit.fluxcd.io/v1beta2", sourceAPIVersion) + sourceKind, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "sourceRef", "kind") + assert.Equal(t, "GitRepository", sourceKind) + sourceName, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "sourceRef", "name") + assert.Equal(t, "fake-repo", sourceName) + sourceNS, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "sourceRef", "namespace") + assert.Equal(t, "fake-ns", sourceNS) + chart, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "chart") + assert.Equal(t, "./helm-chart", chart) + chartVersion, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "version") + assert.Equal(t, "0.1.0", chartVersion) + chartInterval, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "interval") + assert.Equal(t, "1m0s", chartInterval) + chartReconcileStrategy, _, _ := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "reconcileStrategy") + assert.Equal(t, "Revision", chartReconcileStrategy) + chartValueFiles, _, _ := unstructured.NestedStringSlice(hr.Object, "spec", "chart", "spec", "valuesFiles") + assert.Equal(t, 1, len(chartValueFiles)) + assert.Equal(t, "./helm-chart/values.yaml", chartValueFiles[0]) + switch hr.GetName() { + case "fake-targetNamespace": + kubeconfigName, _, _ := unstructured.NestedString(hr.Object, "spec", "kubeConfig", "secretRef", "name") + assert.Equal(t, "aliyun-kubeconfig", kubeconfigName) + kubeconfigKey, _, _ := unstructured.NestedString(hr.Object, "spec", "kubeConfig", "secretRef", "key") + assert.Equal(t, "kubeconfig", kubeconfigKey) + case "another-fake-targetNamespace": + kubeconfigName, _, _ := unstructured.NestedString(hr.Object, "spec", "kubeConfig", "secretRef", "name") + assert.Equal(t, "tencentcloud-kubeconfig", kubeconfigName) + kubeconfigKey, _, _ := unstructured.NestedString(hr.Object, "spec", "kubeConfig", "secretRef", "key") + assert.Equal(t, "kubeconfig", kubeconfigKey) + } + } + + fluxChart := createBareFluxHelmTemplateObject() + err = Client.Get(ctx, types.NamespacedName{Namespace: appNS, Name: getHelmTemplateName(appNS, appName)}, fluxChart) + assert.Nil(t, err) + + sourceAPIVersion, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "sourceRef", "apiVersion") + assert.Equal(t, "source.toolkit.fluxcd.io/v1beta2", sourceAPIVersion) + sourceKind, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "sourceRef", "kind") + assert.Equal(t, "GitRepository", sourceKind) + sourceName, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "sourceRef", "name") + assert.Equal(t, "fake-repo", sourceName) + sourceNS, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "sourceRef", "namespace") + assert.Equal(t, "fake-ns", sourceNS) + chart, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "chart") + assert.Equal(t, "./helm-chart", chart) + chartVersion, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "version") + assert.Equal(t, "0.1.0", chartVersion) + chartInterval, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "interval") + assert.Equal(t, "1m0s", chartInterval) + chartReconcileStrategy, _, _ := unstructured.NestedString(fluxChart.Object, "spec", "reconcileStrategy") + assert.Equal(t, "Revision", chartReconcileStrategy) + chartValueFiles, _, _ := unstructured.NestedStringSlice(fluxChart.Object, "spec", "valuesFiles") + assert.Equal(t, 1, len(chartValueFiles)) + assert.Equal(t, "./helm-chart/values.yaml", chartValueFiles[0]) + }, + }, + { + name: "create a Multi-Clusters FluxApp(Kustomization)", + fields: fields{ + Client: fake.NewFakeClientWithScheme(schema), + }, + args: args{ + app: kusApp.DeepCopy(), + }, + verify: func(t *testing.T, Client client.Client, err error) { + assert.Nil(t, err) + ctx := context.Background() + + kusList := createBareFluxKustomizationListObject() + appNS, appName := kusApp.GetNamespace(), kusApp.GetName() + err = Client.List(ctx, kusList, client.InNamespace(appNS), client.MatchingLabels{ + "app.kubernetes.io/managed-by": getKusManagerName(appNS, appName), + }) + assert.Nil(t, err) + assert.Equal(t, 2, len(kusList.Items)) + + for _, kus := range kusList.Items { + // same settings + sourceAPIVersion, _, _ := unstructured.NestedString(kus.Object, "spec", "sourceRef", "apiVersion") + assert.Equal(t, "source.toolkit.fluxcd.io/v1beta2", sourceAPIVersion) + sourceKind, _, _ := unstructured.NestedString(kus.Object, "spec", "sourceRef", "kind") + assert.Equal(t, "GitRepository", sourceKind) + sourceName, _, _ := unstructured.NestedString(kus.Object, "spec", "sourceRef", "name") + assert.Equal(t, "fake-repo", sourceName) + sourceNS, _, _ := unstructured.NestedString(kus.Object, "spec", "sourceRef", "namespace") + assert.Equal(t, "fake-ns", sourceNS) + path, _, _ := unstructured.NestedString(kus.Object, "spec", "path") + assert.Equal(t, "kustomization", path) + prune, _, _ := unstructured.NestedBool(kus.Object, "spec", "prune") + assert.True(t, prune) + + switch kus.GetName() { + case "fake-targetNamespace": + kubeconfigName, _, _ := unstructured.NestedString(kus.Object, "spec", "kubeConfig", "secretRef", "name") + assert.Equal(t, "aliyun-kubeconfig", kubeconfigName) + kubeconfigKey, _, _ := unstructured.NestedString(kus.Object, "spec", "kubeConfig", "secretRef", "key") + assert.Equal(t, "kubeconfig", kubeconfigKey) + case "another-fake-targetNamespace": + kubeconfigName, _, _ := unstructured.NestedString(kus.Object, "spec", "kubeConfig", "secretRef", "name") + assert.Equal(t, "tencentcloud-kubeconfig", kubeconfigName) + kubeconfigKey, _, _ := unstructured.NestedString(kus.Object, "spec", "kubeConfig", "secretRef", "key") + assert.Equal(t, "kubeconfig", kubeconfigKey) + } + } + }, + }, + { + name: "update the Multi-Clusters FluxApp(Kustomization)", + fields: fields{ + Client: fake.NewFakeClientWithScheme(schema, fluxKus.DeepCopy()), + }, + args: args{ + app: kusAppWithUpdate.DeepCopy(), + }, + verify: func(t *testing.T, Client client.Client, err error) { + assert.Nil(t, err) + ctx := context.Background() + + kusList := createBareFluxKustomizationListObject() + appNS, appName := kusApp.GetNamespace(), kusApp.GetName() + err = Client.List(ctx, kusList, client.InNamespace(appNS), client.MatchingLabels{ + "app.kubernetes.io/managed-by": getKusManagerName(appNS, appName), + }) + assert.Nil(t, err) + assert.Equal(t, 2, len(kusList.Items)) + + for _, kus := range kusList.Items { + if kus.GetName() == "fake-targetNamespace" { + path, _, _ := unstructured.NestedString(kus.Object, "spec", "path") + assert.Equal(t, "another-kustomization", path) + } + } + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + r := ApplicationReconciler{ + Client: tt.fields.Client, + log: log.NullLogger{}, + recorder: &record.FakeRecorder{}, + } + err := r.reconcileApp(tt.args.app) + tt.verify(t, tt.fields.Client, err) + }) + } +} + +func TestApplicationReconciler_GetName(t *testing.T) { + t.Run("get FluxApplicationReconciler name", func(t *testing.T) { + + r := &ApplicationReconciler{} + assert.Equal(t, "FluxApplicationReconciler", r.GetName()) + }) +} + +func TestApplicationReconciler_GetGroupName(t *testing.T) { + t.Run("get Flux Controllers GroupName", func(t *testing.T) { + r := &ApplicationReconciler{} + assert.Equal(t, "fluxcd", r.GetGroupName()) + }) +} diff --git a/controllers/fluxcd/constants.go b/controllers/fluxcd/constants.go new file mode 100644 index 00000000..643adc33 --- /dev/null +++ b/controllers/fluxcd/constants.go @@ -0,0 +1,29 @@ +/* +Copyright 2022 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fluxcd + +const controllerGroupName = "fluxcd" + +// AppType stand for the GitOps Application Type +type AppType string + +const ( + // HelmRelease stand for FluxCD HelmRelease Application Type + HelmRelease AppType = "HelmRelease" + // Kustomization stand for FluxCD Kustomization Application Type + Kustomization AppType = "Kustomization" +) diff --git a/pkg/api/gitops/v1alpha1/application.go b/pkg/api/gitops/v1alpha1/application.go index 85ec4e02..b6a58767 100644 --- a/pkg/api/gitops/v1alpha1/application.go +++ b/pkg/api/gitops/v1alpha1/application.go @@ -17,12 +17,694 @@ limitations under the License. package v1alpha1 import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +type FluxApplication struct { + Spec FluxApplicationSpec `json:"spec,omitempty"` +} + +type FluxApplicationSpec struct { + Source *FluxApplicationSource `json:"source,omitempty"` + Config *FluxApplicationConfig `json:"config"` +} + +type FluxApplicationSource struct { + SourceRef CrossNamespaceObjectReference `json:"sourceRef"` +} + +// CrossNamespaceObjectReference contains enough information to let you locate +// the typed referenced object at cluster level. +type CrossNamespaceObjectReference struct { + // APIVersion of the referent. + APIVersion string `json:"apiVersion,omitempty"` + + // Kind of the referent. + // Enum=HelmRepository;GitRepository;Bucket + Kind string `json:"kind,omitempty"` + + // Name of the referent. + Name string `json:"name"` + + // Namespace of the referent. + Namespace string `json:"namespace,omitempty"` +} + +type FluxApplicationDestination struct { + // The KubeConfig for reconciling the Kustomization on a remote cluster. + // When used in combination with KustomizationSpec.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 KustomizationSpec.ServiceAccountName + // is empty. + KubeConfig *KubeConfig `json:"kubeConfig,omitempty"` + // TargetNamespace to target when performing operations for the HelmRelease. + // Defaults to the namespace of the HelmRelease. + TargetNamespace string `json:"targetNamespace,omitempty"` +} + +// KubeConfig references a Kubernetes secret that contains a kubeconfig file. +type KubeConfig struct { + // SecretRef holds the name of a secret that contains a key with + // the kubeconfig file as the value. If no key is set, the key will default + // to 'value'. The secret must be in the same namespace as + // the Kustomization. + // 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 Kustomization. + SecretRef SecretKeyReference `json:"secretRef,omitempty"` +} + +// SecretKeyReference contains enough information to locate the referenced Kubernetes Secret object in the same +// namespace. Optionally a key can be specified. +// Use this type instead of core/v1 SecretKeySelector when the Key is optional and the Optional field is not +// applicable. +type SecretKeyReference struct { + // Name of the Secret. + Name string `json:"name"` + + // Key in the Secret, when not specified an implementation-specific default key is used. + Key string `json:"key,omitempty"` +} + +type FluxApplicationConfig struct { + // HelmRelease for FluxCD HelmRelease + HelmRelease *HelmReleaseSpec `json:"helmRelease,omitempty"` + + // Kustomization for FluxCD Kustomization + Kustomization []*KustomizationSpec `json:"kustomization,omitempty"` +} + +// HelmReleaseSpec defines the desired state of a Helm release. +type HelmReleaseSpec struct { + // Chart defines the template of the v1beta2.HelmChart that should be created + // for this HelmRelease. + Chart *HelmChartTemplateSpec `json:"chart,omitempty"` + + // Template ref a HelmTemplate that has been saved before + Template string `json:"template,omitempty"` + + // HelmReleaseConfig stand for multi-clusters and multi-targetNamespace config + Deploy []*Deploy `json:"deploy"` +} + +// HelmChartTemplateSpec is just a simple copy of helm.toolkit.fluxcd.io/HelmRelease's HelmChartTemplateSpec fields +// exclude sourceRef field because it's in the fluxApp.spec.source field +type HelmChartTemplateSpec struct { + // The name or path the Helm chart is available at in the SourceRef. + Chart string `json:"chart"` + + // Version semver expression, ignored for charts from v1beta2.GitRepository and + // v1beta2.Bucket sources. Defaults to latest when omitted. + // +kubebuilder:default:=* + Version string `json:"version,omitempty"` + + // Interval at which to check the v1beta2.Source for updates. Defaults to + // 'HelmReleaseSpec.Interval'. + Interval *metav1.Duration `json:"interval,omitempty"` + + // 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. + ReconcileStrategy string `json:"reconcileStrategy,omitempty"` + + // 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. + ValuesFiles []string `json:"valuesFiles,omitempty"` +} + +type Deploy struct { + // Destination stand for the destination of the helmrelease + Destination FluxApplicationDestination `json:"destination"` + + // The interval at which to reconcile the Kustomization. + Interval metav1.Duration `json:"interval"` + // Suspend tells the controller to suspend reconciliation for this HelmRelease, + // it does not apply to already started reconciliations. Defaults to false. + Suspend bool `json:"suspend,omitempty"` + + // 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'. + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // DependsOn may contain a meta.NamespacedObjectReference slice with + // references to HelmRelease resources that must be ready before this HelmRelease + // can be reconciled. + DependsOn []NamespacedObjectReference `json:"dependsOn,omitempty"` + + // The name of the Kubernetes service account to impersonate + // when reconciling this HelmRelease. + ServiceAccountName string `json:"serviceAccountName,omitempty"` + + // ReleaseName used for the Helm release. Defaults to a composition of + // '[TargetNamespace-]Name'. + ReleaseName string `json:"releaseName,omitempty"` + + // StorageNamespace used for the Helm storage. + // Defaults to the namespace of the HelmRelease. + StorageNamespace string `json:"storageNamespace,omitempty"` + + // MaxHistory is the number of revisions saved by Helm for this HelmRelease. + // Use '0' for an unlimited number of revisions; defaults to '10'. + MaxHistory *int `json:"maxHistory,omitempty"` + + // Install holds the configuration for Helm install actions for this HelmRelease. + Install *Install `json:"install,omitempty"` + + // Upgrade holds the configuration for Helm upgrade actions for this HelmRelease. + Upgrade *Upgrade `json:"upgrade,omitempty"` + + // Test holds the configuration for Helm test actions for this HelmRelease. + Test *Test `json:"test,omitempty"` + + // Rollback holds the configuration for Helm rollback actions for this HelmRelease. + Rollback *Rollback `json:"rollback,omitempty"` + + // Uninstall holds the configuration for Helm uninstall actions for this HelmRelease. + Uninstall *Uninstall `json:"uninstall,omitempty"` + + // ValuesFrom holds references to resources containing Helm values for this HelmRelease, + // and information about how they should be merged. + ValuesFrom []ValuesReference `json:"valuesFrom,omitempty"` + + // Values holds the values for this Helm release. + Values *apiextensionsv1.JSON `json:"values,omitempty"` + + // PostRenderers holds an array of Helm PostRenderers, which will be applied in order + // of their definition. + PostRenderers []PostRenderer `json:"postRenderers,omitempty"` +} + +type Install struct { + // 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'. + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // Remediation holds the remediation configuration for when the Helm install + // action for the HelmRelease fails. The default is to not perform any action. + Remediation *InstallRemediation `json:"remediation,omitempty"` + + // DisableWait disables the waiting for resources to be ready after a Helm + // install has been performed. + DisableWait bool `json:"disableWait,omitempty"` + + // DisableWaitForJobs disables waiting for jobs to complete after a Helm + // install has been performed. + DisableWaitForJobs bool `json:"disableWaitForJobs,omitempty"` + + // DisableHooks prevents hooks from running during the Helm install action. + DisableHooks bool `json:"disableHooks,omitempty"` + + // DisableOpenAPIValidation prevents the Helm install action from validating + // rendered templates against the Kubernetes OpenAPI Schema. + DisableOpenAPIValidation bool `json:"disableOpenAPIValidation,omitempty"` + + // 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. + Replace bool `json:"replace,omitempty"` + + // SkipCRDs tells the Helm install action to not install any CRDs. By default, + // CRDs are installed if not already present. + SkipCRDs bool `json:"skipCRDs,omitempty"` + + // 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. + // + // Skip: do neither install nor replace (update) any CRDs. + // + // Create: new CRDs are created, existing CRDs are neither updated nor deleted. + // + // CreateReplace: new CRDs are created, existing CRDs are updated (replaced) + // but not deleted. + // + // 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. + CRDs CRDsPolicy `json:"crds,omitempty"` + + // 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. + CreateNamespace bool `json:"createNamespace,omitempty"` +} + +type InstallRemediation struct { + // 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. + Retries int `json:"retries,omitempty"` + + // IgnoreTestFailures tells the controller to skip remediation when the Helm + // tests are run after an install action but fail. Defaults to + // 'Test.IgnoreFailures'. + IgnoreTestFailures *bool `json:"ignoreTestFailures,omitempty"` + + // RemediateLastFailure tells the controller to remediate the last failure, when + // no retries remain. Defaults to 'false'. + RemediateLastFailure *bool `json:"remediateLastFailure,omitempty"` +} + +type Upgrade struct { + // 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'. + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // Remediation holds the remediation configuration for when the Helm upgrade + // action for the HelmRelease fails. The default is to not perform any action. + Remediation *UpgradeRemediation `json:"remediation,omitempty"` + + // DisableWait disables the waiting for resources to be ready after a Helm + // upgrade has been performed. + DisableWait bool `json:"disableWait,omitempty"` + + // DisableWaitForJobs disables waiting for jobs to complete after a Helm + // upgrade has been performed. + DisableWaitForJobs bool `json:"disableWaitForJobs,omitempty"` + + // DisableHooks prevents hooks from running during the Helm upgrade action. + DisableHooks bool `json:"disableHooks,omitempty"` + + // DisableOpenAPIValidation prevents the Helm upgrade action from validating + // rendered templates against the Kubernetes OpenAPI Schema. + DisableOpenAPIValidation bool `json:"disableOpenAPIValidation,omitempty"` + + // Force forces resource updates through a replacement strategy. + Force bool `json:"force,omitempty"` + + // PreserveValues will make Helm reuse the last release's values and merge in + // overrides from 'Values'. Setting this flag makes the HelmRelease + // non-declarative. + PreserveValues bool `json:"preserveValues,omitempty"` + + // CleanupOnFail allows deletion of new resources created during the Helm + // upgrade action when it fails. + CleanupOnFail bool `json:"cleanupOnFail,omitempty"` + + // 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. + // + // Skip: do neither install nor replace (update) any CRDs. + // + // Create: new CRDs are created, existing CRDs are neither updated nor deleted. + // + // CreateReplace: new CRDs are created, existing CRDs are updated (replaced) + // but not deleted. + // + // 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. + CRDs CRDsPolicy `json:"crds,omitempty"` +} + +type UpgradeRemediation struct { + // 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. + Retries int `json:"retries,omitempty"` + + // IgnoreTestFailures tells the controller to skip remediation when the Helm + // tests are run after an upgrade action but fail. + // Defaults to 'Test.IgnoreFailures'. + IgnoreTestFailures *bool `json:"ignoreTestFailures,omitempty"` + + // RemediateLastFailure tells the controller to remediate the last failure, when + // no retries remain. Defaults to 'false' unless 'Retries' is greater than 0. + RemediateLastFailure *bool `json:"remediateLastFailure,omitempty"` + + // Strategy to use for failure remediation. Defaults to 'rollback'. + Strategy *RemediationStrategy `json:"strategy,omitempty"` +} + +// CRDsPolicy defines the install/upgrade approach to use for CRDs when +// installing or upgrading a HelmRelease. +type CRDsPolicy string + +// RemediationStrategy returns the strategy to use to remediate a failed install +// or upgrade. +type RemediationStrategy string + +type Test struct { + // Enable enables Helm test actions for this HelmRelease after an Helm install + // or upgrade action has been performed. + Enable bool `json:"enable,omitempty"` + + // Timeout is the time to wait for any individual Kubernetes operation during + // the performance of a Helm test action. Defaults to 'HelmReleaseSpec.Timeout'. + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // 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'. + IgnoreFailures bool `json:"ignoreFailures,omitempty"` +} + +type Rollback struct { + // 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'. + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // DisableWait disables the waiting for resources to be ready after a Helm + // rollback has been performed. + DisableWait bool `json:"disableWait,omitempty"` + + // DisableWaitForJobs disables waiting for jobs to complete after a Helm + // rollback has been performed. + DisableWaitForJobs bool `json:"disableWaitForJobs,omitempty"` + + // DisableHooks prevents hooks from running during the Helm rollback action. + DisableHooks bool `json:"disableHooks,omitempty"` + + // Recreate performs pod restarts for the resource if applicable. + Recreate bool `json:"recreate,omitempty"` + + // Force forces resource updates through a replacement strategy. + Force bool `json:"force,omitempty"` + + // CleanupOnFail allows deletion of new resources created during the Helm + // rollback action when it fails. + CleanupOnFail bool `json:"cleanupOnFail,omitempty"` +} + +type Uninstall struct { + // 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'. + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // DisableHooks prevents hooks from running during the Helm rollback action. + DisableHooks bool `json:"disableHooks,omitempty"` + + // KeepHistory tells Helm to remove all associated resources and mark the + // release as deleted, but retain the release history. + KeepHistory bool `json:"keepHistory,omitempty"` + + // DisableWait disables waiting for all the resources to be deleted after + // a Helm uninstall is performed. + DisableWait bool `json:"disableWait,omitempty"` +} + +type ValuesReference struct { + // Kind of the values referent, valid values are ('Secret', 'ConfigMap'). + Kind string `json:"kind"` + + // Name of the values referent. Should reside in the same namespace as the + // referring resource. + Name string `json:"name"` + + // ValuesKey is the data key where the values.yaml or a specific value can be + // found at. Defaults to 'values.yaml'. + ValuesKey string `json:"valuesKey,omitempty"` + + // 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. + TargetPath string `json:"targetPath,omitempty"` + + // 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. + Optional bool `json:"optional,omitempty"` +} + +type PostRenderer struct { + // Kustomization to apply as PostRenderer. + Kustomize *Kustomize `json:"kustomize,omitempty"` +} + +type Kustomize struct { + // Strategic merge and JSON patches, defined as inline YAML objects, + // capable of targeting objects based on kind, label and annotation selectors. + Patches []Patch `json:"patches,omitempty"` + + // Strategic merge patches, defined as inline YAML objects. + PatchesStrategicMerge []apiextensionsv1.JSON `json:"patchesStrategicMerge,omitempty"` + + // 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. + Images []Image `json:"images,omitempty" yaml:"images,omitempty"` +} + +// KustomizationSpec defines the configuration to calculate the desired state from a Source using Kustomize. +type KustomizationSpec struct { + // Destination stand for the destination of the kustomization + Destination FluxApplicationDestination `json:"destination"` + + // DependsOn may contain a meta.NamespacedObjectReference slice + // with references to Kustomization resources that must be ready before this + // Kustomization can be reconciled. + DependsOn []NamespacedObjectReference `json:"dependsOn,omitempty"` + + // Decrypt Kubernetes secrets before applying them on the cluster. + Decryption *Decryption `json:"decryption,omitempty"` + + // The interval at which to reconcile the Kustomization. + Interval metav1.Duration `json:"interval"` + + // The interval at which to retry a previously failed reconciliation. + // When not specified, the controller uses the KustomizationSpec.Interval + // value to retry failures. + RetryInterval *metav1.Duration `json:"retryInterval,omitempty"` + + // Path to the directory containing the kustomization.yaml file, or the + // set of plain YAMLs a kustomization.yaml should be generated for. + // Defaults to 'None', which translates to the root path of the SourceRef. + Path string `json:"path,omitempty"` + + // PostBuild describes which actions to perform on the YAML manifest + // generated by building the kustomize overlay. + PostBuild *PostBuild `json:"postBuild,omitempty"` + + // Prune enables garbage collection. + Prune bool `json:"prune"` + + // A list of resources to be included in the health assessment. + HealthChecks []NamespacedObjectKindReference `json:"healthChecks,omitempty"` + + // Strategic merge and JSON patches, defined as inline YAML objects, + // capable of targeting objects based on kind, label and annotation selectors. + Patches []Patch `json:"patches,omitempty"` + + // 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. + Images []Image `json:"images,omitempty"` + + // The name of the Kubernetes service account to impersonate + // when reconciling this Kustomization. + ServiceAccountName string `json:"serviceAccountName,omitempty"` + + // This flag tells the controller to suspend subsequent kustomize executions, + // it does not apply to already started executions. Defaults to false. + Suspend bool `json:"suspend,omitempty"` + + // Timeout for validation, apply and health checking operations. + // Defaults to 'Interval' duration. + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // Force instructs the controller to recreate resources + // when patching fails due to an immutable field change. + Force bool `json:"force,omitempty"` + + // Wait instructs the controller to check the health of all the reconciled resources. + // When enabled, the HealthChecks are ignored. Defaults to false. + Wait bool `json:"wait,omitempty"` +} + +// PostBuild describes which actions to perform on the YAML manifest +// generated by building the kustomize overlay. +type PostBuild struct { + // Substitute holds a map of key/value pairs. + // The variables defined in your YAML manifests + // that match any of the keys defined in the map + // will be substituted with the set value. + // Includes support for bash string replacement functions + // e.g. ${var:=default}, ${var:position} and ${var/substring/replacement}. + Substitute map[string]string `json:"substitute,omitempty"` + + // SubstituteFrom holds references to ConfigMaps and Secrets containing + // the variables and their values to be substituted in the YAML manifests. + // The ConfigMap and the Secret data keys represent the var names and they + // must match the vars declared in the manifests for the substitution to happen. + SubstituteFrom []SubstituteReference `json:"substituteFrom,omitempty"` +} + +// SubstituteReference contains a reference to a resource containing +// the variables name and value. +type SubstituteReference struct { + // Kind of the values referent, valid values are ('Secret', 'ConfigMap'). + Kind string `json:"kind"` + + // Name of the values referent. Should reside in the same namespace as the + // referring resource. + Name string `json:"name"` + + // Optional indicates whether the referenced resource must exist, or whether to + // tolerate its absence. If true and the referenced resource is absent, proceed + // as if the resource was present but empty, without any variables defined. + Optional bool `json:"optional,omitempty"` +} + +// Decryption defines how decryption is handled for Kubernetes manifests. +type Decryption struct { + // Provider is the name of the decryption engine. + Provider string `json:"provider"` + + // The secret name containing the private OpenPGP keys used for decryption. + SecretRef *LocalObjectReference `json:"secretRef,omitempty"` +} + +// ResourceInventory contains a list of Kubernetes resource object references that have been applied by a Kustomization. +type ResourceInventory struct { + // Entries of Kubernetes resource object references. + Entries []ResourceRef `json:"entries"` +} + +// ResourceRef contains the information necessary to locate a resource within a cluster. +type ResourceRef struct { + // ID is the string representation of the Kubernetes resource object's metadata, + // in the format '___'. + ID string `json:"id"` + + // Version is the API version of the Kubernetes resource object's kind. + Version string `json:"v"` +} + +// LocalObjectReference contains enough information to locate the referenced Kubernetes resource object. +type LocalObjectReference struct { + // Name of the referent. + // +required + Name string `json:"name"` +} + +// NamespacedObjectReference contains enough information to locate the referenced Kubernetes resource object in any +// namespace. +type NamespacedObjectReference struct { + // Name of the referent. + // +required + Name string `json:"name"` + + // Namespace of the referent, when not specified it acts as LocalObjectReference. + // +optional + Namespace string `json:"namespace,omitempty"` +} + +// NamespacedObjectKindReference contains enough information to locate the typed referenced Kubernetes resource object +// in any namespace. +type NamespacedObjectKindReference struct { + // API version of the referent, if not specified the Kubernetes preferred version will be used. + // +optional + APIVersion string `json:"apiVersion,omitempty"` + + // Kind of the referent. + // +required + Kind string `json:"kind"` + + // Name of the referent. + // +required + Name string `json:"name"` + + // Namespace of the referent, when not specified it acts as LocalObjectReference. + // +optional + Namespace string `json:"namespace,omitempty"` +} + +// Image contains an image name, a new name, a new tag or digest, which will replace the original name and tag. +type Image struct { + // Name is a tag-less image name. + // +required + Name string `json:"name"` + + // NewName is the value used to replace the original name. + // +optional + NewName string `json:"newName,omitempty"` + + // NewTag is the value used to replace the original tag. + // +optional + NewTag string `json:"newTag,omitempty"` + + // Digest is the value used to replace the original image tag. + // If digest is present NewTag value is ignored. + // +optional + Digest string `json:"digest,omitempty"` +} + +// Patch contains an inline StrategicMerge or JSON6902 patch, and the target the patch should +// be applied to. +type Patch struct { + // Patch contains an inline StrategicMerge patch or an inline JSON6902 patch with + // an array of operation objects. + // +required + Patch string `json:"patch,omitempty"` + + // Target points to the resources that the patch document should be applied to. + // +optional + Target Selector `json:"target,omitempty"` +} + +// Selector specifies a set of resources. Any resource that matches intersection of all conditions is included in this +// set. +type Selector struct { + // 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 + // +optional + Group string `json:"group,omitempty"` + + // 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 + // +optional + Version string `json:"version,omitempty"` + + // 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 + // +optional + Kind string `json:"kind,omitempty"` + + // Namespace to select resources from. + // +optional + Namespace string `json:"namespace,omitempty"` + + // Name to match resources with. + // +optional + Name string `json:"name,omitempty"` + + // 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. + // +optional + AnnotationSelector string `json:"annotationSelector,omitempty"` + + // 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. + // +optional + LabelSelector string `json:"labelSelector,omitempty"` +} + // ApplicationSpec is the specification of the Application type ApplicationSpec struct { + Kind Engine `json:"kind"` ArgoApp *ArgoApplication `json:"argoApp,omitempty"` + FluxApp *FluxApplication `json:"fluxApp,omitempty"` } // ArgoApplication is a definition of Argo Application resource. @@ -347,7 +1029,6 @@ type SyncStrategyApply struct { // If no hook annotation is specified falls back to `kubectl apply`. type SyncStrategyHook struct { // Embed SyncStrategyApply type to inherit any `apply` options - // +optional SyncStrategyApply `json:",inline"` } @@ -368,7 +1049,9 @@ type Application struct { // ApplicationStatus represents the status of the Application type ApplicationStatus struct { + Kind Engine `json:"kind"` ArgoApp string `json:"argoApp,omitempty"` + FluxApp string `json:"fluxApp,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/api/gitops/v1alpha1/constants.go b/pkg/api/gitops/v1alpha1/constants.go index f312eacb..02d9689a 100644 --- a/pkg/api/gitops/v1alpha1/constants.go +++ b/pkg/api/gitops/v1alpha1/constants.go @@ -38,3 +38,18 @@ const ApplicationFinalizerName = "application." + GroupName const ArgoCDResourcesFinalizer = "resources-finalizer.argocd.argoproj.io" const ArtifactRepoLabelKey = GroupName + "/is-artifact-repository" + +type Engine string + +const ( + ArgoCD Engine = "argocd" + FluxCD Engine = "fluxcd" +) + +const ( + // SaveTemplateLabelKey control whether to save a HelmTemplate + SaveTemplateLabelKey = GroupName + "/save-helm-template" + + // HelmTemplateName represent the user interface HelmTemplate name + HelmTemplateName = GroupName + "/helm-template-name" +) diff --git a/pkg/api/gitops/v1alpha1/zz_generated.deepcopy.go b/pkg/api/gitops/v1alpha1/zz_generated.deepcopy.go index c483f20d..146d2270 100644 --- a/pkg/api/gitops/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/gitops/v1alpha1/zz_generated.deepcopy.go @@ -22,6 +22,8 @@ limitations under the License. package v1alpha1 import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -303,6 +305,11 @@ func (in *ApplicationSpec) DeepCopyInto(out *ApplicationSpec) { *out = new(ArgoApplication) (*in).DeepCopyInto(*out) } + if in.FluxApp != nil { + in, out := &in.FluxApp, &out.FluxApp + *out = new(FluxApplication) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSpec. @@ -461,6 +468,115 @@ func (in *Backoff) DeepCopy() *Backoff { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CrossNamespaceObjectReference) DeepCopyInto(out *CrossNamespaceObjectReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossNamespaceObjectReference. +func (in *CrossNamespaceObjectReference) DeepCopy() *CrossNamespaceObjectReference { + if in == nil { + return nil + } + out := new(CrossNamespaceObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Decryption) DeepCopyInto(out *Decryption) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(LocalObjectReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Decryption. +func (in *Decryption) DeepCopy() *Decryption { + if in == nil { + return nil + } + out := new(Decryption) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Deploy) DeepCopyInto(out *Deploy) { + *out = *in + in.Destination.DeepCopyInto(&out.Destination) + out.Interval = in.Interval + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } + if in.DependsOn != nil { + in, out := &in.DependsOn, &out.DependsOn + *out = make([]NamespacedObjectReference, len(*in)) + copy(*out, *in) + } + if in.MaxHistory != nil { + in, out := &in.MaxHistory, &out.MaxHistory + *out = new(int) + **out = **in + } + if in.Install != nil { + in, out := &in.Install, &out.Install + *out = new(Install) + (*in).DeepCopyInto(*out) + } + if in.Upgrade != nil { + in, out := &in.Upgrade, &out.Upgrade + *out = new(Upgrade) + (*in).DeepCopyInto(*out) + } + if in.Test != nil { + in, out := &in.Test, &out.Test + *out = new(Test) + (*in).DeepCopyInto(*out) + } + if in.Rollback != nil { + in, out := &in.Rollback, &out.Rollback + *out = new(Rollback) + (*in).DeepCopyInto(*out) + } + if in.Uninstall != nil { + in, out := &in.Uninstall, &out.Uninstall + *out = new(Uninstall) + (*in).DeepCopyInto(*out) + } + if in.ValuesFrom != nil { + in, out := &in.ValuesFrom, &out.ValuesFrom + *out = make([]ValuesReference, len(*in)) + copy(*out, *in) + } + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = new(apiextensionsv1.JSON) + (*in).DeepCopyInto(*out) + } + if in.PostRenderers != nil { + in, out := &in.PostRenderers, &out.PostRenderers + *out = make([]PostRenderer, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Deploy. +func (in *Deploy) DeepCopy() *Deploy { + if in == nil { + return nil + } + out := new(Deploy) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in Env) DeepCopyInto(out *Env) { { @@ -501,6 +617,139 @@ func (in *EnvEntry) DeepCopy() *EnvEntry { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FluxApplication) DeepCopyInto(out *FluxApplication) { + *out = *in + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxApplication. +func (in *FluxApplication) DeepCopy() *FluxApplication { + if in == nil { + return nil + } + out := new(FluxApplication) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FluxApplicationConfig) DeepCopyInto(out *FluxApplicationConfig) { + *out = *in + if in.HelmRelease != nil { + in, out := &in.HelmRelease, &out.HelmRelease + *out = new(HelmReleaseSpec) + (*in).DeepCopyInto(*out) + } + if in.Kustomization != nil { + in, out := &in.Kustomization, &out.Kustomization + *out = make([]*KustomizationSpec, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(KustomizationSpec) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxApplicationConfig. +func (in *FluxApplicationConfig) DeepCopy() *FluxApplicationConfig { + if in == nil { + return nil + } + out := new(FluxApplicationConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FluxApplicationDestination) DeepCopyInto(out *FluxApplicationDestination) { + *out = *in + if in.KubeConfig != nil { + in, out := &in.KubeConfig, &out.KubeConfig + *out = new(KubeConfig) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxApplicationDestination. +func (in *FluxApplicationDestination) DeepCopy() *FluxApplicationDestination { + if in == nil { + return nil + } + out := new(FluxApplicationDestination) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FluxApplicationSource) DeepCopyInto(out *FluxApplicationSource) { + *out = *in + out.SourceRef = in.SourceRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxApplicationSource. +func (in *FluxApplicationSource) DeepCopy() *FluxApplicationSource { + if in == nil { + return nil + } + out := new(FluxApplicationSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FluxApplicationSpec) DeepCopyInto(out *FluxApplicationSpec) { + *out = *in + if in.Source != nil { + in, out := &in.Source, &out.Source + *out = new(FluxApplicationSource) + **out = **in + } + if in.Config != nil { + in, out := &in.Config, &out.Config + *out = new(FluxApplicationConfig) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxApplicationSpec. +func (in *FluxApplicationSpec) DeepCopy() *FluxApplicationSpec { + if in == nil { + return nil + } + out := new(FluxApplicationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmChartTemplateSpec) DeepCopyInto(out *HelmChartTemplateSpec) { + *out = *in + if in.Interval != nil { + in, out := &in.Interval, &out.Interval + *out = new(v1.Duration) + **out = **in + } + if in.ValuesFiles != nil { + in, out := &in.ValuesFiles, &out.ValuesFiles + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartTemplateSpec. +func (in *HelmChartTemplateSpec) DeepCopy() *HelmChartTemplateSpec { + if in == nil { + return nil + } + out := new(HelmChartTemplateSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HelmFileParameter) DeepCopyInto(out *HelmFileParameter) { *out = *in @@ -531,6 +780,52 @@ func (in *HelmParameter) DeepCopy() *HelmParameter { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmReleaseSpec) DeepCopyInto(out *HelmReleaseSpec) { + *out = *in + if in.Chart != nil { + in, out := &in.Chart, &out.Chart + *out = new(HelmChartTemplateSpec) + (*in).DeepCopyInto(*out) + } + if in.Deploy != nil { + in, out := &in.Deploy, &out.Deploy + *out = make([]*Deploy, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Deploy) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmReleaseSpec. +func (in *HelmReleaseSpec) DeepCopy() *HelmReleaseSpec { + if in == nil { + return nil + } + out := new(HelmReleaseSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Image) DeepCopyInto(out *Image) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Image. +func (in *Image) DeepCopy() *Image { + if in == nil { + return nil + } + out := new(Image) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ImageUpdater) DeepCopyInto(out *ImageUpdater) { *out = *in @@ -629,6 +924,56 @@ func (in *Info) DeepCopy() *Info { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Install) DeepCopyInto(out *Install) { + *out = *in + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } + if in.Remediation != nil { + in, out := &in.Remediation, &out.Remediation + *out = new(InstallRemediation) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Install. +func (in *Install) DeepCopy() *Install { + if in == nil { + return nil + } + out := new(Install) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstallRemediation) DeepCopyInto(out *InstallRemediation) { + *out = *in + if in.IgnoreTestFailures != nil { + in, out := &in.IgnoreTestFailures, &out.IgnoreTestFailures + *out = new(bool) + **out = **in + } + if in.RemediateLastFailure != nil { + in, out := &in.RemediateLastFailure, &out.RemediateLastFailure + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallRemediation. +func (in *InstallRemediation) DeepCopy() *InstallRemediation { + if in == nil { + return nil + } + out := new(InstallRemediation) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JsonnetVar) DeepCopyInto(out *JsonnetVar) { *out = *in @@ -659,6 +1004,111 @@ func (in *KsonnetParameter) DeepCopy() *KsonnetParameter { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeConfig) DeepCopyInto(out *KubeConfig) { + *out = *in + out.SecretRef = in.SecretRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeConfig. +func (in *KubeConfig) DeepCopy() *KubeConfig { + if in == nil { + return nil + } + out := new(KubeConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KustomizationSpec) DeepCopyInto(out *KustomizationSpec) { + *out = *in + in.Destination.DeepCopyInto(&out.Destination) + if in.DependsOn != nil { + in, out := &in.DependsOn, &out.DependsOn + *out = make([]NamespacedObjectReference, len(*in)) + copy(*out, *in) + } + if in.Decryption != nil { + in, out := &in.Decryption, &out.Decryption + *out = new(Decryption) + (*in).DeepCopyInto(*out) + } + out.Interval = in.Interval + if in.RetryInterval != nil { + in, out := &in.RetryInterval, &out.RetryInterval + *out = new(v1.Duration) + **out = **in + } + if in.PostBuild != nil { + in, out := &in.PostBuild, &out.PostBuild + *out = new(PostBuild) + (*in).DeepCopyInto(*out) + } + if in.HealthChecks != nil { + in, out := &in.HealthChecks, &out.HealthChecks + *out = make([]NamespacedObjectKindReference, len(*in)) + copy(*out, *in) + } + if in.Patches != nil { + in, out := &in.Patches, &out.Patches + *out = make([]Patch, len(*in)) + copy(*out, *in) + } + if in.Images != nil { + in, out := &in.Images, &out.Images + *out = make([]Image, len(*in)) + copy(*out, *in) + } + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KustomizationSpec. +func (in *KustomizationSpec) DeepCopy() *KustomizationSpec { + if in == nil { + return nil + } + out := new(KustomizationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Kustomize) DeepCopyInto(out *Kustomize) { + *out = *in + if in.Patches != nil { + in, out := &in.Patches, &out.Patches + *out = make([]Patch, len(*in)) + copy(*out, *in) + } + if in.PatchesStrategicMerge != nil { + in, out := &in.PatchesStrategicMerge, &out.PatchesStrategicMerge + *out = make([]apiextensionsv1.JSON, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Images != nil { + in, out := &in.Images, &out.Images + *out = make([]Image, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Kustomize. +func (in *Kustomize) DeepCopy() *Kustomize { + if in == nil { + return nil + } + out := new(Kustomize) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in KustomizeImages) DeepCopyInto(out *KustomizeImages) { { @@ -678,6 +1128,51 @@ func (in KustomizeImages) DeepCopy() KustomizeImages { return *out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalObjectReference) DeepCopyInto(out *LocalObjectReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalObjectReference. +func (in *LocalObjectReference) DeepCopy() *LocalObjectReference { + if in == nil { + return nil + } + out := new(LocalObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedObjectKindReference) DeepCopyInto(out *NamespacedObjectKindReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedObjectKindReference. +func (in *NamespacedObjectKindReference) DeepCopy() *NamespacedObjectKindReference { + if in == nil { + return nil + } + out := new(NamespacedObjectKindReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedObjectReference) DeepCopyInto(out *NamespacedObjectReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedObjectReference. +func (in *NamespacedObjectReference) DeepCopy() *NamespacedObjectReference { + if in == nil { + return nil + } + out := new(NamespacedObjectReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Operation) DeepCopyInto(out *Operation) { *out = *in @@ -726,6 +1221,69 @@ func (in *OperationInitiator) DeepCopy() *OperationInitiator { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Patch) DeepCopyInto(out *Patch) { + *out = *in + out.Target = in.Target +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Patch. +func (in *Patch) DeepCopy() *Patch { + if in == nil { + return nil + } + out := new(Patch) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PostBuild) DeepCopyInto(out *PostBuild) { + *out = *in + if in.Substitute != nil { + in, out := &in.Substitute, &out.Substitute + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.SubstituteFrom != nil { + in, out := &in.SubstituteFrom, &out.SubstituteFrom + *out = make([]SubstituteReference, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostBuild. +func (in *PostBuild) DeepCopy() *PostBuild { + if in == nil { + return nil + } + out := new(PostBuild) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PostRenderer) DeepCopyInto(out *PostRenderer) { + *out = *in + if in.Kustomize != nil { + in, out := &in.Kustomize, &out.Kustomize + *out = new(Kustomize) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostRenderer. +func (in *PostRenderer) DeepCopy() *PostRenderer { + if in == nil { + return nil + } + out := new(PostRenderer) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceIgnoreDifferences) DeepCopyInto(out *ResourceIgnoreDifferences) { *out = *in @@ -756,6 +1314,41 @@ func (in *ResourceIgnoreDifferences) DeepCopy() *ResourceIgnoreDifferences { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceInventory) DeepCopyInto(out *ResourceInventory) { + *out = *in + if in.Entries != nil { + in, out := &in.Entries, &out.Entries + *out = make([]ResourceRef, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceInventory. +func (in *ResourceInventory) DeepCopy() *ResourceInventory { + if in == nil { + return nil + } + out := new(ResourceInventory) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceRef) DeepCopyInto(out *ResourceRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceRef. +func (in *ResourceRef) DeepCopy() *ResourceRef { + if in == nil { + return nil + } + out := new(ResourceRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RetryStrategy) DeepCopyInto(out *RetryStrategy) { *out = *in @@ -776,6 +1369,71 @@ func (in *RetryStrategy) DeepCopy() *RetryStrategy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Rollback) DeepCopyInto(out *Rollback) { + *out = *in + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rollback. +func (in *Rollback) DeepCopy() *Rollback { + if in == nil { + return nil + } + out := new(Rollback) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretKeyReference) DeepCopyInto(out *SecretKeyReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeyReference. +func (in *SecretKeyReference) DeepCopy() *SecretKeyReference { + if in == nil { + return nil + } + out := new(SecretKeyReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Selector) DeepCopyInto(out *Selector) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Selector. +func (in *Selector) DeepCopy() *Selector { + if in == nil { + return nil + } + out := new(Selector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubstituteReference) DeepCopyInto(out *SubstituteReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubstituteReference. +func (in *SubstituteReference) DeepCopy() *SubstituteReference { + if in == nil { + return nil + } + out := new(SubstituteReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SyncOperation) DeepCopyInto(out *SyncOperation) { *out = *in @@ -950,3 +1608,113 @@ func (in *SyncStrategyHook) DeepCopy() *SyncStrategyHook { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Test) DeepCopyInto(out *Test) { + *out = *in + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Test. +func (in *Test) DeepCopy() *Test { + if in == nil { + return nil + } + out := new(Test) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Uninstall) DeepCopyInto(out *Uninstall) { + *out = *in + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Uninstall. +func (in *Uninstall) DeepCopy() *Uninstall { + if in == nil { + return nil + } + out := new(Uninstall) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Upgrade) DeepCopyInto(out *Upgrade) { + *out = *in + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } + if in.Remediation != nil { + in, out := &in.Remediation, &out.Remediation + *out = new(UpgradeRemediation) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Upgrade. +func (in *Upgrade) DeepCopy() *Upgrade { + if in == nil { + return nil + } + out := new(Upgrade) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpgradeRemediation) DeepCopyInto(out *UpgradeRemediation) { + *out = *in + if in.IgnoreTestFailures != nil { + in, out := &in.IgnoreTestFailures, &out.IgnoreTestFailures + *out = new(bool) + **out = **in + } + if in.RemediateLastFailure != nil { + in, out := &in.RemediateLastFailure, &out.RemediateLastFailure + *out = new(bool) + **out = **in + } + if in.Strategy != nil { + in, out := &in.Strategy, &out.Strategy + *out = new(RemediationStrategy) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpgradeRemediation. +func (in *UpgradeRemediation) DeepCopy() *UpgradeRemediation { + if in == nil { + return nil + } + out := new(UpgradeRemediation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValuesReference) DeepCopyInto(out *ValuesReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValuesReference. +func (in *ValuesReference) DeepCopy() *ValuesReference { + if in == nil { + return nil + } + out := new(ValuesReference) + in.DeepCopyInto(out) + return out +}