Repository: kubesphere/ks-devops
Pull request #814 (2022-09-05 05:39:21)

Change the FluxApp field in ApplicationStatus
### What type of PR is this?

/kind feature
/kind design


### What this PR does / why we need it:

We can use the external fluxcd api package to represent the status of fluxcd application (HelmRelease and Kustomization) in the gitops.kubesphere.io/application CR.
So we can track the status of application in the frontend in the future.

### Which issue(s) this PR fixes:
<!--
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
Please leave it or change # to be None if there is no corresponding issue that exists
-->
Fixes #

### Special notes for reviewers:
```golang
type HelmReleaseStatus struct {
	Name   string                   `json:"name"`
	Status helmv2.HelmReleaseStatus `json:"status"`
}
```

We need the **name** of each flux application(HelmRelease or Kustomization) to identify different status because FluxApplication is a Multi-Cluster application that manage multiple fluxcd applications.

<!--
You can use the following command to let the DevOps SIG members help you to review your PR.
/cc @kubesphere/sig-devops 
And please avoid cc any individual.
-->
Please check the following list before waiting reviewers:

- [ ] Already committed the CRD files to [the Helm Chart](https://github.com/kubesphere-sigs/ks-devops-helm-chart/) if you created some new CRDs
- [ ] Already [added the permission](https://github.com/kubesphere/ks-installer/blob/9e063b085a0e43fdb3d0d9e3e7f4149146f14b9c/roles/ks-core/prepare/files/ks-init/role-templates.yaml) for the new API
- [ ] Already added the RBAC markers for the new controllers

### Does this PR introduce a user-facing change??
<!--
If no, just write "None" in the release-note block below.
If yes, a release note is required:
Enter your extended-release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

Please keep the note be same as your PR title if you believe it should be in the release notes.
-->
```release-note
None
```


ks-ci-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **linuxsuren** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @linuxsuren` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubesphere%2Fks-devops).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubesphere/ks-devops/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["linuxsuren"]} -->
codecov[bot]: # [Codecov](https://codecov.io/gh/kubesphere/ks-devops/pull/814?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) Report
> Merging [#814](https://codecov.io/gh/kubesphere/ks-devops/pull/814?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) (c63fceb) into [master](https://codecov.io/gh/kubesphere/ks-devops/commit/625e29c72d3788b908a45d06692bb9c83b9d0689?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) (625e29c) will **decrease** coverage by `0.04%`.
> The diff coverage is `n/a`.

```diff
@@            Coverage Diff             @@
##           master     #814      +/-   ##
==========================================
- Coverage   56.29%   56.24%   -0.05%     
==========================================
  Files         183      183              
  Lines       10916    10916              
==========================================
- Hits         6145     6140       -5     
- Misses       4403     4407       +4     
- Partials      368      369       +1     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `56.24% <ø> (-0.05%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/kubesphere/ks-devops/pull/814?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) | Coverage Δ | |
|---|---|---|
| [pkg/api/gitops/v1alpha1/application.go](https://codecov.io/gh/kubesphere/ks-devops/pull/814/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2FwaS9naXRvcHMvdjFhbHBoYTEvYXBwbGljYXRpb24uZ28=) | `100.00% <ø> (ø)` | |
| [.../jenkins/devopsproject/devopsproject\_controller.go](https://codecov.io/gh/kubesphere/ks-devops/pull/814/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvamVua2lucy9kZXZvcHNwcm9qZWN0L2Rldm9wc3Byb2plY3RfY29udHJvbGxlci5nbw==) | `40.62% <0.00%> (-2.61%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere)

chengleqi: @lxm 

Patch:
diff --git a/.github/Jenkinsfile b/.github/Jenkinsfile
index 1f088bb..ba81d22 100644
--- a/.github/Jenkinsfile
+++ b/.github/Jenkinsfile
@@ -12,6 +12,14 @@ pipeline {
           agent none
           steps {
             container('golangci') {
+              sh '''
+              go mod tidy
+              if [ -n "$(git status --porcelain)" ]; then
+                echo 'To fix this check, run "go mod tidy"'
+                git status # Show the files that failed to pass the check.
+                exit 1
+              fi
+              '''
               sh 'golangci-lint run ./... -v'
             }
           }
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 6abd1a3..bdac852 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -5,10 +5,6 @@ on:
   push:
     branches:
       - master
-      - tekton-support
-      - feat/*
-      - fix/*
-      - test-* # make it be easier for contributors to test
     tags:
       - 'v*.*.*'
     paths-ignore:
@@ -22,24 +18,6 @@ on:
       - "!config/**"
       - "OWNERS"
       - "PROJECT"
-  pull_request:
-    paths-ignore:
-      - "**.md"
-      - "LICENSE"
-      - "docs/**"
-      - ".devcontainer/**"
-      - "*.ya?ml" # ignore all yaml files(with suffix yaml or yml) in the root of the repository
-      - "!codecov.yml"
-      - "!.golangci.yml"
-      - "!config/**"
-      - "OWNERS"
-      - "PROJECT"
-    branches:
-      - 'master'
-      - 'tekton-support'
-      - 'jenkins-client-refactor'
-      - 'feat/*'
-      - 'fix/*'
 
 jobs:
   BuildController:
diff --git a/.github/workflows/ci-check-license.yaml b/.github/workflows/ci-check-license.yaml.bak
similarity index 100%
rename from .github/workflows/ci-check-license.yaml
rename to .github/workflows/ci-check-license.yaml.bak
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml.bak
similarity index 100%
rename from .github/workflows/lint.yaml
rename to .github/workflows/lint.yaml.bak
diff --git a/config/crd/bases/gitops.kubesphere.io_applications.yaml b/config/crd/bases/gitops.kubesphere.io_applications.yaml
index a74ccd7..fccdc89 100644
--- a/config/crd/bases/gitops.kubesphere.io_applications.yaml
+++ b/config/crd/bases/gitops.kubesphere.io_applications.yaml
@@ -2030,7 +2030,275 @@ spec:
               argoApp:
                 type: string
               fluxApp:
-                type: string
+                description: FluxApplicationStatus represent the status of a FluxApp
+                properties:
+                  helmReleaseStatus:
+                    additionalProperties:
+                      description: HelmReleaseStatus defines the observed state of
+                        a HelmRelease.
+                      properties:
+                        conditions:
+                          description: Conditions holds the conditions for the HelmRelease.
+                          items:
+                            description: "Condition contains details for one aspect
+                              of the current state of this API Resource. --- This
+                              struct is intended for direct use as an array at the
+                              field path .status.conditions.  For example, type FooStatus
+                              struct{     // Represents the observations of a foo's
+                              current state.     // Known .status.conditions.type
+                              are: \"Available\", \"Progressing\", and \"Degraded\"
+                              \    // +patchMergeKey=type     // +patchStrategy=merge
+                              \    // +listType=map     // +listMapKey=type     Conditions
+                              []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\"
+                              patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`
+                              \n     // other fields }"
+                            properties:
+                              lastTransitionTime:
+                                description: lastTransitionTime is the last time the
+                                  condition transitioned from one status to another.
+                                  This should be when the underlying condition changed.  If
+                                  that is not known, then using the time when the
+                                  API field changed is acceptable.
+                                format: date-time
+                                type: string
+                              message:
+                                description: message is a human readable message indicating
+                                  details about the transition. This may be an empty
+                                  string.
+                                maxLength: 32768
+                                type: string
+                              observedGeneration:
+                                description: observedGeneration represents the .metadata.generation
+                                  that the condition was set based upon. For instance,
+                                  if .metadata.generation is currently 12, but the
+                                  .status.conditions[x].observedGeneration is 9, the
+                                  condition is out of date with respect to the current
+                                  state of the instance.
+                                format: int64
+                                minimum: 0
+                                type: integer
+                              reason:
+                                description: reason contains a programmatic identifier
+                                  indicating the reason for the condition's last transition.
+                                  Producers of specific condition types may define
+                                  expected values and meanings for this field, and
+                                  whether the values are considered a guaranteed API.
+                                  The value should be a CamelCase string. This field
+                                  may not be empty.
+                                maxLength: 1024
+                                minLength: 1
+                                pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+                                type: string
+                              status:
+                                description: status of the condition, one of True,
+                                  False, Unknown.
+                                enum:
+                                - "True"
+                                - "False"
+                                - Unknown
+                                type: string
+                              type:
+                                description: type of condition in CamelCase or in
+                                  foo.example.com/CamelCase. --- Many .condition.type
+                                  values are consistent across resources like Available,
+                                  but because arbitrary conditions can be useful (see
+                                  .node.status.conditions), the ability to deconflict
+                                  is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
+                                maxLength: 316
+                                pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+                                type: string
+                            required:
+                            - lastTransitionTime
+                            - message
+                            - reason
+                            - status
+                            - type
+                            type: object
+                          type: array
+                        failures:
+                          description: Failures is the reconciliation failure count
+                            against the latest desired state. It is reset after a
+                            successful reconciliation.
+                          format: int64
+                          type: integer
+                        helmChart:
+                          description: HelmChart is the namespaced name of the HelmChart
+                            resource created by the controller for the HelmRelease.
+                          type: string
+                        installFailures:
+                          description: InstallFailures is the install failure count
+                            against the latest desired state. It is reset after a
+                            successful reconciliation.
+                          format: int64
+                          type: integer
+                        lastAppliedRevision:
+                          description: LastAppliedRevision is the revision of the
+                            last successfully applied source.
+                          type: string
+                        lastAttemptedRevision:
+                          description: LastAttemptedRevision is the revision of the
+                            last reconciliation attempt.
+                          type: string
+                        lastAttemptedValuesChecksum:
+                          description: LastAttemptedValuesChecksum is the SHA1 checksum
+                            of the values of the last reconciliation attempt.
+                          type: string
+                        lastHandledReconcileAt:
+                          description: LastHandledReconcileAt holds the value of the
+                            most recent reconcile request value, so a change of the
+                            annotation value can be detected.
+                          type: string
+                        lastReleaseRevision:
+                          description: LastReleaseRevision is the revision of the
+                            last successful Helm release.
+                          type: integer
+                        observedGeneration:
+                          description: ObservedGeneration is the last observed generation.
+                          format: int64
+                          type: integer
+                        upgradeFailures:
+                          description: UpgradeFailures is the upgrade failure count
+                            against the latest desired state. It is reset after a
+                            successful reconciliation.
+                          format: int64
+                          type: integer
+                      type: object
+                    description: HelmReleaseStatus represent the status of each HelmRelease
+                      the key is the HelmRelease's name and the value is the HelmRelease's
+                      status
+                    type: object
+                  kustomizationStatus:
+                    additionalProperties:
+                      description: KustomizationStatus defines the observed state
+                        of a kustomization.
+                      properties:
+                        conditions:
+                          items:
+                            description: "Condition contains details for one aspect
+                              of the current state of this API Resource. --- This
+                              struct is intended for direct use as an array at the
+                              field path .status.conditions.  For example, type FooStatus
+                              struct{     // Represents the observations of a foo's
+                              current state.     // Known .status.conditions.type
+                              are: \"Available\", \"Progressing\", and \"Degraded\"
+                              \    // +patchMergeKey=type     // +patchStrategy=merge
+                              \    // +listType=map     // +listMapKey=type     Conditions
+                              []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\"
+                              patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`
+                              \n     // other fields }"
+                            properties:
+                              lastTransitionTime:
+                                description: lastTransitionTime is the last time the
+                                  condition transitioned from one status to another.
+                                  This should be when the underlying condition changed.  If
+                                  that is not known, then using the time when the
+                                  API field changed is acceptable.
+                                format: date-time
+                                type: string
+                              message:
+                                description: message is a human readable message indicating
+                                  details about the transition. This may be an empty
+                                  string.
+                                maxLength: 32768
+                                type: string
+                              observedGeneration:
+                                description: observedGeneration represents the .metadata.generation
+                                  that the condition was set based upon. For instance,
+                                  if .metadata.generation is currently 12, but the
+                                  .status.conditions[x].observedGeneration is 9, the
+                                  condition is out of date with respect to the current
+                                  state of the instance.
+                                format: int64
+                                minimum: 0
+                                type: integer
+                              reason:
+                                description: reason contains a programmatic identifier
+                                  indicating the reason for the condition's last transition.
+                                  Producers of specific condition types may define
+                                  expected values and meanings for this field, and
+                                  whether the values are considered a guaranteed API.
+                                  The value should be a CamelCase string. This field
+                                  may not be empty.
+                                maxLength: 1024
+                                minLength: 1
+                                pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+                                type: string
+                              status:
+                                description: status of the condition, one of True,
+                                  False, Unknown.
+                                enum:
+                                - "True"
+                                - "False"
+                                - Unknown
+                                type: string
+                              type:
+                                description: type of condition in CamelCase or in
+                                  foo.example.com/CamelCase. --- Many .condition.type
+                                  values are consistent across resources like Available,
+                                  but because arbitrary conditions can be useful (see
+                                  .node.status.conditions), the ability to deconflict
+                                  is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
+                                maxLength: 316
+                                pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+                                type: string
+                            required:
+                            - lastTransitionTime
+                            - message
+                            - reason
+                            - status
+                            - type
+                            type: object
+                          type: array
+                        inventory:
+                          description: Inventory contains the list of Kubernetes resource
+                            object references that have been successfully applied.
+                          properties:
+                            entries:
+                              description: Entries of Kubernetes resource object references.
+                              items:
+                                description: ResourceRef contains the information
+                                  necessary to locate a resource within a cluster.
+                                properties:
+                                  id:
+                                    description: ID is the string representation of
+                                      the Kubernetes resource object's metadata, in
+                                      the format '<namespace>_<name>_<group>_<kind>'.
+                                    type: string
+                                  v:
+                                    description: Version is the API version of the
+                                      Kubernetes resource object's kind.
+                                    type: string
+                                required:
+                                - id
+                                - v
+                                type: object
+                              type: array
+                          required:
+                          - entries
+                          type: object
+                        lastAppliedRevision:
+                          description: The last successfully applied revision. The
+                            revision format for Git sources is <branch|tag>/<commit-sha>.
+                          type: string
+                        lastAttemptedRevision:
+                          description: LastAttemptedRevision is the revision of the
+                            last reconciliation attempt.
+                          type: string
+                        lastHandledReconcileAt:
+                          description: LastHandledReconcileAt holds the value of the
+                            most recent reconcile request value, so a change of the
+                            annotation value can be detected.
+                          type: string
+                        observedGeneration:
+                          description: ObservedGeneration is the last reconciled generation.
+                          format: int64
+                          type: integer
+                      type: object
+                    description: KustomizationStatus represent the status of each
+                      Kustomization the key is the Kustomization's name and the value
+                      is the Kustomization's status
+                    type: object
+                type: object
               kind:
                 description: Engine is the backend GitOps Solutions type
                 type: string
diff --git a/controllers/fluxcd/application-controller.go b/controllers/fluxcd/application-controller.go
index e7780d0..4ff1eec 100644
--- a/controllers/fluxcd/application-controller.go
+++ b/controllers/fluxcd/application-controller.go
@@ -33,6 +33,7 @@ import (
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/manager"
+	"time"
 )
 
 //+kubebuilder:rbac:groups=gitops.kubesphere.io,resources=applications,verbs=watch;get;list
@@ -396,7 +397,7 @@ func buildTemplateFromApp(fluxApp *v1alpha1.FluxApplication) (*sourcev1.HelmChar
 			},
 			Chart:             c.Chart,
 			Version:           c.Version,
-			Interval:          *c.Interval,
+			Interval:          convertInterval(c.Interval),
 			ReconcileStrategy: c.ReconcileStrategy,
 			ValuesFiles:       c.ValuesFiles,
 		},
@@ -492,6 +493,13 @@ func convertKubeconfig(kubeconfig *helmv2.KubeConfig) *kusv1.KubeConfig {
 	return &kusv1.KubeConfig{SecretRef: kubeconfig.SecretRef}
 }
 
+func convertInterval(interval *metav1.Duration) metav1.Duration {
+	if interval == nil {
+		return metav1.Duration{Duration: 10 * time.Minute}
+	}
+	return *interval
+}
+
 func checkFluxApp(fluxApp *v1alpha1.FluxApplication) (err error) {
 	if fluxApp.Spec.Config == nil {
 		return fmt.Errorf("should provide FluxApplication Configuration (HelmRelease or Kustomization)")
diff --git a/go.mod b/go.mod
index 2dbca1f..740b02d 100644
--- a/go.mod
+++ b/go.mod
@@ -20,8 +20,8 @@ require (
 	github.com/google/go-cmp v0.5.5
 	github.com/h2non/gock v1.0.9
 	github.com/jenkins-x/go-scm v1.11.19
-	github.com/jenkins-zh/jenkins-client v0.0.14-0.20220902054515-a6af553a94b4
-	github.com/jenkins-zh/jenkins-client/pkg/k8s v0.0.0-20220811083317-bb6f87a3f866
+	github.com/jenkins-zh/jenkins-client v0.0.14-0.20220905100332-0c9041a612a1
+	github.com/jenkins-zh/jenkins-client/pkg/k8s v0.0.0-20220905100332-0c9041a612a1
 	github.com/kubesphere/sonargo v0.0.2
 	github.com/onsi/ginkgo v1.16.5
 	github.com/onsi/gomega v1.18.1
@@ -133,9 +133,4 @@ require (
 	sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
 )
 
-replace (
-	github.com/golang/example => github.com/golang/example v0.0.0-20170904185048-46695d81d1fa
-	github.com/googleapis/gnostic => github.com/googleapis/gnostic v0.4.0
-	github.com/kubesphere/sonargo => github.com/kubesphere/sonargo v0.0.2
-	golang.org/x/text => golang.org/x/text v0.3.7
-)
+replace github.com/googleapis/gnostic => github.com/googleapis/gnostic v0.4.0
diff --git a/go.sum b/go.sum
index defc357..d10c5c5 100644
--- a/go.sum
+++ b/go.sum
@@ -389,10 +389,11 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i
 github.com/jenkins-x/go-scm v1.11.19 h1:H4CzaM/C/0QcCVLDh603Q6Bv4hqU4G3De2yQntWubqg=
 github.com/jenkins-x/go-scm v1.11.19/go.mod h1:eIcty4+tf6E7ycGOg0cUqnaLP+1LH1Z8zncQFQqRa3E=
 github.com/jenkins-zh/jenkins-cli v0.0.32/go.mod h1:uE1mH9PNITrg0sugv6HXuM/CSddg0zxXoYu3w57I3JY=
-github.com/jenkins-zh/jenkins-client v0.0.14-0.20220902054515-a6af553a94b4 h1:OkOUTuHOxW6awhNNzUAAVz1NoZ/RA+xb4VXCkrVDQgU=
-github.com/jenkins-zh/jenkins-client v0.0.14-0.20220902054515-a6af553a94b4/go.mod h1:T8M/g0p7jjx0NBwN6gNqsAAmT1P3B70GBBJJgsX9kLw=
-github.com/jenkins-zh/jenkins-client/pkg/k8s v0.0.0-20220811083317-bb6f87a3f866 h1:MQwSe0z1hju5X9c5BEfQAvkb4Fub1ZoHLo+ZLJ7mAyo=
-github.com/jenkins-zh/jenkins-client/pkg/k8s v0.0.0-20220811083317-bb6f87a3f866/go.mod h1:zetE8JfNmxkot7OCNXJyS/QwxFnfcUFzrs/jg4U2gAw=
+github.com/jenkins-zh/jenkins-client v0.0.13/go.mod h1:ICBk7OOoTafVP//f/VfKZ34c0ff8vJwVnOsF9btiMYU=
+github.com/jenkins-zh/jenkins-client v0.0.14-0.20220905100332-0c9041a612a1 h1:FqiUegQ9pu3n4l7KtvX6uOBg6dV4BndXUkJeuDT896c=
+github.com/jenkins-zh/jenkins-client v0.0.14-0.20220905100332-0c9041a612a1/go.mod h1:T8M/g0p7jjx0NBwN6gNqsAAmT1P3B70GBBJJgsX9kLw=
+github.com/jenkins-zh/jenkins-client/pkg/k8s v0.0.0-20220905100332-0c9041a612a1 h1:ZfmezrO6f1VCUeP+f7skW6U11a5NmdIiFU6giMDqjB0=
+github.com/jenkins-zh/jenkins-client/pkg/k8s v0.0.0-20220905100332-0c9041a612a1/go.mod h1:Gfq/TcOliP3fB+kNBpv/Hrx8XGt0Bz0VPJ7BxdY/u2U=
 github.com/jenkins-zh/jenkins-formulas v0.0.5/go.mod h1:zS8fm8u5L6FcjZM0QznXsLV9T2UtSVK+hT6Sm76iUZ4=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
@@ -929,6 +930,15 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/pkg/api/gitops/v1alpha1/application.go b/pkg/api/gitops/v1alpha1/application.go
index 8d45611..6a80126 100644
--- a/pkg/api/gitops/v1alpha1/application.go
+++ b/pkg/api/gitops/v1alpha1/application.go
@@ -236,6 +236,16 @@ type KustomizationSpec struct {
 	Wait bool `json:"wait,omitempty"`
 }
 
+// FluxApplicationStatus represent the status of a FluxApp
+type FluxApplicationStatus struct {
+	// HelmReleaseStatus represent the status of each HelmRelease
+	// the key is the HelmRelease's name and the value is the HelmRelease's status
+	HelmReleaseStatus map[string]*helmv2.HelmReleaseStatus `json:"helmReleaseStatus,omitempty"`
+	// KustomizationStatus represent the status of each Kustomization
+	// the key is the Kustomization's name and the value is the Kustomization's status
+	KustomizationStatus map[string]*kusv1.KustomizationStatus `json:"kustomizationStatus,omitempty"`
+}
+
 // ApplicationSpec is the specification of the Application
 type ApplicationSpec struct {
 	Kind    Engine           `json:"kind,omitempty"`
@@ -586,9 +596,9 @@ type Application struct {
 
 // ApplicationStatus represents the status of the Application
 type ApplicationStatus struct {
-	Kind    Engine `json:"kind,omitempty"`
-	ArgoApp string `json:"argoApp,omitempty"`
-	FluxApp string `json:"fluxApp,omitempty"`
+	Kind    Engine                `json:"kind,omitempty"`
+	ArgoApp string                `json:"argoApp,omitempty"`
+	FluxApp FluxApplicationStatus `json:"fluxApp,omitempty"`
 }
 
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
diff --git a/pkg/api/gitops/v1alpha1/zz_generated.deepcopy.go b/pkg/api/gitops/v1alpha1/zz_generated.deepcopy.go
index 796642e..3740302 100644
--- a/pkg/api/gitops/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/api/gitops/v1alpha1/zz_generated.deepcopy.go
@@ -36,7 +36,7 @@ func (in *Application) DeepCopyInto(out *Application) {
 	out.TypeMeta = in.TypeMeta
 	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
 	in.Spec.DeepCopyInto(&out.Spec)
-	out.Status = in.Status
+	in.Status.DeepCopyInto(&out.Status)
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Application.
@@ -328,6 +328,7 @@ func (in *ApplicationSpec) DeepCopy() *ApplicationSpec {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ApplicationStatus) DeepCopyInto(out *ApplicationStatus) {
 	*out = *in
+	in.FluxApp.DeepCopyInto(&out.FluxApp)
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationStatus.
@@ -693,6 +694,51 @@ func (in *FluxApplicationSpec) DeepCopy() *FluxApplicationSpec {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FluxApplicationStatus) DeepCopyInto(out *FluxApplicationStatus) {
+	*out = *in
+	if in.HelmReleaseStatus != nil {
+		in, out := &in.HelmReleaseStatus, &out.HelmReleaseStatus
+		*out = make(map[string]*v2beta1.HelmReleaseStatus, len(*in))
+		for key, val := range *in {
+			var outVal *v2beta1.HelmReleaseStatus
+			if val == nil {
+				(*out)[key] = nil
+			} else {
+				in, out := &val, &outVal
+				*out = new(v2beta1.HelmReleaseStatus)
+				(*in).DeepCopyInto(*out)
+			}
+			(*out)[key] = outVal
+		}
+	}
+	if in.KustomizationStatus != nil {
+		in, out := &in.KustomizationStatus, &out.KustomizationStatus
+		*out = make(map[string]*v1beta2.KustomizationStatus, len(*in))
+		for key, val := range *in {
+			var outVal *v1beta2.KustomizationStatus
+			if val == nil {
+				(*out)[key] = nil
+			} else {
+				in, out := &val, &outVal
+				*out = new(v1beta2.KustomizationStatus)
+				(*in).DeepCopyInto(*out)
+			}
+			(*out)[key] = outVal
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxApplicationStatus.
+func (in *FluxApplicationStatus) DeepCopy() *FluxApplicationStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(FluxApplicationStatus)
+	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


Test patch:
diff --git a/controllers/fluxcd/application-controller_test.go b/controllers/fluxcd/application-controller_test.go
index ce01669..3cf84a9 100644
--- a/controllers/fluxcd/application-controller_test.go
+++ b/controllers/fluxcd/application-controller_test.go
@@ -402,6 +402,9 @@ func TestApplicationReconciler_reconcileApp(t *testing.T) {
 	helmAppWithTemplate.Spec.FluxApp.Spec.Config.HelmRelease.Chart = nil
 	helmAppWithTemplate.Spec.FluxApp.Spec.Config.HelmRelease.Template = "fake-app"
 
+	helmAppWithNoInterval := helmAppWithLabel.DeepCopy()
+	helmAppWithNoInterval.Spec.FluxApp.Spec.Config.HelmRelease.Chart.Interval = nil
+
 	kusApp := &v1alpha1.Application{
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: "fake-ns",
@@ -621,6 +624,36 @@ func TestApplicationReconciler_reconcileApp(t *testing.T) {
 				assert.Equal(t, "./helm-chart/values.yaml", chartValueFiles[0])
 			},
 		},
+		{
+			name: "create the Multi-Clusters FluxApp(HelmRelease) but with no Interval for HelmChart",
+			fields: fields{
+				Client: fake.NewFakeClientWithScheme(schema),
+			},
+			args: args{
+				app: helmAppWithNoInterval.DeepCopy(),
+			},
+			verify: func(t *testing.T, Client client.Client, err error) {
+				assert.Nil(t, err)
+				ctx := context.Background()
+				appNS, appName := helmApp.GetNamespace(), helmApp.GetName()
+
+				fluxChart := &sourcev1.HelmChart{}
+				err = Client.Get(ctx, types.NamespacedName{Namespace: appNS, Name: appName}, fluxChart)
+				assert.Nil(t, err)
+
+				assert.Equal(t, "source.toolkit.fluxcd.io/v1beta2", fluxChart.Spec.SourceRef.APIVersion)
+				assert.Equal(t, "GitRepository", fluxChart.Spec.SourceRef.Kind)
+				assert.Equal(t, "fake-repo", fluxChart.Spec.SourceRef.Name)
+				assert.Equal(t, "./helm-chart", fluxChart.Spec.Chart)
+				assert.Equal(t, "0.1.0", fluxChart.Spec.Version)
+				// The default interval is 10m0s
+				assert.Equal(t, 10*time.Minute, fluxChart.Spec.Interval.Duration)
+				assert.Equal(t, "Revision", fluxChart.Spec.ReconcileStrategy)
+				chartValueFiles := fluxChart.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{
@@ -940,6 +973,31 @@ func TestApplicationReconciler_convertKubeconfig(t *testing.T) {
 	}
 }
 
+func TestApplicationReconciler_convertInterval(t *testing.T) {
+	tests := []struct {
+		name   string
+		args   *metav1.Duration
+		expect metav1.Duration
+	}{
+		{
+			name:   "did not set interval",
+			args:   nil,
+			expect: metav1.Duration{Duration: 10 * time.Minute},
+		},
+		{
+			name:   "set interval",
+			args:   &metav1.Duration{Duration: 5 * time.Minute},
+			expect: metav1.Duration{Duration: 5 * time.Minute},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			assert.Equal(t, tt.expect.Duration, convertInterval(tt.args).Duration)
+		})
+	}
+}
+
 func TestApplicationReconciler_SetupWithManager(t *testing.T) {
 	schema, err := v1alpha1.SchemeBuilder.Register().Build()
 	assert.Nil(t, err)