├── docs └── img │ └── kcl-flux.png ├── config ├── dev │ ├── namespace.yaml │ └── kustomization.yaml ├── testdata │ └── status-defaults │ │ └── empty-kcl-run.yaml ├── default │ ├── namespace.yaml │ └── kustomization.yaml ├── samples │ ├── kustomization.yaml │ └── krm.kcl.dev.fluxcd_v1alpha1_kclrun.yaml ├── rbac │ ├── kustomization.yaml │ ├── leader_election_role_binding.yaml │ ├── leader_election_role.yaml │ ├── role.yaml │ ├── role_binding.yaml │ ├── kclrun_viewer_role.yaml │ └── kclrun_editor_role.yaml ├── manager │ ├── kustomization.yaml │ └── deployment.yaml ├── release │ └── kustomization.yaml └── crd │ ├── kustomizeconfig.yaml │ ├── kustomization.yaml │ └── bases │ ├── krm.kcl.dev.fluxcd_kclruns.yaml │ └── ocirepositories.yaml ├── CODE_OF_CONDUCT.md ├── .gitignore ├── internal ├── kcl │ ├── kcl_test.go │ ├── kcl.go │ └── testdata │ │ └── crds │ │ └── crd.k ├── controller │ ├── utils.go │ ├── kclrun_controller_test.go │ ├── testdata │ │ └── crds │ │ │ └── crd.k │ └── suite_test.go ├── predicates │ └── source_predicate.go ├── statusreaders │ └── job.go └── inventory │ ├── inventory.go │ └── LICENSE ├── samples ├── ocirepo.yaml ├── gitrepo.yaml └── ocirepo_with_params.yaml ├── PROJECT ├── hack └── boilerplate.go.txt ├── .goreleaser.yaml ├── api └── v1alpha1 │ ├── doc.go │ ├── inventory_types.go │ ├── testdata │ └── kclrun.yaml │ ├── groupversion_info.go │ ├── reference_types.go │ ├── kclrun_types_test.go │ ├── zz_generated.deepcopy.go │ └── kclrun_types.go ├── .github ├── dependabot.yaml └── workflows │ ├── backport.yaml │ ├── release.yaml │ └── test.yaml ├── .vscode └── settings.json ├── Dockerfile ├── README-zh.md ├── README.md ├── cmd └── main.go ├── Makefile ├── LICENSE └── go.mod /docs/img/kcl-flux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kcl-lang/flux-kcl-controller/HEAD/docs/img/kcl-flux.png -------------------------------------------------------------------------------- /config/dev/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller 6 | name: source-system 7 | -------------------------------------------------------------------------------- /config/testdata/status-defaults/empty-kcl-run.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: krm.kcl.dev.fluxcd/v1alpha1 2 | kind: KCLRun 3 | metadata: 4 | name: status-defaults 5 | -------------------------------------------------------------------------------- /config/default/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller 6 | name: source-system 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | 3 | KCL controller follows the [KCL Code of Conduct](https://github.com/kcl-lang/kcl/blob/main/CODE_OF_CONDUCT.md). 4 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples of your project ## 2 | resources: 3 | - krm.kcl.dev.fluxcd_v1alpha1_kclrun.yaml 4 | #+kubebuilder:scaffold:manifestskustomizesamples 5 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - role.yaml 5 | - role_binding.yaml 6 | - leader_election_role.yaml 7 | - leader_election_role_binding.yaml 8 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - deployment.yaml 5 | images: 6 | - name: kcl-controller 7 | newName: ghcr.io/kcl-lang/flux-kcl-controller 8 | newTag: v0.1.0 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool 12 | *.out 13 | 14 | # Local build output dir 15 | bin/ 16 | testbin/ 17 | build/ 18 | -------------------------------------------------------------------------------- /config/dev/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: source-system 4 | resources: 5 | - ../crd 6 | - ../rbac 7 | - ../manager 8 | - github.com/fluxcd/source-controller/config/crd?ref=main 9 | - github.com/fluxcd/source-controller/config/manager?ref=main 10 | - namespace.yaml 11 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: source-system 4 | resources: 5 | - ../crd 6 | - ../rbac 7 | - ../manager 8 | - github.com/fluxcd/source-controller/config/crd?ref=main 9 | - github.com/fluxcd/source-controller/config/manager?ref=main 10 | - namespace.yaml 11 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /internal/kcl/kcl_test.go: -------------------------------------------------------------------------------- 1 | package kcl 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kcl-lang/flux-kcl-controller/api/v1alpha1" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCompileKclPackage(t *testing.T) { 11 | obj := &v1alpha1.KCLRun{} 12 | _, err := CompileKclPackage(obj, "testdata/crds", nil) 13 | assert.NoError(t, err) 14 | } 15 | -------------------------------------------------------------------------------- /config/release/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: flux-system 4 | resources: 5 | - ../manager 6 | patchesStrategicMerge: 7 | - |- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: kcl-controller 12 | spec: 13 | template: 14 | spec: 15 | serviceAccountName: source-controller 16 | -------------------------------------------------------------------------------- /config/samples/krm.kcl.dev.fluxcd_v1alpha1_kclrun.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: krm.kcl.dev.fluxcd/v1alpha1 2 | kind: KCLRun 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: kclrun 6 | app.kubernetes.io/instance: kclrun-sample 7 | app.kubernetes.io/part-of: 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: 10 | name: kclrun-sample 11 | spec: 12 | # TODO(user): Add fields here 13 | -------------------------------------------------------------------------------- /samples/ocirepo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta2 2 | kind: OCIRepository 3 | metadata: 4 | name: podinfo 5 | namespace: default 6 | spec: 7 | interval: 5m0s 8 | url: oci://ghcr.io/kcl-lang/podinfo 9 | ref: 10 | tag: latest 11 | --- 12 | apiVersion: krm.kcl.dev.fluxcd/v1alpha1 13 | kind: KCLRun 14 | metadata: 15 | name: kcl-deployment 16 | namespace: source-system 17 | spec: 18 | sourceRef: 19 | kind: OCIRepository 20 | name: podinfo 21 | -------------------------------------------------------------------------------- /samples/gitrepo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1 2 | kind: GitRepository 3 | metadata: 4 | name: kcl-deployment 5 | namespace: source-system 6 | spec: 7 | interval: 30s 8 | url: https://github.com/kcl-lang/flask-demo-kcl-manifests.git 9 | ref: 10 | branch: main 11 | --- 12 | apiVersion: krm.kcl.dev.fluxcd/v1alpha1 13 | kind: KCLRun 14 | metadata: 15 | name: kcl-deployment 16 | namespace: source-system 17 | spec: 18 | sourceRef: 19 | kind: GitRepository 20 | name: kcl-deployment -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | # Code generated by tool. DO NOT EDIT. 2 | # This file is used to track the info used to scaffold your project 3 | # and allow the plugins properly work. 4 | # More info: https://book.kubebuilder.io/reference/project-config.html 5 | domain: kcl-lang.io 6 | repo: github.com/kcl-lang/flux-kcl-controller 7 | resources: 8 | - api: 9 | crdVersion: v1 10 | namespaced: true 11 | controller: true 12 | domain: kcl-lang.io 13 | group: krm.kcl.dev.fluxcd 14 | kind: KCLRun 15 | path: github.com/kcl-lang/flux-kcl-controller/api/v1alpha1 16 | version: v1alpha1 17 | version: "3" 18 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - configmaps/status 23 | verbs: 24 | - get 25 | - update 26 | - patch 27 | - apiGroups: 28 | - "" 29 | resources: 30 | - events 31 | verbs: 32 | - create 33 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: source-reader 6 | rules: 7 | - apiGroups: 8 | - krm.kcl.dev.fluxcd 9 | resources: 10 | - kclruns 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - krm.kcl.dev.fluxcd 21 | resources: 22 | - kclruns/finalizers 23 | verbs: 24 | - update 25 | - apiGroups: 26 | - krm.kcl.dev.fluxcd 27 | resources: 28 | - kclruns/status 29 | verbs: 30 | - get 31 | - patch 32 | - update 33 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The KCL authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /samples/ocirepo_with_params.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta2 2 | kind: OCIRepository 3 | metadata: 4 | name: podinfo 5 | namespace: default 6 | spec: 7 | interval: 5m0s 8 | url: oci://ghcr.io/kcl-lang/podinfo 9 | ref: 10 | tag: latest 11 | --- 12 | apiVersion: krm.kcl.dev.fluxcd/v1alpha1 13 | kind: KCLRun 14 | metadata: 15 | name: kcl-deployment 16 | namespace: source-system 17 | spec: 18 | sourceRef: 19 | kind: OCIRepository 20 | name: podinfo 21 | path: "." 22 | config: 23 | arguments: 24 | image: "foo/bar" 25 | vendor: true 26 | sortKeys: true 27 | showHidden: false 28 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: source-reader 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: source-reader 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: source-system 13 | --- 14 | apiVersion: rbac.authorization.k8s.io/v1 15 | kind: ClusterRoleBinding 16 | metadata: 17 | name: source-writter 18 | roleRef: 19 | apiGroup: rbac.authorization.k8s.io 20 | kind: ClusterRole 21 | name: cluster-admin 22 | subjects: 23 | - kind: ServiceAccount 24 | name: default 25 | namespace: source-system 26 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | project_name: kcl-controller 2 | 3 | builds: 4 | - skip: true 5 | 6 | release: 7 | prerelease: auto 8 | footer: | 9 | ## Container images 10 | 11 | - `docker.io/kcllang/{{.ProjectName}}:{{.Tag}}` 12 | - `ghcr.io/kcl-lang/{{.ProjectName}}:{{.Tag}}` 13 | 14 | Supported architectures: `linux/amd64`, `linux/arm64`. 15 | 16 | The container images are built on GitHub hosted runners and are signed with cosign and GitHub OIDC. 17 | 18 | changelog: 19 | use: github-native 20 | 21 | checksum: 22 | name_template: 'checksums.txt' 23 | 24 | source: 25 | enabled: true 26 | 27 | sboms: 28 | - artifacts: archive 29 | - id: source 30 | artifacts: source 31 | -------------------------------------------------------------------------------- /config/rbac/kclrun_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view kclruns. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: kclrun-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: 10 | app.kubernetes.io/part-of: 11 | app.kubernetes.io/managed-by: kustomize 12 | name: kclrun-viewer-role 13 | rules: 14 | - apiGroups: 15 | - krm.kcl.dev.fluxcd 16 | resources: 17 | - kclruns 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - krm.kcl.dev.fluxcd 24 | resources: 25 | - kclruns/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/kclrun_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit kclruns. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: kclrun-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: 10 | app.kubernetes.io/part-of: 11 | app.kubernetes.io/managed-by: kustomize 12 | name: kclrun-editor-role 13 | rules: 14 | - apiGroups: 15 | - krm.kcl.dev.fluxcd 16 | resources: 17 | - kclruns 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - krm.kcl.dev.fluxcd 28 | resources: 29 | - kclruns/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /api/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The KCL authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 contains API Schema definitions for the kustomize.toolkit.fluxcd.io 18 | // v1 API group. 19 | // +kubebuilder:object:generate=true 20 | // +groupName=krm.kcl.dev.fluxcd 21 | package v1alpha1 22 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "gomod" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | groups: 9 | flux-deps: 10 | patterns: 11 | - "github.com/fluxcd/*" 12 | misc-deps: 13 | patterns: 14 | - "*" 15 | exclude-patterns: 16 | - "github.com/fluxcd/*" 17 | allow: 18 | - dependency-type: "direct" 19 | ignore: 20 | - dependency-name: "k8s.io/*" 21 | - dependency-name: "sigs.k8s.io/*" 22 | - package-ecosystem: "github-actions" 23 | directory: "/" 24 | schedule: 25 | interval: "daily" 26 | groups: 27 | ci: 28 | patterns: 29 | - "*" 30 | - package-ecosystem: "docker" 31 | directory: "/" 32 | schedule: 33 | interval: "daily" 34 | groups: 35 | docker: 36 | patterns: 37 | - "*" -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.activeBackground": "#65c89b", 4 | "activityBar.background": "#65c89b", 5 | "activityBar.foreground": "#15202b", 6 | "activityBar.inactiveForeground": "#15202b99", 7 | "activityBarBadge.background": "#945bc4", 8 | "activityBarBadge.foreground": "#e7e7e7", 9 | "commandCenter.border": "#15202b99", 10 | "sash.hoverBorder": "#65c89b", 11 | "statusBar.background": "#42b883", 12 | "statusBar.foreground": "#15202b", 13 | "statusBarItem.hoverBackground": "#359268", 14 | "statusBarItem.remoteBackground": "#42b883", 15 | "statusBarItem.remoteForeground": "#15202b", 16 | "titleBar.activeBackground": "#42b883", 17 | "titleBar.activeForeground": "#15202b", 18 | "titleBar.inactiveBackground": "#42b88399", 19 | "titleBar.inactiveForeground": "#15202b99" 20 | }, 21 | "peacock.color": "#42b883" 22 | } -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/krm.kcl.dev.fluxcd_kclruns.yaml 6 | #+kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patches: 9 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 10 | # patches here are for enabling the conversion webhook for each CRD 11 | #- path: patches/webhook_in_kclruns.yaml 12 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 13 | 14 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 15 | # patches here are for enabling the CA injection for each CRD 16 | #- path: patches/cainjection_in_kclruns.yaml 17 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 18 | 19 | # [WEBHOOK] To enable webhook, uncomment the following section 20 | # the following config is for teaching kustomize how to do kustomization for CRDs. 21 | 22 | #configurations: 23 | #- kustomizeconfig.yaml 24 | -------------------------------------------------------------------------------- /api/v1alpha1/inventory_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Flux authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | // ResourceInventory contains a list of Kubernetes resource object references 20 | // that have been applied by a Kustomization. 21 | type ResourceInventory struct { 22 | // Entries of Kubernetes resource object references. 23 | Entries []ResourceRef `json:"entries"` 24 | } 25 | 26 | // ResourceRef contains the information necessary to locate a resource within a cluster. 27 | type ResourceRef struct { 28 | // ID is the string representation of the Kubernetes resource object's metadata, 29 | // in the format '___'. 30 | ID string `json:"id"` 31 | 32 | // Version is the API version of the Kubernetes resource object's kind. 33 | Version string `json:"v"` 34 | } 35 | -------------------------------------------------------------------------------- /api/v1alpha1/testdata/kclrun.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kclrun.example.com/v1 2 | kind: KCLRun 3 | metadata: 4 | name: example-kclrun 5 | namespace: default 6 | spec: 7 | commonMetadata: 8 | annotations: 9 | some_annotation: "example_value" 10 | labels: 11 | app: my-app 12 | dependsOn: 13 | - name: my-kustomization 14 | namespace: default 15 | kind: Kustomization 16 | timeout: "10m" 17 | persistentClient: true 18 | kubeConfig: 19 | secretRef: 20 | name: my-kubeconfig 21 | serviceAccountName: my-service-account 22 | targetNamespace: my-target-namespace 23 | force: false 24 | interval: "5m" 25 | retryInterval: "1m" 26 | path: /path/to/kcl_mod_file_path 27 | config: 28 | arguments: 29 | - env="prod" 30 | - debug="true" 31 | settings: 32 | - setting1.yaml 33 | - setting2.yaml 34 | overrides: 35 | - app.image="v2" 36 | pathSelectors: 37 | - path.to.output 38 | vendor: false 39 | sortKeys: true 40 | showHidden: false 41 | disableNone: true 42 | argumentsReferences: 43 | - kind: ConfigMap 44 | name: config-map-reference 45 | optional: false 46 | prune: true 47 | healthChecks: 48 | - kind: Pod 49 | name: my-pod 50 | namespace: default 51 | wait: false 52 | sourceRef: 53 | kind: OCIRepository 54 | name: podinfo 55 | suspend: false 56 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The KCL authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the krm.kcl.dev.fluxcd v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=krm.kcl.dev.fluxcd 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "krm.kcl.dev.fluxcd", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /internal/kcl/kcl.go: -------------------------------------------------------------------------------- 1 | package kcl 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | 7 | "github.com/kcl-lang/flux-kcl-controller/api/v1alpha1" 8 | "kcl-lang.io/kcl-go/pkg/kcl" 9 | "kcl-lang.io/kpm/pkg/client" 10 | ) 11 | 12 | // Compile the KCL source code into kubernetes manifests. 13 | func CompileKclPackage(obj *v1alpha1.KCLRun, pkgPath string, vars map[string]string) (*kcl.KCLResultList, error) { 14 | cli, _ := client.NewKpmClient() 15 | opts := []client.RunOption{} 16 | 17 | pkgPath, err := filepath.Abs(pkgPath) 18 | if err != nil { 19 | return nil, err 20 | } 21 | opts = append(opts, client.WithWorkDir(pkgPath)) 22 | // Build KCL top level arguments 23 | var args []string 24 | for k, v := range vars { 25 | args = append(args, fmt.Sprintf("%s=%s", k, v)) 26 | } 27 | if obj != nil { 28 | if obj.Spec.Config != nil { 29 | args = append(args, obj.Spec.Config.Arguments...) 30 | opts = append( 31 | opts, 32 | client.WithSettingFiles(obj.Spec.Config.Settings), 33 | client.WithVendor(obj.Spec.Config.Vendor), 34 | client.WithArguments(args), 35 | client.WithOverrides(obj.Spec.Config.Overrides, false), 36 | client.WithPathSelectors(obj.Spec.Config.PathSelectors), 37 | client.WithSortKeys(obj.Spec.Config.SortKeys), 38 | client.WithShowHidden(obj.Spec.Config.ShowHidden), 39 | client.WithDisableNone(obj.Spec.Config.DisableNone), 40 | ) 41 | } 42 | } 43 | return cli.Run(opts...) 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/backport.yaml: -------------------------------------------------------------------------------- 1 | name: backport 2 | 3 | on: 4 | pull_request_target: 5 | types: [closed, labeled] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | pull-request: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write 15 | pull-requests: write 16 | if: github.event.pull_request.state == 'closed' && github.event.pull_request.merged && (github.event_name != 'labeled' || startsWith('backport:', github.event.label.name)) 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 20 | with: 21 | ref: ${{ github.event.pull_request.head.sha }} 22 | - name: Create backport PRs 23 | uses: korthout/backport-action@0193454f0c5947491d348f33a275c119f30eb736 # v3.2.1 24 | # xref: https://github.com/korthout/backport-action#inputs 25 | with: 26 | # Use token to allow workflows to be triggered for the created PR 27 | github_token: ${{ secrets.DEPLOY_ACCESS_TOKEN }} 28 | # Match labels with a pattern `backport:` 29 | label_pattern: '^backport:([^ ]+)$' 30 | # A bit shorter pull-request title than the default 31 | pull_title: '[${target_branch}] ${pull_title}' 32 | # Simpler PR description than default 33 | pull_description: |- 34 | Automated backport to `${target_branch}`, triggered by a label in #${pull_number}. 35 | -------------------------------------------------------------------------------- /internal/controller/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Flux authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controller 18 | 19 | import ( 20 | "strings" 21 | 22 | "github.com/fluxcd/pkg/ssa" 23 | ) 24 | 25 | func extractDigest(revision string) string { 26 | if strings.Contains(revision, "@") { 27 | // expects a revision in the @: format 28 | tagD := strings.Split(revision, "@") 29 | if len(tagD) != 2 { 30 | return "" 31 | } 32 | return tagD[1] 33 | } else { 34 | // revision in the : format 35 | return revision 36 | } 37 | } 38 | 39 | // HasChanged evaluates the given action and returns true 40 | // if the action type matches a resource mutation or deletion. 41 | func HasChanged(action ssa.Action) bool { 42 | switch action { 43 | case ssa.SkippedAction: 44 | return false 45 | case ssa.UnchangedAction: 46 | return false 47 | default: 48 | return true 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /api/v1alpha1/reference_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Flux authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import "fmt" 20 | 21 | // CrossNamespaceSourceReference contains enough information to let you locate the 22 | // typed Kubernetes resource object at cluster level. 23 | type CrossNamespaceSourceReference struct { 24 | // API version of the referent. 25 | // +optional 26 | APIVersion string `json:"apiVersion,omitempty"` 27 | 28 | // Kind of the referent. 29 | // +kubebuilder:validation:Enum=OCIRepository;GitRepository;Bucket 30 | // +required 31 | Kind string `json:"kind"` 32 | 33 | // Name of the referent. 34 | // +required 35 | Name string `json:"name"` 36 | 37 | // Namespace of the referent, defaults to the namespace of the Kubernetes 38 | // resource object that contains the reference. 39 | // +optional 40 | Namespace string `json:"namespace,omitempty"` 41 | } 42 | 43 | func (s *CrossNamespaceSourceReference) String() string { 44 | if s.Namespace != "" { 45 | return fmt.Sprintf("%s/%s/%s", s.Kind, s.Namespace, s.Name) 46 | } 47 | return fmt.Sprintf("%s/%s", s.Kind, s.Name) 48 | } 49 | -------------------------------------------------------------------------------- /config/manager/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: kcl-controller 5 | labels: 6 | control-plane: controller 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: kcl-controller 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | app: kcl-controller 16 | annotations: 17 | prometheus.io/scrape: "true" 18 | prometheus.io/port: "8083" 19 | spec: 20 | terminationGracePeriodSeconds: 10 21 | containers: 22 | - name: manager 23 | image: kcl-controller 24 | imagePullPolicy: Always 25 | securityContext: 26 | allowPrivilegeEscalation: false 27 | readOnlyRootFilesystem: true 28 | ports: 29 | - containerPort: 8083 30 | name: http-prom 31 | env: 32 | - name: RUNTIME_NAMESPACE 33 | valueFrom: 34 | fieldRef: 35 | fieldPath: metadata.namespace 36 | args: 37 | - --log-level=info 38 | - --enable-leader-election 39 | livenessProbe: 40 | httpGet: 41 | port: http-prom 42 | path: /metrics 43 | readinessProbe: 44 | httpGet: 45 | port: http-prom 46 | path: /metrics 47 | resources: 48 | limits: 49 | cpu: 1000m 50 | memory: 1Gi 51 | requests: 52 | cpu: 50m 53 | memory: 64Mi 54 | volumeMounts: 55 | - name: tmp 56 | mountPath: /tmp 57 | volumes: 58 | - name: tmp 59 | emptyDir: {} 60 | 61 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # We use the Go 1.23 version unless asked to use something else. 2 | # The GitHub Actions CI job sets this argument for a consistent Go version. 3 | ARG GO_VERSION=1.23 4 | ARG BASE_IMAGE=kcllang/kcl 5 | 6 | # Setup the base environment. The BUILDPLATFORM is set automatically by Docker. 7 | # The --platform=${BUILDPLATFORM} flag tells Docker to build the function using 8 | # the OS and architecture of the host running the build, not the OS and 9 | # architecture that we're building the function for. 10 | FROM --platform=$BUILDPLATFORM golang:${GO_VERSION} as build 11 | 12 | COPY / /src 13 | WORKDIR /src 14 | 15 | ENV CGO_ENABLED=0 16 | 17 | # We run go mod download in a separate step so that we can cache its results. 18 | # This lets us avoid re-downloading modules if we don't need to. The type=target 19 | # mount tells Docker to mount the current directory read-only in the WORKDIR. 20 | # The type=cache mount tells Docker to cache the Go modules cache across builds. 21 | RUN --mount=target=. --mount=type=cache,target=/go/pkg/mod go mod download 22 | 23 | # The TARGETOS and TARGETARCH args are set by docker. We set GOOS and GOARCH to 24 | # these values to ask Go to compile a binary for these architectures. If 25 | # TARGETOS and TARGETOS are different from BUILDPLATFORM, Go will cross compile 26 | # for us (e.g. compile a linux/amd64 binary on a linux/arm64 build machine). 27 | ARG TARGETOS 28 | ARG TARGETARCH 29 | 30 | # Build the function binary. The type=target mount tells Docker to mount the 31 | # current directory read-only in the WORKDIR. The type=cache mount tells Docker 32 | # to cache the Go modules cache across builds. 33 | RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -a -o kcl-controller cmd/main.go 34 | 35 | FROM ${BASE_IMAGE} as image 36 | RUN apt-get update && apt-get install -y ca-certificates tini 37 | COPY --from=build /src/kcl-controller /usr/local/bin/ 38 | # RUN addgroup -S controller && adduser -S controller -G controller 39 | RUN groupadd controller && useradd -g controller controller 40 | 41 | ENTRYPOINT [ "/usr/bin/tini", "--", "kcl-controller" ] 42 | -------------------------------------------------------------------------------- /api/v1alpha1/kclrun_types_test.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "testing" 7 | 8 | "github.com/fluxcd/pkg/apis/meta" 9 | "github.com/fluxcd/pkg/runtime/conditions" 10 | "github.com/stretchr/testify/assert" 11 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/apimachinery/pkg/util/yaml" 13 | ) 14 | 15 | func TestKCLRunDeserialize(t *testing.T) { 16 | yamlContent, err := os.ReadFile("testdata/kclrun.yaml") 17 | assert.NoError(t, err) 18 | kclRun := &KCLRun{} 19 | decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(yamlContent), 100) 20 | err = decoder.Decode(kclRun) 21 | assert.NoError(t, err) 22 | assert.Equal(t, "example-kclrun", kclRun.ObjectMeta.Name) 23 | assert.Equal(t, "default", kclRun.ObjectMeta.Namespace) 24 | assert.Equal(t, &v1.Duration{Duration: 600000000000}, kclRun.Spec.Timeout) 25 | assert.True(t, kclRun.Spec.Prune) 26 | assert.Equal(t, true, kclRun.Spec.Prune) 27 | assert.NotNil(t, kclRun.Spec.CommonMetadata) 28 | assert.Equal(t, "example_value", kclRun.Spec.CommonMetadata.Annotations["some_annotation"]) 29 | assert.Equal(t, "my-app", kclRun.Spec.CommonMetadata.Labels["app"]) 30 | assert.Contains(t, kclRun.Spec.DependsOn[0].Name, "my-kustomization") 31 | assert.NotNil(t, kclRun.Spec.KubeConfig) 32 | assert.Equal(t, "my-kubeconfig", kclRun.Spec.KubeConfig.SecretRef.Name) 33 | assert.Equal(t, "my-service-account", kclRun.Spec.ServiceAccountName) 34 | assert.Equal(t, "my-target-namespace", kclRun.Spec.TargetNamespace) 35 | assert.False(t, kclRun.Spec.Force) 36 | assert.NotNil(t, kclRun.Spec.RetryInterval) 37 | assert.Equal(t, "/path/to/kcl_mod_file_path", kclRun.Spec.Path) 38 | assert.NotNil(t, kclRun.Spec.Config) 39 | assert.Contains(t, kclRun.Spec.Config.Arguments, "env=\"prod\"") 40 | assert.NotNil(t, kclRun.Spec.ArgumentsReferences) 41 | assert.Equal(t, "ConfigMap", kclRun.Spec.ArgumentsReferences[0].Kind) 42 | assert.Equal(t, "config-map-reference", kclRun.Spec.ArgumentsReferences[0].Name) 43 | } 44 | 45 | func TestKCLRunConditions(t *testing.T) { 46 | obj := &KCLRun{} 47 | // Mark the object as ready. 48 | conditions.MarkTrue( 49 | obj, 50 | meta.ReadyCondition, 51 | meta.ReconciliationSucceededReason, 52 | "Set ready condition is true", 53 | ) 54 | assert.True(t, conditions.IsTrue(obj, meta.ReadyCondition)) 55 | } 56 | -------------------------------------------------------------------------------- /internal/predicates/source_predicate.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Flux authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package predicates 18 | 19 | import ( 20 | "github.com/fluxcd/pkg/runtime/conditions" 21 | "sigs.k8s.io/controller-runtime/pkg/event" 22 | "sigs.k8s.io/controller-runtime/pkg/predicate" 23 | 24 | sourcev1 "github.com/fluxcd/source-controller/api/v1" 25 | ) 26 | 27 | // SourceRevisionChangePredicate detects revision changes to the v1.Artifact of 28 | // a v1.Source object. 29 | type SourceRevisionChangePredicate struct { 30 | predicate.Funcs 31 | } 32 | 33 | func (SourceRevisionChangePredicate) Update(e event.UpdateEvent) bool { 34 | if e.ObjectOld == nil || e.ObjectNew == nil { 35 | return false 36 | } 37 | 38 | oldSource, ok := e.ObjectOld.(sourcev1.Source) 39 | if !ok { 40 | return false 41 | } 42 | 43 | newSource, ok := e.ObjectNew.(sourcev1.Source) 44 | if !ok { 45 | return false 46 | } 47 | 48 | if oldSource.GetArtifact() == nil && newSource.GetArtifact() != nil { 49 | return true 50 | } 51 | 52 | if oldSource.GetArtifact() != nil && newSource.GetArtifact() != nil && 53 | !oldSource.GetArtifact().HasRevision(newSource.GetArtifact().Revision) { 54 | return true 55 | } 56 | 57 | oldConditions, ok := e.ObjectOld.(conditions.Getter) 58 | if !ok { 59 | return false 60 | } 61 | 62 | newConditions, ok := e.ObjectNew.(conditions.Getter) 63 | if !ok { 64 | return false 65 | } 66 | 67 | if !conditions.IsReady(oldConditions) && conditions.IsReady(newConditions) { 68 | return true 69 | } 70 | 71 | return false 72 | } 73 | 74 | func (SourceRevisionChangePredicate) Create(e event.CreateEvent) bool { 75 | return false 76 | } 77 | 78 | func (SourceRevisionChangePredicate) Delete(e event.DeleteEvent) bool { 79 | return false 80 | } 81 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | workflow_dispatch: 8 | inputs: 9 | tag: 10 | description: 'image tag prefix' 11 | default: 'rc' 12 | required: true 13 | 14 | permissions: 15 | contents: read 16 | 17 | env: 18 | CONTROLLER: ${{ github.event.repository.name }} 19 | 20 | jobs: 21 | release: 22 | outputs: 23 | hashes: ${{ steps.hash.outputs.hashes }} 24 | image_url: ${{ steps.hash.outputs.image_url }} 25 | image_digest: ${{ steps.hash.outputs.image_digest }} 26 | runs-on: ubuntu-latest 27 | permissions: 28 | contents: write # needed to write releases 29 | id-token: write # needed for keyless signing 30 | packages: write # needed for ghcr access 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 34 | - name: Setup Kustomize 35 | uses: fluxcd/pkg/actions/kustomize@main 36 | - name: Prepare 37 | id: prep 38 | run: | 39 | VERSION="${{ github.event.inputs.tag }}-${GITHUB_SHA::8}" 40 | if [[ $GITHUB_REF == refs/tags/* ]]; then 41 | VERSION=${GITHUB_REF/refs\/tags\//} 42 | fi 43 | echo "version=${VERSION}" >> $GITHUB_OUTPUT 44 | - name: Setup Go 45 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 46 | with: 47 | go-version: 1.23 48 | cache-dependency-path: | 49 | **/go.sum 50 | **/go.mod 51 | - uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 52 | - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 53 | - uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2 54 | - uses: anchore/sbom-action/download-syft@7b36ad622f042cab6f59a75c2ac24ccb256e9b45 # v0.20.4 55 | - name: Docker login ghcr.io 56 | uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 57 | with: 58 | registry: ghcr.io 59 | username: kclbot 60 | password: ${{ secrets.DEPLOY_ACCESS_TOKEN }} 61 | - name: Docker login docker.io 62 | uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 63 | with: 64 | username: ${{ secrets.DOCKER_USERNAME }} 65 | password: ${{ secrets.DOCKER_PASSWORD }} 66 | - name: Docker meta 67 | id: meta 68 | uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 69 | with: 70 | images: | 71 | kcllang/${{ env.CONTROLLER }} 72 | ghcr.io/kcl-lang/${{ env.CONTROLLER }} 73 | tags: | 74 | type=raw,value=${{ steps.prep.outputs.version }} 75 | - name: Docker push 76 | uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 77 | id: build-push 78 | with: 79 | sbom: true 80 | provenance: true 81 | push: true 82 | builder: ${{ steps.buildx.outputs.name }} 83 | context: . 84 | file: ./Dockerfile 85 | platforms: linux/amd64 86 | tags: ${{ steps.meta.outputs.tags }} 87 | labels: ${{ steps.meta.outputs.labels }} 88 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - test* 9 | 10 | jobs: 11 | unit: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 16 | - name: Setup QEMU 17 | uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 18 | - name: Setup Docker Buildx 19 | id: buildx 20 | uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 21 | - name: Cache Docker layers 22 | uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 23 | id: cache 24 | with: 25 | path: /tmp/.buildx-cache 26 | key: ${{ runner.os }}-buildx-ghcache-${{ github.sha }} 27 | restore-keys: | 28 | ${{ runner.os }}-buildx-ghcache- 29 | - name: Setup Go 30 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 31 | with: 32 | go-version: 1.23 33 | cache-dependency-path: | 34 | **/go.sum 35 | **/go.mod 36 | - name: Setup Kubernetes 37 | uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0 38 | with: 39 | version: v0.20.0 40 | cluster_name: kind 41 | node_image: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72 42 | - name: Setup Kustomize 43 | uses: fluxcd/pkg/actions/kustomize@main 44 | - name: Enable integration tests 45 | # Only run integration tests for main branch 46 | if: github.ref == 'refs/heads/main' 47 | run: | 48 | echo 'GO_TEST_ARGS=-tags integration' >> $GITHUB_ENV 49 | - name: Run tests 50 | run: make test 51 | - name: Build container image 52 | run: | 53 | make docker-build IMG=test/flux-kcl-controller:latest \ 54 | BUILD_PLATFORMS=linux/amd64 \ 55 | BUILD_ARGS="--cache-from=type=local,src=/tmp/.buildx-cache \ 56 | --cache-to=type=local,dest=/tmp/.buildx-cache-new,mode=max \ 57 | --load" 58 | - # Temp fix 59 | # https://github.com/docker/build-push-action/issues/252 60 | # https://github.com/moby/buildkit/issues/1896 61 | name: Move cache 62 | run: | 63 | rm -rf /tmp/.buildx-cache 64 | mv /tmp/.buildx-cache-new /tmp/.buildx-cache 65 | - name: Load test image 66 | run: kind load docker-image test/flux-kcl-controller:latest 67 | - name: Install CRDs 68 | run: make install 69 | - name: Run default status test 70 | run: | 71 | kubectl apply -f config/testdata/status-defaults 72 | RESULT=$(kubectl get kclrun status-defaults -o go-template={{.status}}) 73 | EXPECTED='map[observedGeneration:-1]' 74 | if [ "${RESULT}" != "${EXPECTED}" ] ; then 75 | echo -e "${RESULT}\n\ndoes not equal\n\n${EXPECTED}" 76 | exit 1 77 | fi 78 | kubectl delete -f config/testdata/status-defaults 79 | - name: Deploy Flux KCL controllers 80 | run: | 81 | make dev-deploy IMG=test/flux-kcl-controller:latest 82 | kubectl -n source-system rollout status deploy/source-controller --timeout=1m 83 | kubectl -n source-system rollout status deploy/kcl-controller --timeout=1m 84 | -------------------------------------------------------------------------------- /internal/controller/kclrun_controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The KCL authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controller 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "testing" 23 | "time" 24 | 25 | "github.com/fluxcd/pkg/apis/meta" 26 | "github.com/fluxcd/pkg/runtime/conditions" 27 | sourcev1 "github.com/fluxcd/source-controller/api/v1" 28 | . "github.com/onsi/gomega" 29 | corev1 "k8s.io/api/core/v1" 30 | "k8s.io/apimachinery/pkg/api/errors" 31 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 | "k8s.io/apimachinery/pkg/types" 33 | 34 | "github.com/kcl-lang/flux-kcl-controller/api/v1alpha1" 35 | ) 36 | 37 | func TestKCLRunReconciler_StagedApply(t *testing.T) { 38 | g := NewWithT(t) 39 | 40 | namespaceName := "flux-kcl-" + randStringRunes(5) 41 | namespace := &corev1.Namespace{ 42 | ObjectMeta: metav1.ObjectMeta{Name: namespaceName}, 43 | } 44 | g.Expect(k8sClient.Create(ctx, namespace)).ToNot(HaveOccurred()) 45 | t.Cleanup(func() { 46 | g.Expect(k8sClient.Delete(ctx, namespace)).NotTo(HaveOccurred()) 47 | }) 48 | 49 | err := createKubeConfigSecret(namespaceName) 50 | g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret") 51 | 52 | artifactName := "val-" + randStringRunes(5) 53 | artifactChecksum, err := testServer.ArtifactFromDir("testdata/crds", artifactName) 54 | g.Expect(err).ToNot(HaveOccurred()) 55 | 56 | repositoryName := types.NamespacedName{ 57 | Name: fmt.Sprintf("val-%s", randStringRunes(5)), 58 | Namespace: namespaceName, 59 | } 60 | 61 | err = applyGitRepository(repositoryName, artifactName, "main/"+artifactChecksum) 62 | g.Expect(err).NotTo(HaveOccurred()) 63 | 64 | obj := &v1alpha1.KCLRun{} 65 | obj.Name = "test-flux-kcl" 66 | obj.Namespace = namespaceName 67 | obj.Spec = v1alpha1.KCLRunSpec{ 68 | Interval: metav1.Duration{Duration: 10 * time.Minute}, 69 | Prune: true, 70 | Path: "./testdata/crds", 71 | SourceRef: v1alpha1.CrossNamespaceSourceReference{ 72 | Name: repositoryName.Name, 73 | Namespace: repositoryName.Namespace, 74 | Kind: sourcev1.GitRepositoryKind, 75 | }, 76 | KubeConfig: &meta.KubeConfigReference{ 77 | SecretRef: meta.SecretKeyReference{ 78 | Name: "kubeconfig", 79 | }, 80 | }, 81 | } 82 | key := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()} 83 | g.Expect(k8sClient.Create(context.Background(), obj)).To(Succeed()) 84 | 85 | g.Eventually(func() bool { 86 | var obj v1alpha1.KCLRun 87 | err := k8sClient.Get(context.Background(), key, &obj) 88 | fmt.Println("event result", err, obj.Status.LastAttemptedRevision, isReconcileSuccess(&obj), conditions.IsReady(&obj)) 89 | return err == nil && isReconcileSuccess(&obj) && obj.Status.LastAttemptedRevision == "main/"+artifactChecksum 90 | }, timeout, time.Second).Should(BeTrue()) 91 | 92 | g.Expect(k8sClient.Delete(context.Background(), obj)).To(Succeed()) 93 | 94 | g.Eventually(func() bool { 95 | var obj v1alpha1.KCLRun 96 | err := k8sClient.Get(context.Background(), key, &obj) 97 | return errors.IsNotFound(err) 98 | }, timeout, time.Second).Should(BeTrue()) 99 | } 100 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 |

Flux KCL controller

2 | 3 |

4 | English | 简体中文 5 |

6 | 7 | 8 | # 介绍 9 | 10 | flux-kcl-controller 是一个组件,用于集成 [KCL](https://github.com/kcl-lang/kcl) 和 [Flux](https://github.com/fluxcd/flux2), 主要用来根据存储在 git/oci 仓库中的 KCL 程序定义的基础设施和工作负载,通过 [source-controller](https://github.com/fluxcd/source-controller) 获取 KCL 程序,实现基础设施和工作负载的持续交付。 11 | 12 | ![kcl-flux](./docs/img/kcl-flux.png) 13 | 14 | # 特性 15 | 16 | - 定期监控存储 KCL 程序的 git/oci 仓库,并根据 git 仓库中的变化,调谐 k8s 集群状态。 17 | 18 | # 快速开始 19 | 20 | # 先决条件 21 | 22 | ## Linux/Mac 用户: 23 | - k3d:用于创建 k8s 集群进行测试,如果您已经有 k8s 集群,可以跳过此步骤。 24 | - Kustomize 25 | - Kubectl 26 | 27 | ## Windows 用户: 28 | **安装 WSL (Windows 子系统 Linux):** 29 | 1. 以管理员身份打开 PowerShell 并运行: 30 | ```powershell 31 | wsl --install 32 | ``` 33 | 2. 重启您的计算机。 34 | 3. 打开 WSL 终端并安装所需工具: 35 | ```bash 36 | sudo apt update 37 | sudo apt install make kubectl git 38 | 39 | # 安装 k3d 40 | curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash 41 | 42 | # 安装 kustomize 43 | curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash 44 | sudo mv kustomize /usr/local/bin/ 45 | ``` 46 | 47 | ### 注意:所有后续命令在所有平台上都是相同的 48 | 49 | ## 创建测试用的 k8s 集群 50 | 51 | 通过如下命令创建集群: 52 | 53 | ```shell 54 | k3d cluster create 55 | ``` 56 | 57 | ## 下载 kcl-controller 并且安装到集群中 58 | 59 | clone 本仓库到本地: 60 | 61 | ```shell 62 | git clone https://github.com/kcl-lang/flux-kcl-controller.git 63 | ``` 64 | 65 | 进入到本仓库的根目录: 66 | 67 | ```shell 68 | cd flux-kcl-controller 69 | ``` 70 | 71 | 将 kcl-controller 安装到集群中: 72 | 73 | ```shell 74 | make deploy 75 | ``` 76 | 77 | ## 监控一个 git 仓库 78 | 79 | 我们以仓库 https://github.com/awesome-kusion/kcl-deployment 为例,该仓库中存储了一个 KCL 程序,该程序定义了一个 Deployment,我们将使用 `flux-kcl-controller` 来部署该程序。 80 | 81 | 通过 `gitrepo.yaml` 文件,定义一个 `GitRepository` 对象,用来监控该仓库: 82 | 83 | ```yaml 84 | apiVersion: source.toolkit.fluxcd.io/v1 85 | kind: GitRepository 86 | metadata: 87 | name: kcl-deployment 88 | namespace: source-system 89 | spec: 90 | interval: 30s # 每隔 30s 检查一次仓库 91 | url: https://github.com/awesome-kusion/kcl-deployment.git 92 | ref: 93 | branch: main # 监控 main 分支 94 | --- 95 | apiVersion: krm.kcl.dev.fluxcd/v1alpha1 96 | kind: KCLRun 97 | metadata: 98 | name: kcl-deployment 99 | namespace: source-system 100 | spec: 101 | sourceRef: 102 | kind: GitRepository 103 | name: kcl-deployment 104 | ``` 105 | 106 | 使用命令 `kubectl apply -f gitrepo.yaml` 将该对象部署到集群中。 107 | 108 | ## 查看部署结果 109 | 110 | 使用命令 `kubectl get deployment` 查看部署结果: 111 | 112 | ```shell 113 | NAME READY UP-TO-DATE AVAILABLE AGE 114 | nginx-deployment 1/1 1 0 28s 115 | ``` 116 | 117 | 可以看到,kcl-controller 根据仓库中的 KCL 程序,创建了一个 Deployment。 118 | 119 | ## 更新仓库中的 KCL 程序 120 | 121 | 我们可以通过修改仓库中的 KCL 程序,来更新集群中的 Deployment。 122 | 123 | 修改仓库中的 KCL 程序,将 nginx 的版本从 `1.7.7` 修改为 `1.7.8`,将`deployment`的名字改为 `nginx-deployment-1`,并且提交到 main 分支。 124 | 125 | 具体变化可以参考: 126 | [nginx:1.7.7 deployment](https://github.com/awesome-kusion/kcl-deployment/commit/dc8b2aa70b1b47bef0637212ea184792b8c43449) -> [nginx:1.7.8 deployment](https://github.com/awesome-kusion/kcl-deployment/commit/f257a71fdff6cb9190f49c1dbf5fa4496d7b3cb2) 127 | 128 | 129 | 使用命令 `kubectl get deployment` 查看部署结果: 130 | 131 | ```shell 132 | NAME READY UP-TO-DATE AVAILABLE AGE 133 | NAME READY UP-TO-DATE AVAILABLE AGE 134 | nginx-deployment 1/1 1 1 20m 135 | nginx-deployment-1 1/1 1 0 4s 136 | ``` 137 | 138 | 可以看到,`flux-kcl-controller` 根据仓库中的 KCL 程序,创建了一个 `nginx-deployment-1` 资源。 139 | -------------------------------------------------------------------------------- /internal/statusreaders/job.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Flux authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package statusreaders 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | batchv1 "k8s.io/api/batch/v1" 24 | corev1 "k8s.io/api/core/v1" 25 | "k8s.io/apimachinery/pkg/api/meta" 26 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 27 | "k8s.io/apimachinery/pkg/runtime/schema" 28 | 29 | "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" 30 | "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" 31 | kstatusreaders "github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders" 32 | "github.com/fluxcd/cli-utils/pkg/kstatus/status" 33 | "github.com/fluxcd/cli-utils/pkg/object" 34 | ) 35 | 36 | type customJobStatusReader struct { 37 | genericStatusReader engine.StatusReader 38 | } 39 | 40 | func NewCustomJobStatusReader(mapper meta.RESTMapper) engine.StatusReader { 41 | genericStatusReader := kstatusreaders.NewGenericStatusReader(mapper, jobConditions) 42 | return &customJobStatusReader{ 43 | genericStatusReader: genericStatusReader, 44 | } 45 | } 46 | 47 | func (j *customJobStatusReader) Supports(gk schema.GroupKind) bool { 48 | return gk == batchv1.SchemeGroupVersion.WithKind("Job").GroupKind() 49 | } 50 | 51 | func (j *customJobStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, resource object.ObjMetadata) (*event.ResourceStatus, error) { 52 | return j.genericStatusReader.ReadStatus(ctx, reader, resource) 53 | } 54 | 55 | func (j *customJobStatusReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, resource *unstructured.Unstructured) (*event.ResourceStatus, error) { 56 | return j.genericStatusReader.ReadStatusForObject(ctx, reader, resource) 57 | } 58 | 59 | // Ref: https://github.com/kubernetes-sigs/cli-utils/blob/v0.29.4/pkg/kstatus/status/core.go 60 | // Modified to return Current status only when the Job has completed as opposed to when it's in progress. 61 | func jobConditions(u *unstructured.Unstructured) (*status.Result, error) { 62 | obj := u.UnstructuredContent() 63 | 64 | parallelism := status.GetIntField(obj, ".spec.parallelism", 1) 65 | completions := status.GetIntField(obj, ".spec.completions", parallelism) 66 | succeeded := status.GetIntField(obj, ".status.succeeded", 0) 67 | failed := status.GetIntField(obj, ".status.failed", 0) 68 | 69 | // Conditions 70 | // https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/utils.go#L24 71 | objc, err := status.GetObjectWithConditions(obj) 72 | if err != nil { 73 | return nil, err 74 | } 75 | for _, c := range objc.Status.Conditions { 76 | switch c.Type { 77 | case "Complete": 78 | if c.Status == corev1.ConditionTrue { 79 | message := fmt.Sprintf("Job Completed. succeeded: %d/%d", succeeded, completions) 80 | return &status.Result{ 81 | Status: status.CurrentStatus, 82 | Message: message, 83 | Conditions: []status.Condition{}, 84 | }, nil 85 | } 86 | case "Failed": 87 | message := fmt.Sprintf("Job Failed. failed: %d/%d", failed, completions) 88 | if c.Status == corev1.ConditionTrue { 89 | return &status.Result{ 90 | Status: status.FailedStatus, 91 | Message: message, 92 | Conditions: []status.Condition{ 93 | { 94 | Type: status.ConditionStalled, 95 | Status: corev1.ConditionTrue, 96 | Reason: "JobFailed", 97 | Message: message, 98 | }, 99 | }, 100 | }, nil 101 | } 102 | } 103 | } 104 | 105 | message := "Job in progress" 106 | return &status.Result{ 107 | Status: status.InProgressStatus, 108 | Message: message, 109 | Conditions: []status.Condition{ 110 | { 111 | Type: status.ConditionReconciling, 112 | Status: corev1.ConditionTrue, 113 | Reason: "JobInProgress", 114 | Message: message, 115 | }, 116 | }, 117 | }, nil 118 | } 119 | -------------------------------------------------------------------------------- /internal/kcl/testdata/crds/crd.k: -------------------------------------------------------------------------------- 1 | """ 2 | This file was generated by the KCL auto-gen tool. DO NOT EDIT. 3 | Editing this file might prove futile when you re-run the KCL auto-gen generate command. 4 | """ 5 | import manifests 6 | 7 | items = [ 8 | { 9 | apiVersion = "apiextensions.k8s.io/v1" 10 | kind = "CustomResourceDefinition" 11 | metadata = { 12 | name = "namespaces.servicebus.azure.com" 13 | } 14 | spec = { 15 | group = "servicebus.azure.com" 16 | names = { 17 | kind = "Namespace" 18 | listKind = "NamespaceList" 19 | plural = "namespaces" 20 | singular = "namespace" 21 | } 22 | scope = "Namespaced" 23 | versions = [ 24 | { 25 | additionalPrinterColumns = [ 26 | { 27 | jsonPath = ".spec.type" 28 | name = "TYPE" 29 | $type = "string" 30 | } 31 | ] 32 | name = "v1beta20210101preview" 33 | $schema = { 34 | openAPIV3Schema = { 35 | description = "Test is the Schema for the testing API" 36 | properties = { 37 | apiVersion = { 38 | $type = "string" 39 | } 40 | kind = { 41 | $type = "string" 42 | } 43 | metadata = { 44 | $type = "object" 45 | } 46 | spec = { 47 | description = "TestSpec defines the desired state of a test run" 48 | properties = { 49 | $type = { 50 | description = "Type of test" 51 | $type = "string" 52 | enum = [ 53 | "unit" 54 | "integration" 55 | ] 56 | } 57 | valuesFrom = { 58 | description = "config reference" 59 | $type = "string" 60 | } 61 | } 62 | $type = "object" 63 | } 64 | status = { 65 | default = { 66 | observedGeneration = -1 67 | } 68 | properties = { 69 | observedGeneration = { 70 | description = "ObservedGeneration is the last observed generation." 71 | format = "int64" 72 | $type = "integer" 73 | } 74 | } 75 | $type = "object" 76 | } 77 | } 78 | $type = "object" 79 | } 80 | } 81 | served = True 82 | storage = True 83 | subresources = { 84 | status = { 85 | } 86 | } 87 | } 88 | ] 89 | } 90 | } 91 | { 92 | apiVersion = "servicebus.azure.com/v1beta20210101preview" 93 | kind = "Namespace" 94 | metadata = { 95 | annotations = { 96 | "serviceoperator.azure.com/reconcile-policy" = "detach-on-delete" 97 | } 98 | name = "sptribs-servicebus-preview" 99 | namespace = "sptribs" 100 | } 101 | spec = { 102 | $type = "integration" 103 | valuesFrom = "test-config" 104 | } 105 | } 106 | { 107 | apiVersion = "v1" 108 | kind = "Namespace" 109 | metadata = { 110 | labels = { 111 | slackChannel = "special-tribunals-builds" 112 | } 113 | name = "sptribs" 114 | } 115 | } 116 | ] 117 | manifests.yaml_stream(items) 118 | -------------------------------------------------------------------------------- /internal/controller/testdata/crds/crd.k: -------------------------------------------------------------------------------- 1 | """ 2 | This file was generated by the KCL auto-gen tool. DO NOT EDIT. 3 | Editing this file might prove futile when you re-run the KCL auto-gen generate command. 4 | """ 5 | import manifests 6 | 7 | items = [ 8 | { 9 | apiVersion = "apiextensions.k8s.io/v1" 10 | kind = "CustomResourceDefinition" 11 | metadata = { 12 | name = "namespaces.servicebus.azure.com" 13 | } 14 | spec = { 15 | group = "servicebus.azure.com" 16 | names = { 17 | kind = "Namespace" 18 | listKind = "NamespaceList" 19 | plural = "namespaces" 20 | singular = "namespace" 21 | } 22 | scope = "Namespaced" 23 | versions = [ 24 | { 25 | additionalPrinterColumns = [ 26 | { 27 | jsonPath = ".spec.type" 28 | name = "TYPE" 29 | $type = "string" 30 | } 31 | ] 32 | name = "v1beta20210101preview" 33 | $schema = { 34 | openAPIV3Schema = { 35 | description = "Test is the Schema for the testing API" 36 | properties = { 37 | apiVersion = { 38 | $type = "string" 39 | } 40 | kind = { 41 | $type = "string" 42 | } 43 | metadata = { 44 | $type = "object" 45 | } 46 | spec = { 47 | description = "TestSpec defines the desired state of a test run" 48 | properties = { 49 | $type = { 50 | description = "Type of test" 51 | $type = "string" 52 | enum = [ 53 | "unit" 54 | "integration" 55 | ] 56 | } 57 | valuesFrom = { 58 | description = "config reference" 59 | $type = "string" 60 | } 61 | } 62 | $type = "object" 63 | } 64 | status = { 65 | default = { 66 | observedGeneration = -1 67 | } 68 | properties = { 69 | observedGeneration = { 70 | description = "ObservedGeneration is the last observed generation." 71 | format = "int64" 72 | $type = "integer" 73 | } 74 | } 75 | $type = "object" 76 | } 77 | } 78 | $type = "object" 79 | } 80 | } 81 | served = True 82 | storage = True 83 | subresources = { 84 | status = { 85 | } 86 | } 87 | } 88 | ] 89 | } 90 | } 91 | { 92 | apiVersion = "servicebus.azure.com/v1beta20210101preview" 93 | kind = "Namespace" 94 | metadata = { 95 | annotations = { 96 | "serviceoperator.azure.com/reconcile-policy" = "detach-on-delete" 97 | } 98 | name = "sptribs-servicebus-preview" 99 | namespace = "sptribs" 100 | } 101 | spec = { 102 | $type = "integration" 103 | valuesFrom = "test-config" 104 | } 105 | } 106 | { 107 | apiVersion = "v1" 108 | kind = "Namespace" 109 | metadata = { 110 | labels = { 111 | slackChannel = "special-tribunals-builds" 112 | } 113 | name = "sptribs" 114 | } 115 | } 116 | ] 117 | manifests.yaml_stream(items) 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Flux KCL controller

2 | 3 |

4 | English | 简体中文 5 |

6 | 7 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fkcl-lang%2Fflux-kcl-controller.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fkcl-lang%2Fflux-kcl-controller?ref=badge_shield) 8 | 9 | # Introduction 10 | 11 | The `flux-kcl-controller` is a component developed for the integration of [KCL](https://github.com/kcl-lang/kcl) and [Flux](https://github.com/fluxcd/flux2), designed to orchestrate continuous delivery pipelines for infrastructure and workloads defined with KCL based on the [source-controller](https://github.com/fluxcd/source-controller), [kustomize-controller](https://github.com/fluxcd/kustomize-controller) and [helm-controller](https://github.com/fluxcd/helm-controller) to acquire the KCL program from repositories. 12 | 13 | ![kcl-flux](./docs/img/kcl-flux.png) 14 | 15 | # Features 16 | 17 | - Periodically monitor git repositories that store KCL programs and reconcile k8s cluster status according to changes in git repositories. 18 | 19 | # Quick Start 20 | 21 | ## Prerequisites 22 | 23 | ### For Linux/Mac Users: 24 | - k3d: used to create a k8s cluster for testing, if you already have a k8s cluster, you can skip ignore this. 25 | - Kustomize 26 | - Kubectl 27 | 28 | ### For Windows Users: 29 | **Install WSL (Windows Subsystem for Linux):** 30 | 1. Open PowerShell as Administrator and run: 31 | ```powershell 32 | wsl --install 33 | 2. Restart your computer. 34 | 3. Open WSL terminal and install required tools: 35 | ``` 36 | sudo apt update 37 | sudo apt install make kubectl git 38 | 39 | # Install k3d 40 | curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash 41 | 42 | # Install kustomize 43 | curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash 44 | sudo mv kustomize /usr/local/bin/ 45 | ``` 46 | ### Note: All subsequent commands are the same for all platforms 47 | 48 | ## Create a test k8s cluster 49 | 50 | Create a cluster using the following command: 51 | 52 | ```shell 53 | k3d cluster create 54 | ``` 55 | 56 | ## Download kcl-controller and install it into the cluster 57 | 58 | Clone this repository to local: 59 | 60 | ```shell 61 | git clone https://github.com/kcl-lang/flux-kcl-controller.git 62 | ``` 63 | 64 | Enter the root directory of this repository: 65 | 66 | ```shell 67 | cd flux-kcl-controller 68 | ``` 69 | 70 | Install kcl-controller into the cluster: 71 | 72 | ```shell 73 | make deploy 74 | ``` 75 | 76 | ## Monitor a git repository 77 | 78 | Take the GitHub repository https://github.com/awesome-kusion/kcl-deployment as an example. This repository stores a KCL program that defines a `Deployment`. We will use `flux-kcl-controller` to deploy this program. 79 | 80 | Define a `GitRepository` object through the `gitrepo.yaml` file to monitor the repository: 81 | 82 | ```yaml 83 | apiVersion: source.toolkit.fluxcd.io/v1 84 | kind: GitRepository 85 | metadata: 86 | name: kcl-deployment 87 | namespace: source-system 88 | spec: 89 | interval: 30s 90 | url: https://github.com/awesome-kusion/kcl-deployment.git 91 | ref: 92 | branch: main 93 | --- 94 | apiVersion: krm.kcl.dev.fluxcd/v1alpha1 95 | kind: KCLRun 96 | metadata: 97 | name: kcl-deployment 98 | namespace: source-system 99 | spec: 100 | sourceRef: 101 | kind: GitRepository 102 | name: kcl-deployment 103 | ``` 104 | 105 | Use the command `kubectl apply -f gitrepo.yaml` to deploy the object to the cluster. 106 | 107 | ## View the deployment result 108 | 109 | Use the command `kubectl get deployment` to view the deployment result: 110 | 111 | ```shell 112 | NAME READY UP-TO-DATE AVAILABLE AGE 113 | nginx-deployment 1/1 1 0 28s 114 | ``` 115 | 116 | The `nginx-deployment` is deployed successfully. 117 | 118 | ## Update the KCL program in the repository 119 | 120 | We can update the `Deployment` in the cluster by modifying the KCL program in the repository. 121 | 122 | Change the version of nginx from `1.7.7` to `1.7.8` and the name of `deployment` to `nginx-deployment-1`, and commit to the main branch. 123 | 124 | The changes can be referred to: [nginx:1.7.7 deployment](https://github.com/awesome-kusion/kcl-deployment/commit/dc8b2aa70b1b47bef0637212ea184792b8c43449) -> [nginx:1.7.8 deployment](https://github.com/awesome-kusion/kcl-deployment/commit/f257a71fdff6cb9190f49c1dbf5fa4496d7b3cb2) 125 | 126 | Use the command `kubectl get deployment` to view the deployment result: 127 | 128 | 129 | ```shell 130 | NAME READY UP-TO-DATE AVAILABLE AGE 131 | nginx-deployment 1/1 1 1 20m 132 | nginx-deployment-1 1/1 1 0 4s 133 | ``` 134 | 135 | `flux-kcl-controller` creates a `nginx-deployment-1` according to the KCL program in the repository. 136 | -------------------------------------------------------------------------------- /internal/inventory/inventory.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Flux authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package inventory 18 | 19 | import ( 20 | "sort" 21 | 22 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | 25 | "github.com/fluxcd/cli-utils/pkg/object" 26 | "github.com/fluxcd/pkg/apis/meta" 27 | "github.com/fluxcd/pkg/ssa" 28 | 29 | "github.com/kcl-lang/flux-kcl-controller/api/v1alpha1" 30 | ) 31 | 32 | func New() *v1alpha1.ResourceInventory { 33 | return &v1alpha1.ResourceInventory{ 34 | Entries: []v1alpha1.ResourceRef{}, 35 | } 36 | } 37 | 38 | // AddChangeSet extracts the metadata from the given objects and adds it to the inventory. 39 | func AddChangeSet(inv *v1alpha1.ResourceInventory, set *ssa.ChangeSet) error { 40 | if set == nil { 41 | return nil 42 | } 43 | 44 | for _, entry := range set.Entries { 45 | inv.Entries = append(inv.Entries, v1alpha1.ResourceRef{ 46 | ID: entry.ObjMetadata.String(), 47 | Version: entry.GroupVersion, 48 | }) 49 | } 50 | 51 | return nil 52 | } 53 | 54 | // List returns the inventory entries as unstructured.Unstructured objects. 55 | func List(inv *v1alpha1.ResourceInventory) ([]*unstructured.Unstructured, error) { 56 | objects := make([]*unstructured.Unstructured, 0) 57 | 58 | if inv.Entries == nil { 59 | return objects, nil 60 | } 61 | 62 | for _, entry := range inv.Entries { 63 | objMetadata, err := object.ParseObjMetadata(entry.ID) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | u := &unstructured.Unstructured{} 69 | u.SetGroupVersionKind(schema.GroupVersionKind{ 70 | Group: objMetadata.GroupKind.Group, 71 | Kind: objMetadata.GroupKind.Kind, 72 | Version: entry.Version, 73 | }) 74 | u.SetName(objMetadata.Name) 75 | u.SetNamespace(objMetadata.Namespace) 76 | objects = append(objects, u) 77 | } 78 | 79 | sort.Sort(ssa.SortableUnstructureds(objects)) 80 | return objects, nil 81 | } 82 | 83 | // ListMetadata returns the inventory entries as object.ObjMetadata objects. 84 | func ListMetadata(inv *v1alpha1.ResourceInventory) (object.ObjMetadataSet, error) { 85 | var metas []object.ObjMetadata 86 | for _, e := range inv.Entries { 87 | m, err := object.ParseObjMetadata(e.ID) 88 | if err != nil { 89 | return metas, err 90 | } 91 | metas = append(metas, m) 92 | } 93 | 94 | return metas, nil 95 | } 96 | 97 | // Diff returns the slice of objects that do not exist in the target inventory. 98 | func Diff(inv *v1alpha1.ResourceInventory, target *v1alpha1.ResourceInventory) ([]*unstructured.Unstructured, error) { 99 | versionOf := func(i *v1alpha1.ResourceInventory, objMetadata object.ObjMetadata) string { 100 | for _, entry := range i.Entries { 101 | if entry.ID == objMetadata.String() { 102 | return entry.Version 103 | } 104 | } 105 | return "" 106 | } 107 | 108 | objects := make([]*unstructured.Unstructured, 0) 109 | aList, err := ListMetadata(inv) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | bList, err := ListMetadata(target) 115 | if err != nil { 116 | return nil, err 117 | } 118 | 119 | list := aList.Diff(bList) 120 | if len(list) == 0 { 121 | return objects, nil 122 | } 123 | 124 | for _, metadata := range list { 125 | u := &unstructured.Unstructured{} 126 | u.SetGroupVersionKind(schema.GroupVersionKind{ 127 | Group: metadata.GroupKind.Group, 128 | Kind: metadata.GroupKind.Kind, 129 | Version: versionOf(inv, metadata), 130 | }) 131 | u.SetName(metadata.Name) 132 | u.SetNamespace(metadata.Namespace) 133 | objects = append(objects, u) 134 | } 135 | 136 | sort.Sort(ssa.SortableUnstructureds(objects)) 137 | return objects, nil 138 | } 139 | 140 | // ReferenceToObjMetadataSet transforms a NamespacedObjectKindReference to an ObjMetadataSet. 141 | func ReferenceToObjMetadataSet(cr []meta.NamespacedObjectKindReference) (object.ObjMetadataSet, error) { 142 | var objects []object.ObjMetadata 143 | 144 | for _, c := range cr { 145 | // For backwards compatibility with Kustomization v1beta1 146 | if c.APIVersion == "" { 147 | c.APIVersion = "apps/v1" 148 | } 149 | 150 | gv, err := schema.ParseGroupVersion(c.APIVersion) 151 | if err != nil { 152 | return objects, err 153 | } 154 | 155 | u := &unstructured.Unstructured{} 156 | u.SetGroupVersionKind(schema.GroupVersionKind{ 157 | Group: gv.Group, 158 | Kind: c.Kind, 159 | Version: gv.Version, 160 | }) 161 | u.SetName(c.Name) 162 | if c.Namespace != "" { 163 | u.SetNamespace(c.Namespace) 164 | } 165 | 166 | objects = append(objects, object.UnstructuredToObjMetadata(u)) 167 | 168 | } 169 | 170 | return objects, nil 171 | } 172 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Flux authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "os" 21 | "time" 22 | 23 | flag "github.com/spf13/pflag" 24 | "k8s.io/apimachinery/pkg/runtime" 25 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 26 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 27 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 28 | ctrl "sigs.k8s.io/controller-runtime" 29 | metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 30 | 31 | "github.com/fluxcd/cli-utils/pkg/kstatus/polling" 32 | "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" 33 | "github.com/fluxcd/pkg/runtime/client" 34 | "github.com/fluxcd/pkg/runtime/events" 35 | "github.com/fluxcd/pkg/runtime/logger" 36 | "github.com/fluxcd/pkg/runtime/metrics" 37 | sourcev1 "github.com/fluxcd/source-controller/api/v1" 38 | sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" 39 | 40 | helper "github.com/fluxcd/pkg/runtime/controller" 41 | krmkcldevfluxcdv1alpha1 "github.com/kcl-lang/flux-kcl-controller/api/v1alpha1" 42 | "github.com/kcl-lang/flux-kcl-controller/internal/controller" 43 | "github.com/kcl-lang/flux-kcl-controller/internal/statusreaders" 44 | // +kubebuilder:scaffold:imports 45 | ) 46 | 47 | const controllerName = "flux-kcl-controller" 48 | 49 | var ( 50 | scheme = runtime.NewScheme() 51 | setupLog = ctrl.Log.WithName("setup") 52 | ) 53 | 54 | func init() { 55 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 56 | // GitRepository 57 | utilruntime.Must(sourcev1.AddToScheme(scheme)) 58 | // OCIRepository 59 | utilruntime.Must(sourcev1beta2.AddToScheme(scheme)) 60 | // KCLRun 61 | utilruntime.Must(krmkcldevfluxcdv1alpha1.AddToScheme(scheme)) 62 | // +kubebuilder:scaffold:scheme 63 | } 64 | 65 | func main() { 66 | var ( 67 | metricsAddr string 68 | eventsAddr string 69 | requeueDependency time.Duration 70 | enableLeaderElection bool 71 | httpRetry int 72 | defaultServiceAccount string 73 | logOptions logger.Options 74 | 75 | clientOptions client.Options 76 | kubeConfigOpts client.KubeConfigOptions 77 | 78 | rateLimiterOptions helper.RateLimiterOptions 79 | watchOptions helper.WatchOptions 80 | disallowedFieldManagers []string 81 | ) 82 | 83 | flag.StringVar(&metricsAddr, "metrics-addr", ":8083", "The address the metric endpoint binds to.") 84 | flag.StringVar(&eventsAddr, "events-addr", "", "The address of the events receiver.") 85 | flag.DurationVar(&requeueDependency, "requeue-dependency", 30*time.Second, "The interval at which failing dependencies are reevaluated.") 86 | flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, 87 | "Enable leader election for controller manager. "+ 88 | "Enabling this will ensure there is only one active controller manager.") 89 | flag.IntVar(&httpRetry, "http-retry", 9, "The maximum number of retries when failing to fetch artifacts over HTTP.") 90 | flag.StringVar(&defaultServiceAccount, "default-service-account", "", 91 | "Default service account used for impersonation.") 92 | flag.StringArrayVar(&disallowedFieldManagers, "override-manager", []string{}, "Field manager disallowed to perform changes on managed resources.") 93 | 94 | clientOptions.BindFlags(flag.CommandLine) 95 | logOptions.BindFlags(flag.CommandLine) 96 | rateLimiterOptions.BindFlags(flag.CommandLine) 97 | kubeConfigOpts.BindFlags(flag.CommandLine) 98 | watchOptions.BindFlags(flag.CommandLine) 99 | 100 | flag.Parse() 101 | ctrl.SetLogger(logger.NewLogger(logOptions)) 102 | ctx := ctrl.SetupSignalHandler() 103 | 104 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 105 | Scheme: scheme, 106 | Metrics: metricsserver.Options{BindAddress: metricsAddr}, 107 | LeaderElection: enableLeaderElection, 108 | LeaderElectionID: "kcl-lang.io", 109 | Logger: ctrl.Log, 110 | }) 111 | if err != nil { 112 | setupLog.Error(err, "unable to start manager") 113 | os.Exit(1) 114 | } 115 | 116 | var eventRecorder *events.Recorder 117 | if eventRecorder, err = events.NewRecorder(mgr, ctrl.Log, eventsAddr, controllerName); err != nil { 118 | setupLog.Error(err, "unable to create event recorder") 119 | os.Exit(1) 120 | } 121 | 122 | jobStatusReader := statusreaders.NewCustomJobStatusReader(mgr.GetRESTMapper()) 123 | pollingOpts := polling.Options{ 124 | CustomStatusReaders: []engine.StatusReader{jobStatusReader}, 125 | } 126 | 127 | if err = (&controller.KCLRunReconciler{ 128 | ControllerName: controllerName, 129 | DefaultServiceAccount: defaultServiceAccount, 130 | Client: mgr.GetClient(), 131 | Metrics: helper.NewMetrics(mgr, metrics.MustMakeRecorder(), krmkcldevfluxcdv1alpha1.KCLRunFinalizer), 132 | EventRecorder: eventRecorder, 133 | GetClusterConfig: ctrl.GetConfig, 134 | ClientOpts: clientOptions, 135 | KubeConfigOpts: kubeConfigOpts, 136 | PollingOpts: pollingOpts, 137 | StatusPoller: polling.NewStatusPoller(mgr.GetClient(), mgr.GetRESTMapper(), pollingOpts), 138 | DisallowedFieldManagers: disallowedFieldManagers, 139 | }).SetupWithManager(ctx, mgr, controller.KCLRunReconcilerOptions{ 140 | DependencyRequeueInterval: requeueDependency, 141 | HTTPRetry: httpRetry, 142 | }); err != nil { 143 | setupLog.Error(err, "unable to create controller", "controller", "KCLRun") 144 | os.Exit(1) 145 | } 146 | // +kubebuilder:scaffold:builder 147 | 148 | setupLog.Info("starting manager") 149 | if err := mgr.Start(ctx); err != nil { 150 | setupLog.Error(err, "problem running manager") 151 | os.Exit(1) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Image URL to use all building/pushing image targets 2 | IMG ?= ghcr.io/kcl-lang/flux-kcl-controller:v0.1.0 3 | # Produce CRDs that work back to Kubernetes 1.16 4 | CRD_OPTIONS ?= crd:crdVersions=v1 5 | SOURCE_VER ?= $(shell go list -m all | grep github.com/fluxcd/source-controller/api | awk '{print $$2}') 6 | 7 | # Use the same version of SOPS already referenced on go.mod 8 | SOPS_VER := $(shell go list -m all | grep github.com/getsops/sops | awk '{print $$2}') 9 | 10 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 11 | ifeq (,$(shell go env GOBIN)) 12 | GOBIN=$(shell go env GOPATH)/bin 13 | else 14 | GOBIN=$(shell go env GOBIN) 15 | endif 16 | 17 | # Allows for defining additional Go test args, e.g. '-tags integration'. 18 | GO_TEST_ARGS ?= 19 | # Allows for defining additional Docker buildx arguments, e.g. '--push'. 20 | BUILD_ARGS ?= 21 | # Architectures to build images for. 22 | BUILD_PLATFORMS ?= linux/amd64 23 | # Architecture to use envtest with 24 | ENVTEST_ARCH ?= amd64 25 | # Paths to download the CRD dependencies at. 26 | GITREPO_CRD ?= config/crd/bases/gitrepositories.yaml 27 | BUCKET_CRD ?= config/crd/bases/buckets.yaml 28 | OCIREPO_CRD ?= config/crd/bases/ocirepositories.yaml 29 | 30 | # Keep a record of the version of the downloaded source CRDs. It is used to 31 | # detect and download new CRDs when the SOURCE_VER changes. 32 | SOURCE_CRD_VER=bin/.src-crd-$(SOURCE_VER) 33 | 34 | PACKAGE ?= github.com/kcl-lang/flux-kcl-controller 35 | INPUT_DIRS := $(PACKAGE)/api/v1alpha1 36 | BOILERPLATE_PATH := ${PWD}/hack/boilerplate.go.txt 37 | 38 | # API (doc) generation utilities 39 | GEN_API_REF_DOCS_VERSION ?= e327d0730470cbd61b06300f81c5fcf91c23c113 40 | 41 | all: manager 42 | 43 | # Run tests 44 | KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)" 45 | test: generate tidy fmt vet manifests download-crd-deps install-envtest $(SOPS) 46 | KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) go test ./... -coverprofile cover.out 47 | 48 | # Build manager binary 49 | manager: generate fmt vet 50 | go build -o bin/manager main.go 51 | 52 | # Run against the configured Kubernetes cluster in ~/.kube/config 53 | run: generate fmt vet manifests 54 | go run ./cmd/main.go 55 | 56 | # Delete previously downloaded CRDs and record the new version of the source 57 | # CRDs. 58 | $(SOURCE_CRD_VER): 59 | rm -f bin/.src-crd* 60 | $(MAKE) cleanup-crd-deps 61 | if ! test -d "bin"; then mkdir -p bin; fi 62 | touch $(SOURCE_CRD_VER) 63 | 64 | # Download the CRDs the controller depends on FluxCD GitRepo, OCIRepo and Bucket. 65 | download-crd-deps: $(SOURCE_CRD_VER) 66 | curl -s https://raw.githubusercontent.com/fluxcd/source-controller/${SOURCE_VER}/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml -o $(GITREPO_CRD) 67 | curl -s https://raw.githubusercontent.com/fluxcd/source-controller/${SOURCE_VER}/config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml -o $(BUCKET_CRD) 68 | curl -s https://raw.githubusercontent.com/fluxcd/source-controller/${SOURCE_VER}/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml -o $(OCIREPO_CRD) 69 | 70 | # Delete the downloaded CRD dependencies. 71 | cleanup-crd-deps: 72 | rm -f $(GITREPO_CRD) $(BUCKET_CRD) $(OCIREPO_CRD) 73 | 74 | # Install CRDs into a cluster 75 | install: manifests 76 | kustomize build config/crd | kubectl apply -f - 77 | 78 | # Uninstall CRDs from a cluster 79 | uninstall: manifests 80 | kustomize build config/crd | kubectl delete -f - 81 | 82 | # Deploy controller in the configured Kubernetes cluster in ~/.kube/config 83 | deploy: manifests 84 | cd config/manager && kustomize edit set image kcl-controller=${IMG} 85 | kustomize build config/default | kubectl apply -f - 86 | 87 | # Deploy controller dev image in the configured Kubernetes cluster in ~/.kube/config 88 | dev-deploy: manifests 89 | mkdir -p config/dev && cp config/default/* config/dev 90 | cd config/dev && kustomize edit set image fluxcd/kustomize-controller=${IMG} 91 | kustomize build config/dev | kubectl apply -f - 92 | rm -rf config/dev 93 | 94 | # Undeploy controller in the configured Kubernetes cluster in ~/.kube/config 95 | undeploy: 96 | cd config/manager && kustomize edit set image kcl-controller=${IMG} 97 | kustomize build config/default | kubectl delete -f - 98 | 99 | # Generate manifests e.g. CRD, RBAC etc. 100 | manifests: controller-gen deepcopy-gen 101 | $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=source-reader webhook paths="./..." output:crd:artifacts:config=config/crd/bases 102 | $(DEEPCOPY_GEN) --input-dirs=$(INPUT_DIRS) --output-file-base=zz_generated.deepcopy --go-header-file=${BOILERPLATE_PATH} 103 | 104 | # Run go tidy to cleanup go.mod 105 | tidy: 106 | rm -f go.sum; go mod tidy 107 | 108 | # Run go fmt against code 109 | fmt: 110 | go fmt ./... 111 | 112 | # Run go vet against code 113 | vet: 114 | go vet ./... 115 | 116 | # Run go build 117 | build: 118 | go build ./... 119 | 120 | # Generate code 121 | generate: controller-gen deepcopy-gen 122 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 123 | $(DEEPCOPY_GEN) --input-dirs=$(INPUT_DIRS) --output-file-base=zz_generated.deepcopy --go-header-file=${BOILERPLATE_PATH} 124 | 125 | # Build the docker image 126 | docker-build: 127 | docker buildx build \ 128 | --platform=$(BUILD_PLATFORMS) \ 129 | -t ${IMG} \ 130 | --load \ 131 | ${BUILD_ARGS} . 132 | 133 | # Push the docker image 134 | docker-push: 135 | docker push ${IMG} 136 | 137 | manifests-release: 138 | mkdir -p ./build 139 | mkdir -p config/release-tmp && cp config/release/* config/release-tmp 140 | cd config/release-tmp && kustomize edit set image kcl-lang/kcl-controller=${IMG} 141 | kustomize build config/release-tmp > ./build/kcl-controller.deployment.yaml 142 | rm -rf config/release-tmp 143 | 144 | # Find or download controller-gen 145 | CONTROLLER_GEN = $(shell pwd)/bin/controller-gen 146 | CONTROLLER_GEN_VERSION ?= v0.16.1 147 | .PHONY: controller-gen 148 | controller-gen: ## Download controller-gen locally if necessary. 149 | $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION)) 150 | 151 | DEEPCOPY_GEN = $(shell pwd)/bin/deepcopy-gen 152 | DEEPCOPY_GEN_VERSION ?= v0.29.0 153 | .PHONY: deepcopy-gen 154 | deepcopy-gen: ## Download controller-gen locally if necessary. 155 | $(call go-install-tool,$(DEEPCOPY_GEN),k8s.io/code-generator/cmd/deepcopy-gen@$(DEEPCOPY_GEN_VERSION)) 156 | 157 | ENVTEST_ASSETS_DIR=$(shell pwd)/testbin 158 | ENVTEST_KUBERNETES_VERSION?=latest 159 | install-envtest: setup-envtest 160 | mkdir -p ${ENVTEST_ASSETS_DIR} 161 | $(ENVTEST) use $(ENVTEST_KUBERNETES_VERSION) --arch=$(ENVTEST_ARCH) --bin-dir=$(ENVTEST_ASSETS_DIR) 162 | 163 | SOPS = $(GOBIN)/sops 164 | $(SOPS): ## Download latest sops binary if none is found. 165 | $(call go-install-tool,$(SOPS),github.com/getsops/sops/v3/cmd/sops@$(SOPS_VER)) 166 | 167 | ENVTEST = $(shell pwd)/bin/setup-envtest 168 | .PHONY: envtest 169 | setup-envtest: ## Download envtest-setup locally if necessary. 170 | $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) 171 | 172 | # go-install-tool will 'go install' any package $2 and install it to $1. 173 | PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) 174 | define go-install-tool 175 | @[ -f $(1) ] || { \ 176 | set -e ;\ 177 | TMP_DIR=$$(mktemp -d) ;\ 178 | cd $$TMP_DIR ;\ 179 | go mod init tmp ;\ 180 | echo "Downloading $(2)" ;\ 181 | GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\ 182 | rm -rf $$TMP_DIR ;\ 183 | } 184 | endef 185 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | /* 4 | Copyright 2024 The KCL authors 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // Code generated by controller-gen. DO NOT EDIT. 20 | 21 | package v1alpha1 22 | 23 | import ( 24 | "github.com/fluxcd/pkg/apis/meta" 25 | "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | ) 28 | 29 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 30 | func (in *ArgumentReference) DeepCopyInto(out *ArgumentReference) { 31 | *out = *in 32 | } 33 | 34 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgumentReference. 35 | func (in *ArgumentReference) DeepCopy() *ArgumentReference { 36 | if in == nil { 37 | return nil 38 | } 39 | out := new(ArgumentReference) 40 | in.DeepCopyInto(out) 41 | return out 42 | } 43 | 44 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 45 | func (in *CommonMetadata) DeepCopyInto(out *CommonMetadata) { 46 | *out = *in 47 | if in.Annotations != nil { 48 | in, out := &in.Annotations, &out.Annotations 49 | *out = make(map[string]string, len(*in)) 50 | for key, val := range *in { 51 | (*out)[key] = val 52 | } 53 | } 54 | if in.Labels != nil { 55 | in, out := &in.Labels, &out.Labels 56 | *out = make(map[string]string, len(*in)) 57 | for key, val := range *in { 58 | (*out)[key] = val 59 | } 60 | } 61 | } 62 | 63 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommonMetadata. 64 | func (in *CommonMetadata) DeepCopy() *CommonMetadata { 65 | if in == nil { 66 | return nil 67 | } 68 | out := new(CommonMetadata) 69 | in.DeepCopyInto(out) 70 | return out 71 | } 72 | 73 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 74 | func (in *ConfigSpec) DeepCopyInto(out *ConfigSpec) { 75 | *out = *in 76 | if in.Arguments != nil { 77 | in, out := &in.Arguments, &out.Arguments 78 | *out = make([]string, len(*in)) 79 | copy(*out, *in) 80 | } 81 | if in.Settings != nil { 82 | in, out := &in.Settings, &out.Settings 83 | *out = make([]string, len(*in)) 84 | copy(*out, *in) 85 | } 86 | if in.Overrides != nil { 87 | in, out := &in.Overrides, &out.Overrides 88 | *out = make([]string, len(*in)) 89 | copy(*out, *in) 90 | } 91 | if in.PathSelectors != nil { 92 | in, out := &in.PathSelectors, &out.PathSelectors 93 | *out = make([]string, len(*in)) 94 | copy(*out, *in) 95 | } 96 | } 97 | 98 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigSpec. 99 | func (in *ConfigSpec) DeepCopy() *ConfigSpec { 100 | if in == nil { 101 | return nil 102 | } 103 | out := new(ConfigSpec) 104 | in.DeepCopyInto(out) 105 | return out 106 | } 107 | 108 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 109 | func (in *CrossNamespaceSourceReference) DeepCopyInto(out *CrossNamespaceSourceReference) { 110 | *out = *in 111 | } 112 | 113 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossNamespaceSourceReference. 114 | func (in *CrossNamespaceSourceReference) DeepCopy() *CrossNamespaceSourceReference { 115 | if in == nil { 116 | return nil 117 | } 118 | out := new(CrossNamespaceSourceReference) 119 | in.DeepCopyInto(out) 120 | return out 121 | } 122 | 123 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 124 | func (in *KCLRun) DeepCopyInto(out *KCLRun) { 125 | *out = *in 126 | out.TypeMeta = in.TypeMeta 127 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 128 | in.Spec.DeepCopyInto(&out.Spec) 129 | in.Status.DeepCopyInto(&out.Status) 130 | } 131 | 132 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KCLRun. 133 | func (in *KCLRun) DeepCopy() *KCLRun { 134 | if in == nil { 135 | return nil 136 | } 137 | out := new(KCLRun) 138 | in.DeepCopyInto(out) 139 | return out 140 | } 141 | 142 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 143 | func (in *KCLRun) DeepCopyObject() runtime.Object { 144 | if c := in.DeepCopy(); c != nil { 145 | return c 146 | } 147 | return nil 148 | } 149 | 150 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 151 | func (in *KCLRunList) DeepCopyInto(out *KCLRunList) { 152 | *out = *in 153 | out.TypeMeta = in.TypeMeta 154 | in.ListMeta.DeepCopyInto(&out.ListMeta) 155 | if in.Items != nil { 156 | in, out := &in.Items, &out.Items 157 | *out = make([]KCLRun, len(*in)) 158 | for i := range *in { 159 | (*in)[i].DeepCopyInto(&(*out)[i]) 160 | } 161 | } 162 | } 163 | 164 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KCLRunList. 165 | func (in *KCLRunList) DeepCopy() *KCLRunList { 166 | if in == nil { 167 | return nil 168 | } 169 | out := new(KCLRunList) 170 | in.DeepCopyInto(out) 171 | return out 172 | } 173 | 174 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 175 | func (in *KCLRunList) DeepCopyObject() runtime.Object { 176 | if c := in.DeepCopy(); c != nil { 177 | return c 178 | } 179 | return nil 180 | } 181 | 182 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 183 | func (in *KCLRunSpec) DeepCopyInto(out *KCLRunSpec) { 184 | *out = *in 185 | if in.CommonMetadata != nil { 186 | in, out := &in.CommonMetadata, &out.CommonMetadata 187 | *out = new(CommonMetadata) 188 | (*in).DeepCopyInto(*out) 189 | } 190 | if in.DependsOn != nil { 191 | in, out := &in.DependsOn, &out.DependsOn 192 | *out = make([]meta.NamespacedObjectReference, len(*in)) 193 | copy(*out, *in) 194 | } 195 | if in.Timeout != nil { 196 | in, out := &in.Timeout, &out.Timeout 197 | *out = new(v1.Duration) 198 | **out = **in 199 | } 200 | if in.PersistentClient != nil { 201 | in, out := &in.PersistentClient, &out.PersistentClient 202 | *out = new(bool) 203 | **out = **in 204 | } 205 | if in.KubeConfig != nil { 206 | in, out := &in.KubeConfig, &out.KubeConfig 207 | *out = new(meta.KubeConfigReference) 208 | **out = **in 209 | } 210 | out.Interval = in.Interval 211 | if in.RetryInterval != nil { 212 | in, out := &in.RetryInterval, &out.RetryInterval 213 | *out = new(v1.Duration) 214 | **out = **in 215 | } 216 | if in.Config != nil { 217 | in, out := &in.Config, &out.Config 218 | *out = new(ConfigSpec) 219 | (*in).DeepCopyInto(*out) 220 | } 221 | if in.ArgumentsReferences != nil { 222 | in, out := &in.ArgumentsReferences, &out.ArgumentsReferences 223 | *out = make([]ArgumentReference, len(*in)) 224 | copy(*out, *in) 225 | } 226 | if in.HealthChecks != nil { 227 | in, out := &in.HealthChecks, &out.HealthChecks 228 | *out = make([]meta.NamespacedObjectKindReference, len(*in)) 229 | copy(*out, *in) 230 | } 231 | out.SourceRef = in.SourceRef 232 | } 233 | 234 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KCLRunSpec. 235 | func (in *KCLRunSpec) DeepCopy() *KCLRunSpec { 236 | if in == nil { 237 | return nil 238 | } 239 | out := new(KCLRunSpec) 240 | in.DeepCopyInto(out) 241 | return out 242 | } 243 | 244 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 245 | func (in *KCLRunStatus) DeepCopyInto(out *KCLRunStatus) { 246 | *out = *in 247 | out.ReconcileRequestStatus = in.ReconcileRequestStatus 248 | if in.Conditions != nil { 249 | in, out := &in.Conditions, &out.Conditions 250 | *out = make([]v1.Condition, len(*in)) 251 | for i := range *in { 252 | (*in)[i].DeepCopyInto(&(*out)[i]) 253 | } 254 | } 255 | if in.Inventory != nil { 256 | in, out := &in.Inventory, &out.Inventory 257 | *out = new(ResourceInventory) 258 | (*in).DeepCopyInto(*out) 259 | } 260 | } 261 | 262 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KCLRunStatus. 263 | func (in *KCLRunStatus) DeepCopy() *KCLRunStatus { 264 | if in == nil { 265 | return nil 266 | } 267 | out := new(KCLRunStatus) 268 | in.DeepCopyInto(out) 269 | return out 270 | } 271 | 272 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 273 | func (in *ResourceInventory) DeepCopyInto(out *ResourceInventory) { 274 | *out = *in 275 | if in.Entries != nil { 276 | in, out := &in.Entries, &out.Entries 277 | *out = make([]ResourceRef, len(*in)) 278 | copy(*out, *in) 279 | } 280 | } 281 | 282 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceInventory. 283 | func (in *ResourceInventory) DeepCopy() *ResourceInventory { 284 | if in == nil { 285 | return nil 286 | } 287 | out := new(ResourceInventory) 288 | in.DeepCopyInto(out) 289 | return out 290 | } 291 | 292 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 293 | func (in *ResourceRef) DeepCopyInto(out *ResourceRef) { 294 | *out = *in 295 | } 296 | 297 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceRef. 298 | func (in *ResourceRef) DeepCopy() *ResourceRef { 299 | if in == nil { 300 | return nil 301 | } 302 | out := new(ResourceRef) 303 | in.DeepCopyInto(out) 304 | return out 305 | } 306 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /internal/inventory/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kcl-lang/flux-kcl-controller 2 | 3 | go 1.23.0 4 | 5 | // Replace digest lib to master to gather access to BLAKE3. 6 | // xref: https://github.com/opencontainers/go-digest/pull/66 7 | replace github.com/opencontainers/go-digest => github.com/opencontainers/go-digest v1.0.1-0.20220411205349-bde1400a84be 8 | 9 | require ( 10 | github.com/fluxcd/cli-utils v0.36.0-flux.9 11 | github.com/fluxcd/pkg/apis/acl v0.3.0 12 | github.com/fluxcd/pkg/apis/event v0.10.1 13 | github.com/fluxcd/pkg/http/fetch v0.12.1 14 | github.com/fluxcd/pkg/runtime v0.49.1 15 | github.com/fluxcd/pkg/ssa v0.41.1 16 | github.com/fluxcd/pkg/tar v0.8.1 17 | github.com/fluxcd/pkg/testserver v0.7.0 18 | github.com/fluxcd/source-controller/api v1.4.1 19 | github.com/hashicorp/vault/api v1.21.0 20 | github.com/onsi/gomega v1.38.2 21 | github.com/ory/dockertest v3.3.5+incompatible 22 | github.com/spf13/pflag v1.0.10 23 | github.com/stretchr/testify v1.11.1 24 | k8s.io/apimachinery v0.31.1 25 | k8s.io/client-go v0.31.1 26 | kcl-lang.io/kcl-go v0.11.3 27 | kcl-lang.io/kpm v0.11.3 28 | sigs.k8s.io/controller-runtime v0.19.0 29 | ) 30 | 31 | require ( 32 | cloud.google.com/go v0.115.0 // indirect 33 | cloud.google.com/go/auth v0.6.0 // indirect 34 | cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect 35 | cloud.google.com/go/compute/metadata v0.6.0 // indirect 36 | cloud.google.com/go/iam v1.1.8 // indirect 37 | cloud.google.com/go/storage v1.42.0 // indirect 38 | dario.cat/mergo v1.0.1 // indirect 39 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect 40 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect 41 | github.com/BurntSushi/toml v1.5.0 // indirect 42 | github.com/MakeNowJust/heredoc v1.0.0 // indirect 43 | github.com/Microsoft/go-winio v0.6.2 // indirect 44 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect 45 | github.com/ProtonMail/go-crypto v1.1.6 // indirect 46 | github.com/aws/aws-sdk-go v1.48.10 // indirect 47 | github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect 48 | github.com/blang/semver/v4 v4.0.0 // indirect 49 | github.com/cenkalti/backoff v2.2.1+incompatible // indirect 50 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 51 | github.com/chai2010/gettext-go v1.0.2 // indirect 52 | github.com/chai2010/jsonv v1.1.3 // indirect 53 | github.com/chai2010/protorpc v1.1.4 // indirect 54 | github.com/chainguard-dev/git-urls v1.0.2 // indirect 55 | github.com/cloudflare/circl v1.6.1 // indirect 56 | github.com/containerd/containerd v1.7.20 // indirect 57 | github.com/containerd/continuity v0.4.3 // indirect 58 | github.com/containerd/errdefs v0.3.0 // indirect 59 | github.com/containerd/log v0.1.0 // indirect 60 | github.com/containerd/platforms v0.2.1 // indirect 61 | github.com/containers/image/v5 v5.34.3 // indirect 62 | github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect 63 | github.com/containers/ocicrypt v1.2.1 // indirect 64 | github.com/containers/storage v1.57.2 // indirect 65 | github.com/dchest/siphash v1.2.3 // indirect 66 | github.com/distribution/reference v0.6.0 // indirect 67 | github.com/docker/cli v27.5.1+incompatible // indirect 68 | github.com/docker/distribution v2.8.3+incompatible // indirect 69 | github.com/docker/docker v27.5.1+incompatible // indirect 70 | github.com/docker/docker-credential-helpers v0.8.2 // indirect 71 | github.com/docker/go-connections v0.5.0 // indirect 72 | github.com/docker/go-metrics v0.0.1 // indirect 73 | github.com/docker/go-units v0.5.0 // indirect 74 | github.com/dominikbraun/graph v0.23.0 // indirect 75 | github.com/ebitengine/purego v0.7.1 // indirect 76 | github.com/elliotchance/orderedmap/v2 v2.7.0 // indirect 77 | github.com/emirpasic/gods v1.18.1 // indirect 78 | github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect 79 | github.com/felixge/httpsnoop v1.0.4 // indirect 80 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 81 | github.com/go-errors/errors v1.5.1 // indirect 82 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 83 | github.com/go-git/go-billy/v5 v5.6.2 // indirect 84 | github.com/go-git/go-git/v5 v5.16.2 // indirect 85 | github.com/go-jose/go-jose/v4 v4.1.1 // indirect 86 | github.com/go-logr/stdr v1.2.2 // indirect 87 | github.com/goccy/go-yaml v1.18.0 // indirect 88 | github.com/gofrs/flock v0.12.1 // indirect 89 | github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 // indirect 90 | github.com/golang/snappy v0.0.4 // indirect 91 | github.com/google/btree v1.1.2 // indirect 92 | github.com/google/s2a-go v0.1.7 // indirect 93 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 94 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect 95 | github.com/googleapis/gax-go/v2 v2.12.5 // indirect 96 | github.com/gorilla/mux v1.8.1 // indirect 97 | github.com/gorilla/websocket v1.5.0 // indirect 98 | github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect 99 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect 100 | github.com/hashicorp/errwrap v1.1.0 // indirect 101 | github.com/hashicorp/go-getter v1.7.8 // indirect 102 | github.com/hashicorp/go-multierror v1.1.1 // indirect 103 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 104 | github.com/hashicorp/go-safetemp v1.0.0 // indirect 105 | github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect 106 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect 107 | github.com/hashicorp/go-sockaddr v1.0.7 // indirect 108 | github.com/hashicorp/go-version v1.7.0 // indirect 109 | github.com/hashicorp/hcl v1.0.1-vault-7 // indirect 110 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 111 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 112 | github.com/jinzhu/copier v0.4.0 // indirect 113 | github.com/jmespath/go-jmespath v0.4.0 // indirect 114 | github.com/kevinburke/ssh_config v1.2.0 // indirect 115 | github.com/klauspost/compress v1.17.11 // indirect 116 | github.com/kubescape/go-git-url v0.0.30 // indirect 117 | github.com/kylelemons/godebug v1.1.0 // indirect 118 | github.com/lib/pq v1.10.9 // indirect 119 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect 120 | github.com/mitchellh/go-homedir v1.1.0 // indirect 121 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 122 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 123 | github.com/mitchellh/mapstructure v1.5.0 // indirect 124 | github.com/moby/locker v1.0.1 // indirect 125 | github.com/moby/spdystream v0.4.0 // indirect 126 | github.com/moby/sys/capability v0.4.0 // indirect 127 | github.com/moby/sys/mountinfo v0.7.2 // indirect 128 | github.com/moby/sys/user v0.3.0 // indirect 129 | github.com/moby/term v0.5.2 // indirect 130 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 131 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect 132 | github.com/opencontainers/image-spec v1.1.1 // indirect 133 | github.com/opencontainers/runc v1.1.12 // indirect 134 | github.com/opencontainers/runtime-spec v1.2.0 // indirect 135 | github.com/otiai10/copy v1.14.1 // indirect 136 | github.com/otiai10/mint v1.6.3 // indirect 137 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect 138 | github.com/pjbgf/sha1cd v0.3.2 // indirect 139 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 140 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 141 | github.com/ryanuber/go-glob v1.0.0 // indirect 142 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect 143 | github.com/sirupsen/logrus v1.9.3 // indirect 144 | github.com/skeema/knownhosts v1.3.1 // indirect 145 | github.com/spf13/cobra v1.8.1 // indirect 146 | github.com/thoas/go-funk v0.9.3 // indirect 147 | github.com/ulikunitz/xz v0.5.12 // indirect 148 | github.com/x448/float16 v0.8.4 // indirect 149 | github.com/xanzy/ssh-agent v0.3.3 // indirect 150 | github.com/xlab/treeprint v1.2.0 // indirect 151 | go.opencensus.io v0.24.0 // indirect 152 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 153 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect 154 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect 155 | go.opentelemetry.io/otel v1.35.0 // indirect 156 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 157 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 158 | go.starlark.net v0.0.0-20231121155337-90ade8b19d09 // indirect 159 | go.yaml.in/yaml/v3 v3.0.4 // indirect 160 | golang.org/x/crypto v0.41.0 // indirect 161 | golang.org/x/mod v0.26.0 // indirect 162 | golang.org/x/sync v0.16.0 // indirect 163 | google.golang.org/api v0.186.0 // indirect 164 | google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect 165 | google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect 166 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect 167 | google.golang.org/grpc v1.73.0 // indirect 168 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 169 | gopkg.in/warnings.v0 v0.1.2 // indirect 170 | k8s.io/cli-runtime v0.31.1 // indirect 171 | k8s.io/kubectl v0.31.1 // indirect 172 | kcl-lang.io/lib v0.11.2 // indirect 173 | oras.land/oras-go v1.2.6 // indirect 174 | oras.land/oras-go/v2 v2.5.0 // indirect 175 | sigs.k8s.io/kustomize/api v0.17.3 // indirect 176 | sigs.k8s.io/kustomize/kyaml v0.17.2 // indirect 177 | ) 178 | 179 | require ( 180 | github.com/beorn7/perks v1.0.1 // indirect 181 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 182 | github.com/cyphar/filepath-securejoin v0.5.0 183 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 184 | github.com/emicklei/go-restful/v3 v3.12.0 // indirect 185 | github.com/evanphx/json-patch/v5 v5.9.0 // indirect 186 | github.com/fluxcd/pkg/apis/meta v1.6.1 187 | github.com/fluxcd/source-watcher v1.1.0 188 | github.com/fsnotify/fsnotify v1.7.0 // indirect 189 | github.com/go-logr/logr v1.4.3 // indirect 190 | github.com/go-logr/zapr v1.3.0 // indirect 191 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 192 | github.com/go-openapi/jsonreference v0.21.0 // indirect 193 | github.com/go-openapi/swag v0.23.0 // indirect 194 | github.com/gogo/protobuf v1.3.2 // indirect 195 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect 196 | github.com/golang/protobuf v1.5.4 // indirect 197 | github.com/google/gnostic-models v0.6.8 // indirect 198 | github.com/google/go-cmp v0.7.0 // indirect 199 | github.com/google/gofuzz v1.2.0 // indirect 200 | github.com/google/uuid v1.6.0 // indirect 201 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 202 | github.com/hashicorp/go-retryablehttp v0.7.8 // indirect 203 | github.com/imdario/mergo v0.3.16 // indirect 204 | github.com/josharian/intern v1.0.0 // indirect 205 | github.com/json-iterator/go v1.1.12 // indirect 206 | github.com/klauspost/cpuid/v2 v2.2.8 // indirect 207 | github.com/mailru/easyjson v0.7.7 // indirect 208 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 209 | github.com/modern-go/reflect2 v1.0.2 // indirect 210 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 211 | github.com/opencontainers/go-digest v1.0.0 212 | github.com/opencontainers/go-digest/blake3 v0.0.0-20231025023718-d50d2fec9c98 // indirect 213 | github.com/pkg/errors v0.9.1 // indirect 214 | github.com/prometheus/client_golang v1.20.5 // indirect 215 | github.com/prometheus/client_model v0.6.1 // indirect 216 | github.com/prometheus/common v0.57.0 // indirect 217 | github.com/prometheus/procfs v0.15.1 // indirect 218 | github.com/zeebo/blake3 v0.2.3 // indirect 219 | go.uber.org/multierr v1.11.0 // indirect 220 | go.uber.org/zap v1.27.0 // indirect 221 | golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 222 | golang.org/x/net v0.43.0 // indirect 223 | golang.org/x/oauth2 v0.28.0 // indirect 224 | golang.org/x/sys v0.35.0 // indirect 225 | golang.org/x/term v0.34.0 // indirect 226 | golang.org/x/text v0.28.0 // indirect 227 | golang.org/x/time v0.12.0 // indirect 228 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 229 | google.golang.org/protobuf v1.36.7 // indirect 230 | gopkg.in/inf.v0 v0.9.1 // indirect 231 | gopkg.in/yaml.v2 v2.4.0 232 | gopkg.in/yaml.v3 v3.0.1 // indirect 233 | k8s.io/api v0.31.1 234 | k8s.io/apiextensions-apiserver v0.31.1 // indirect 235 | k8s.io/component-base v0.31.1 // indirect 236 | k8s.io/klog/v2 v2.130.1 // indirect 237 | k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 // indirect 238 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect 239 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 240 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 241 | sigs.k8s.io/yaml v1.4.0 // indirect 242 | ) 243 | -------------------------------------------------------------------------------- /internal/controller/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Flux authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controller 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "os" 23 | "path/filepath" 24 | "testing" 25 | "time" 26 | 27 | "github.com/fluxcd/pkg/apis/meta" 28 | "github.com/fluxcd/pkg/runtime/conditions" 29 | kcheck "github.com/fluxcd/pkg/runtime/conditions/check" 30 | "github.com/fluxcd/pkg/runtime/controller" 31 | "github.com/fluxcd/pkg/runtime/metrics" 32 | "github.com/fluxcd/pkg/runtime/testenv" 33 | "github.com/fluxcd/pkg/testserver" 34 | "github.com/hashicorp/vault/api" 35 | "github.com/opencontainers/go-digest" 36 | "github.com/ory/dockertest" 37 | "golang.org/x/exp/rand" 38 | "gopkg.in/yaml.v2" 39 | corev1 "k8s.io/api/core/v1" 40 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 41 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 42 | "k8s.io/client-go/kubernetes/scheme" 43 | controllerLog "sigs.k8s.io/controller-runtime/pkg/log" 44 | 45 | ctrl "sigs.k8s.io/controller-runtime" 46 | "sigs.k8s.io/controller-runtime/pkg/client" 47 | "sigs.k8s.io/controller-runtime/pkg/envtest" 48 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 49 | 50 | sourcev1 "github.com/fluxcd/source-controller/api/v1" 51 | sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" 52 | "github.com/kcl-lang/flux-kcl-controller/api/v1alpha1" 53 | ) 54 | 55 | const ( 56 | timeout = time.Second * 30 57 | interval = time.Second * 1 58 | reconciliationInterval = time.Second * 5 59 | vaultVersion = "1.13.2" 60 | overrideManagerName = "node-fetch" 61 | ) 62 | 63 | var ( 64 | reconciler *KCLRunReconciler 65 | k8sClient client.Client 66 | testEnv *testenv.Environment 67 | testServer *testserver.ArtifactServer 68 | testMetricsH controller.Metrics 69 | ctx = ctrl.SetupSignalHandler() 70 | kubeConfig []byte 71 | kstatusCheck *kcheck.Checker 72 | kstatusInProgressCheck *kcheck.Checker 73 | debugMode = os.Getenv("DEBUG_TEST") != "" 74 | ) 75 | 76 | func runInContext(registerControllers func(*testenv.Environment), run func() int) (code int) { 77 | var err error 78 | // GitRepository 79 | utilruntime.Must(sourcev1.AddToScheme(scheme.Scheme)) 80 | // OCIRepository 81 | utilruntime.Must(sourcev1beta2.AddToScheme(scheme.Scheme)) 82 | // KCLRun 83 | utilruntime.Must(v1alpha1.AddToScheme(scheme.Scheme)) 84 | 85 | if debugMode { 86 | controllerLog.SetLogger(zap.New(zap.WriteTo(os.Stderr), zap.UseDevMode(false))) 87 | } 88 | 89 | testEnv = testenv.New( 90 | testenv.WithCRDPath(filepath.Join("..", "..", "config", "crd", "bases")), 91 | testenv.WithMaxConcurrentReconciles(4), 92 | ) 93 | 94 | testServer, err = testserver.NewTempArtifactServer() 95 | if err != nil { 96 | panic(fmt.Sprintf("Failed to create a temporary storage server: %v", err)) 97 | } 98 | fmt.Println("Starting the test storage server") 99 | testServer.Start() 100 | 101 | registerControllers(testEnv) 102 | 103 | go func() { 104 | fmt.Println("Starting the test environment") 105 | if err := testEnv.Start(ctx); err != nil { 106 | panic(fmt.Sprintf("Failed to start the test environment manager: %v", err)) 107 | } 108 | }() 109 | <-testEnv.Manager.Elected() 110 | 111 | user, err := testEnv.AddUser(envtest.User{ 112 | Name: "testenv-admin", 113 | Groups: []string{"system:masters"}, 114 | }, nil) 115 | if err != nil { 116 | panic(fmt.Sprintf("Failed to create testenv-admin user: %v", err)) 117 | } 118 | 119 | kubeConfig, err = user.KubeConfig() 120 | if err != nil { 121 | panic(fmt.Sprintf("Failed to create the testenv-admin user kubeconfig: %v", err)) 122 | } 123 | 124 | // Client with caching disabled. 125 | k8sClient, err = client.New(testEnv.Config, client.Options{Scheme: scheme.Scheme}) 126 | if err != nil { 127 | panic(fmt.Sprintf("Failed to create k8s client: %v", err)) 128 | } 129 | 130 | // Create a Vault test instance. 131 | pool, resource, err := createVaultTestInstance() 132 | if err != nil { 133 | panic(fmt.Sprintf("Failed to create Vault instance: %v", err)) 134 | } 135 | defer func() { 136 | pool.Purge(resource) 137 | }() 138 | 139 | code = run() 140 | 141 | if debugMode { 142 | events := &corev1.EventList{} 143 | _ = k8sClient.List(ctx, events) 144 | for _, event := range events.Items { 145 | fmt.Printf("%s %s \n%s\n", 146 | event.InvolvedObject.Name, event.GetAnnotations()["kustomize.toolkit.fluxcd.io/revision"], 147 | event.Message) 148 | } 149 | } 150 | 151 | fmt.Println("Stopping the test environment") 152 | if err := testEnv.Stop(); err != nil { 153 | panic(fmt.Sprintf("Failed to stop the test environment: %v", err)) 154 | } 155 | 156 | fmt.Println("Stopping the file server") 157 | testServer.Stop() 158 | if err := os.RemoveAll(testServer.Root()); err != nil { 159 | panic(fmt.Sprintf("Failed to remove storage server dir: %v", err)) 160 | } 161 | 162 | return code 163 | } 164 | 165 | func TestMain(m *testing.M) { 166 | code := runInContext(func(testEnv *testenv.Environment) { 167 | controllerName := "flux-kcl-controller" 168 | testMetricsH = controller.NewMetrics(testEnv, metrics.MustMakeRecorder(), v1alpha1.KCLRunFinalizer) 169 | kstatusCheck = kcheck.NewChecker(testEnv.Client, 170 | &kcheck.Conditions{ 171 | NegativePolarity: []string{meta.StalledCondition, meta.ReconcilingCondition}, 172 | }) 173 | // Disable fetch for the in-progress kstatus checker so that it can be 174 | // asked to evaluate snapshot of an object. This is needed to prevent 175 | // the object status from changing right before the checker fetches it 176 | // for inspection. 177 | kstatusInProgressCheck = kcheck.NewInProgressChecker(testEnv.Client) 178 | kstatusInProgressCheck.DisableFetch = true 179 | reconciler = &KCLRunReconciler{ 180 | ControllerName: controllerName, 181 | Client: testEnv, 182 | EventRecorder: testEnv.GetEventRecorderFor(controllerName), 183 | Metrics: testMetricsH, 184 | DisallowedFieldManagers: []string{overrideManagerName}, 185 | } 186 | if err := (reconciler).SetupWithManager(ctx, testEnv, KCLRunReconcilerOptions{ 187 | DependencyRequeueInterval: 2 * time.Second, 188 | }); err != nil { 189 | panic(fmt.Sprintf("Failed to start KCLRunReconciler: %v", err)) 190 | } 191 | }, m.Run) 192 | 193 | os.Exit(code) 194 | } 195 | 196 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz1234567890") 197 | 198 | func randStringRunes(n int) string { 199 | b := make([]rune, n) 200 | for i := range b { 201 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 202 | } 203 | return string(b) 204 | } 205 | 206 | func isReconcileRunning(k *v1alpha1.KCLRun) bool { 207 | return conditions.IsReconciling(k) && 208 | conditions.GetReason(k, meta.ReconcilingCondition) != meta.ProgressingWithRetryReason 209 | } 210 | 211 | func isReconcileSuccess(k *v1alpha1.KCLRun) bool { 212 | return conditions.IsReady(k) && 213 | conditions.GetObservedGeneration(k, meta.ReadyCondition) == k.Generation && 214 | k.Status.ObservedGeneration == k.Generation && 215 | k.Status.LastAppliedRevision == k.Status.LastAttemptedRevision 216 | } 217 | 218 | func isReconcileFailure(k *v1alpha1.KCLRun) bool { 219 | if conditions.IsStalled(k) { 220 | return true 221 | } 222 | 223 | isHandled := true 224 | if v, ok := meta.ReconcileAnnotationValue(k.GetAnnotations()); ok { 225 | isHandled = k.Status.LastHandledReconcileAt == v 226 | } 227 | 228 | return isHandled && conditions.IsReconciling(k) && 229 | conditions.IsFalse(k, meta.ReadyCondition) && 230 | conditions.GetObservedGeneration(k, meta.ReadyCondition) == k.Generation && 231 | conditions.GetReason(k, meta.ReconcilingCondition) == meta.ProgressingWithRetryReason 232 | } 233 | 234 | func logStatus(t *testing.T, k *v1alpha1.KCLRun) { 235 | sts, _ := yaml.Marshal(k.Status) 236 | t.Log(string(sts)) 237 | } 238 | 239 | func getEvents(objName string, annotations map[string]string) []corev1.Event { 240 | var result []corev1.Event 241 | events := &corev1.EventList{} 242 | _ = k8sClient.List(ctx, events) 243 | for _, event := range events.Items { 244 | if event.InvolvedObject.Name == objName { 245 | if annotations == nil && len(annotations) == 0 { 246 | result = append(result, event) 247 | } else { 248 | for ak, av := range annotations { 249 | if event.GetAnnotations()[ak] == av { 250 | result = append(result, event) 251 | break 252 | } 253 | } 254 | } 255 | } 256 | } 257 | return result 258 | } 259 | 260 | func createNamespace(name string) error { 261 | namespace := &corev1.Namespace{ 262 | ObjectMeta: metav1.ObjectMeta{Name: name}, 263 | } 264 | return k8sClient.Create(context.Background(), namespace) 265 | } 266 | 267 | func createKubeConfigSecret(namespace string) error { 268 | secret := &corev1.Secret{ 269 | ObjectMeta: metav1.ObjectMeta{ 270 | Name: "kubeconfig", 271 | Namespace: namespace, 272 | }, 273 | Data: map[string][]byte{ 274 | "value.yaml": kubeConfig, 275 | }, 276 | } 277 | return k8sClient.Create(context.Background(), secret) 278 | } 279 | 280 | func applyGitRepository(objKey client.ObjectKey, artifactName string, revision string) error { 281 | repo := &sourcev1.GitRepository{ 282 | TypeMeta: metav1.TypeMeta{ 283 | Kind: sourcev1.GitRepositoryKind, 284 | APIVersion: sourcev1.GroupVersion.String(), 285 | }, 286 | ObjectMeta: metav1.ObjectMeta{ 287 | Name: objKey.Name, 288 | Namespace: objKey.Namespace, 289 | }, 290 | Spec: sourcev1.GitRepositorySpec{ 291 | URL: "https://github.com/test/repository", 292 | Interval: metav1.Duration{Duration: time.Minute}, 293 | }, 294 | } 295 | 296 | b, _ := os.ReadFile(filepath.Join(testServer.Root(), artifactName)) 297 | dig := digest.SHA256.FromBytes(b) 298 | 299 | url := fmt.Sprintf("%s/%s", testServer.URL(), artifactName) 300 | 301 | status := sourcev1.GitRepositoryStatus{ 302 | Conditions: []metav1.Condition{ 303 | { 304 | Type: meta.ReadyCondition, 305 | Status: metav1.ConditionTrue, 306 | LastTransitionTime: metav1.Now(), 307 | Reason: sourcev1.GitOperationSucceedReason, 308 | }, 309 | }, 310 | Artifact: &sourcev1.Artifact{ 311 | Path: url, 312 | URL: url, 313 | Revision: revision, 314 | Digest: dig.String(), 315 | LastUpdateTime: metav1.Now(), 316 | }, 317 | } 318 | 319 | opt := []client.PatchOption{ 320 | client.ForceOwnership, 321 | client.FieldOwner("flux-kcl-controller"), 322 | } 323 | 324 | if err := k8sClient.Patch(context.Background(), repo, client.Apply, opt...); err != nil { 325 | return err 326 | } 327 | 328 | repo.ManagedFields = nil 329 | repo.Status = status 330 | 331 | statusOpts := &client.SubResourcePatchOptions{ 332 | PatchOptions: client.PatchOptions{ 333 | FieldManager: "source-controller", 334 | }, 335 | } 336 | 337 | if err := k8sClient.Status().Patch(context.Background(), repo, client.Apply, statusOpts); err != nil { 338 | return err 339 | } 340 | return nil 341 | } 342 | 343 | func createVaultTestInstance() (*dockertest.Pool, *dockertest.Resource, error) { 344 | // uses a sensible default on windows (tcp/http) and linux/osx (socket) 345 | pool, err := dockertest.NewPool("") 346 | if err != nil { 347 | return nil, nil, fmt.Errorf("could not connect to docker: %s", err) 348 | } 349 | 350 | // pulls an image, creates a container based on it and runs it 351 | resource, err := pool.Run("vault", vaultVersion, []string{"VAULT_DEV_ROOT_TOKEN_ID=secret"}) 352 | if err != nil { 353 | return nil, nil, fmt.Errorf("could not start resource: %s", err) 354 | } 355 | 356 | os.Setenv("VAULT_ADDR", fmt.Sprintf("http://127.0.0.1:%v", resource.GetPort("8200/tcp"))) 357 | os.Setenv("VAULT_TOKEN", "secret") 358 | // exponential backoff-retry, because the application in the container might not be ready to accept connections yet 359 | if err := pool.Retry(func() error { 360 | cli, err := api.NewClient(api.DefaultConfig()) 361 | if err != nil { 362 | return fmt.Errorf("cannot create Vault Client: %w", err) 363 | } 364 | status, err := cli.Sys().InitStatus() 365 | if err != nil { 366 | return err 367 | } 368 | if status != true { 369 | return fmt.Errorf("vault not ready yet") 370 | } 371 | if err := cli.Sys().Mount("sops", &api.MountInput{ 372 | Type: "transit", 373 | }); err != nil { 374 | return fmt.Errorf("cannot create Vault Transit Engine: %w", err) 375 | } 376 | 377 | return nil 378 | }); err != nil { 379 | return nil, nil, fmt.Errorf("could not connect to docker: %w", err) 380 | } 381 | 382 | return pool, resource, nil 383 | } 384 | -------------------------------------------------------------------------------- /api/v1alpha1/kclrun_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The KCL authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "time" 21 | 22 | "github.com/fluxcd/pkg/apis/meta" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | ) 25 | 26 | const ( 27 | KCLRunKind = "KCLRun" 28 | KCLRunFinalizer = "finalizers.krm.kcl.dev.fluxcd" 29 | MaxConditionMessageLength = 20000 30 | EnabledValue = "enabled" 31 | DisabledValue = "disabled" 32 | MergeValue = "Merge" 33 | IfNotPresentValue = "IfNotPresent" 34 | IgnoreValue = "Ignore" 35 | ) 36 | 37 | // KCLRunSpec defines the desired state of KCLRun 38 | type KCLRunSpec struct { 39 | // CommonMetadata specifies the common labels and annotations that are 40 | // applied to all resources. Any existing label or annotation will be 41 | // overridden if its key matches a common one. 42 | // +optional 43 | CommonMetadata *CommonMetadata `json:"commonMetadata,omitempty" yaml:"commonMetadata,omitempty"` 44 | 45 | // DependsOn may contain a meta.NamespacedObjectReference slice 46 | // with references to Kustomization resources that must be ready before this 47 | // Kustomization can be reconciled. 48 | // +optional 49 | DependsOn []meta.NamespacedObjectReference `json:"dependsOn,omitempty" yaml:"dependsOn,omitempty"` 50 | 51 | // Timeout is the time to wait for any individual Kubernetes operation (like Jobs 52 | // for hooks) during the performance. Defaults to '5m0s'. 53 | // +kubebuilder:validation:Type=string 54 | // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" 55 | // +optional 56 | Timeout *metav1.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"` 57 | 58 | // PersistentClient tells the controller to use a persistent Kubernetes 59 | // client for this release. When enabled, the client will be reused for the 60 | // duration of the reconciliation, instead of being created and destroyed 61 | // for each (step of a). 62 | // 63 | // If not set, it defaults to true. 64 | // 65 | // +optional 66 | PersistentClient *bool `json:"persistentClient,omitempty" yaml:"persistentClient,omitempty"` 67 | 68 | // The KubeConfig for reconciling the controller on a remote cluster. 69 | // When used in combination with `KCLRunSpec.ServiceAccountName`, 70 | // forces the controller to act on behalf of that Service Account at the 71 | // target cluster. 72 | // If the --default-service-account flag is set, its value will be used as 73 | // a controller level fallback for when `KCLRunSpec.ServiceAccountName` 74 | // is empty. 75 | // +optional 76 | KubeConfig *meta.KubeConfigReference `json:"kubeConfig,omitempty" yaml:"kubeConfig,omitempty"` 77 | 78 | // The name of the Kubernetes service account to impersonate 79 | // when reconciling this KCL source. 80 | // +kubebuilder:validation:MinLength=1 81 | // +kubebuilder:validation:MaxLength=253 82 | // +optional 83 | ServiceAccountName string `json:"serviceAccountName,omitempty" yaml:"serviceAccountName,omitempty"` 84 | 85 | // TargetNamespace to target when performing operations for the KCL. 86 | // Defaults to the namespace of the KCL source. 87 | // +kubebuilder:validation:MinLength=1 88 | // +kubebuilder:validation:MaxLength=63 89 | // +kubebuilder:validation:Optional 90 | // +optional 91 | TargetNamespace string `json:"targetNamespace,omitempty" yaml:"targetNamespace,omitempty"` 92 | 93 | // Force instructs the controller to recreate resources 94 | // when patching fails due to an immutable field change. 95 | // +kubebuilder:default:=false 96 | // +optional 97 | Force bool `json:"force,omitempty" yaml:"force,omitempty"` 98 | 99 | // The interval at which to reconcile the KCL Module. 100 | // This interval is approximate and may be subject to jitter to ensure 101 | // efficient use of resources. 102 | // +kubebuilder:validation:Type=string 103 | // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" 104 | // +required 105 | Interval metav1.Duration `json:"interval" yaml:"interval"` 106 | 107 | // The interval at which to retry a previously failed reconciliation. 108 | // When not specified, the controller uses the KCLRunSpec.Interval 109 | // value to retry failures. 110 | // +kubebuilder:validation:Type=string 111 | // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" 112 | // +optional 113 | RetryInterval *metav1.Duration `json:"retryInterval,omitempty" yaml:"retryInterval,omitempty"` 114 | 115 | // Path to the directory containing the kcl.mod file. 116 | // Defaults to 'None', which translates to the root path of the SourceRef. 117 | // +optional 118 | Path string `json:"path,omitempty" yaml:"path,omitempty"` 119 | 120 | // Config is the KCL compile config. 121 | // +optional 122 | Config *ConfigSpec `json:"config,omitempty" yaml:"config,omitempty"` 123 | 124 | // ArgumentReferences holds references to ConfigMaps and Secrets containing 125 | // the KCL compile config. The ConfigMap and the Secret data keys represent the config names. 126 | // +optional 127 | ArgumentsReferences []ArgumentReference `json:"argumentsReferences,omitempty" yaml:"argumentsReferences,omitempty"` 128 | 129 | // Prune enables garbage collection. 130 | // +required 131 | Prune bool `json:"prune"` 132 | 133 | // A list of resources to be included in the health assessment. 134 | // +optional 135 | HealthChecks []meta.NamespacedObjectKindReference `json:"healthChecks,omitempty"` 136 | 137 | // Wait instructs the controller to check the health of all the reconciled 138 | // resources. When enabled, the HealthChecks are ignored. Defaults to false. 139 | // +optional 140 | Wait bool `json:"wait,omitempty"` 141 | 142 | // Reference of the source where the kcl file is. 143 | // +required 144 | SourceRef CrossNamespaceSourceReference `json:"sourceRef"` 145 | 146 | // This flag tells the controller to suspend subsequent kustomize executions, 147 | // it does not apply to already started executions. Defaults to false. 148 | // +optional 149 | Suspend bool `json:"suspend,omitempty"` 150 | } 151 | 152 | // CommonMetadata defines the common labels and annotations. 153 | type CommonMetadata struct { 154 | // Annotations to be added to the object's metadata. 155 | // +optional 156 | Annotations map[string]string `json:"annotations,omitempty"` 157 | 158 | // Labels to be added to the object's metadata. 159 | // +optional 160 | Labels map[string]string `json:"labels,omitempty"` 161 | } 162 | 163 | // ConfigSpec defines the compile config. 164 | type ConfigSpec struct { 165 | // Arguments is the list of top level dynamic arguments for the kcl option function, e.g., env="prod" 166 | // +optional 167 | Arguments []string `json:"arguments,omitempty" yaml:"arguments,omitempty"` 168 | // Settings is the list of kcl setting files including all of the CLI config. 169 | // +optional 170 | Settings []string `json:"settings,omitempty" yaml:"settings,omitempty"` 171 | // Overrides is the list of override paths and values, e.g., app.image="v2" 172 | // +optional 173 | Overrides []string `json:"overrides,omitempty" yaml:"overrides,omitempty"` 174 | // PathSelectors is the list of path selectors to select output result, e.g., a.b.c 175 | // +optional 176 | PathSelectors []string `json:"pathSelectors,omitempty" yaml:"pathSelectors,omitempty"` 177 | // Vendor denotes running kcl in the vendor mode. 178 | // +optional 179 | Vendor bool `json:"vendor,omitempty" yaml:"vendor,omitempty"` 180 | // SortKeys denotes sorting the output result keys, e.g., `{b = 1, a = 2} => {a = 2, b = 1}`. 181 | // +optional 182 | SortKeys bool `json:"sortKeys,omitempty" yaml:"sortKeys,omitempty"` 183 | // ShowHidden denotes output the hidden attribute in the result. 184 | // +optional 185 | ShowHidden bool `json:"showHidden,omitempty" yaml:"showHidden,omitempty"` 186 | // DisableNone denotes running kcl and disable dumping None values. 187 | // +optional 188 | DisableNone bool `json:"disableNone,omitempty" yaml:"disableNone,omitempty"` 189 | } 190 | 191 | // ArgumentReference contains a reference to a resource containing the KCL compile config. 192 | type ArgumentReference struct { 193 | // Kind of the values referent, valid values are ('Secret', 'ConfigMap'). 194 | // +kubebuilder:validation:Enum=Secret;ConfigMap 195 | // +required 196 | Kind string `json:"kind" yaml:"kind"` 197 | // Name of the values referent. Should reside in the same namespace as the 198 | // referring resource. 199 | // +kubebuilder:validation:MinLength=1 200 | // +kubebuilder:validation:MaxLength=253 201 | // +required 202 | Name string `json:"name" yaml:"name"` 203 | // Optional indicates whether the referenced resource must exist, or whether to 204 | // tolerate its absence. If true and the referenced resource is absent, proceed 205 | // as if the resource was present but empty, without any variables defined. 206 | // +kubebuilder:default:=false 207 | // +optional 208 | Optional bool `json:"optional,omitempty" yaml:"optional,omitempty"` 209 | } 210 | 211 | // KCLRunStatus defines the observed state of KCLRun 212 | type KCLRunStatus struct { 213 | meta.ReconcileRequestStatus `json:",inline" yaml:",inline"` 214 | 215 | // ObservedGeneration is the last reconciled generation. 216 | // +optional 217 | ObservedGeneration int64 `json:"observedGeneration,omitempty" yaml:"observedGeneration,omitempty"` 218 | 219 | // +optional 220 | Conditions []metav1.Condition `json:"conditions,omitempty" yaml:"conditions,omitempty"` 221 | 222 | // The last successfully applied revision. 223 | // Equals the Revision of the applied Artifact from the referenced Source. 224 | // +optional 225 | LastAppliedRevision string `json:"lastAppliedRevision,omitempty" yaml:"lastAppliedRevision,omitempty"` 226 | 227 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 228 | // Important: Run "make" to regenerate code after modifying this file 229 | 230 | // LastAttemptedRevision is the revision of the last reconciliation attempt. 231 | // +optional 232 | LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty" yaml:"lastAttemptedRevision,omitempty"` 233 | 234 | // LastAttemptedRevisionDigest is the digest of the last reconciliation attempt. 235 | // This is only set for OCIRepository sources. 236 | // +optional 237 | LastAttemptedRevisionDigest string `json:"lastAttemptedRevisionDigest,omitempty" yaml:"lastAttemptedRevisionDigest,omitempty"` 238 | 239 | // Inventory contains the list of Kubernetes resource object references that 240 | // have been successfully applied. 241 | // +optional 242 | Inventory *ResourceInventory `json:"inventory,omitempty" yaml:"inventory,omitempty"` 243 | } 244 | 245 | //+kubebuilder:object:root=true 246 | //+kubebuilder:subresource:status 247 | 248 | // KCLRun is the Schema for the kclruns API 249 | type KCLRun struct { 250 | metav1.TypeMeta `json:",inline" yaml:",inline"` 251 | metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"` 252 | 253 | Spec KCLRunSpec `json:"spec,omitempty" yaml:"spec,omitempty"` 254 | // +kubebuilder:default:={"observedGeneration":-1} 255 | Status KCLRunStatus `json:"status,omitempty" yaml:"status,omitempty"` 256 | } 257 | 258 | //+kubebuilder:object:root=true 259 | 260 | // KCLRunList contains a list of KCLRun 261 | type KCLRunList struct { 262 | metav1.TypeMeta `json:",inline" yaml:",inline"` 263 | metav1.ListMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"` 264 | Items []KCLRun `json:"items" yaml:"items"` 265 | } 266 | 267 | func init() { 268 | SchemeBuilder.Register(&KCLRun{}, &KCLRunList{}) 269 | } 270 | 271 | // GetConditions returns the status conditions of the object. 272 | func (in *KCLRun) GetConditions() []metav1.Condition { 273 | return in.Status.Conditions 274 | } 275 | 276 | // SetConditions sets the status conditions on the object. 277 | func (in *KCLRun) SetConditions(conditions []metav1.Condition) { 278 | in.Status.Conditions = conditions 279 | } 280 | 281 | // GetReleaseNamespace returns the configured TargetNamespace, or the namespace 282 | // of the KCLRun. 283 | func (in *KCLRun) GetReleaseNamespace() string { 284 | if in.Spec.TargetNamespace != "" { 285 | return in.Spec.TargetNamespace 286 | } 287 | return in.Namespace 288 | } 289 | 290 | // GetTimeout returns the configured Timeout, or the default of 300s. 291 | func (in *KCLRun) GetTimeout() time.Duration { 292 | duration := in.Spec.Interval.Duration - 30*time.Second 293 | if in.Spec.Timeout != nil { 294 | duration = in.Spec.Timeout.Duration 295 | } 296 | if duration < 30*time.Second { 297 | return 30 * time.Second 298 | } 299 | return duration 300 | } 301 | 302 | // GetRetryInterval returns the retry interval 303 | func (in *KCLRun) GetRetryInterval() time.Duration { 304 | if in.Spec.RetryInterval != nil { 305 | return in.Spec.RetryInterval.Duration 306 | } 307 | return in.GetRequeueAfter() 308 | } 309 | 310 | // GetRequeueAfter returns the duration after which the KCLRun must be 311 | // reconciled again. 312 | func (in *KCLRun) GetRequeueAfter() time.Duration { 313 | return in.Spec.Interval.Duration 314 | } 315 | 316 | // GetDependsOn returns the list of dependencies across-namespaces. 317 | func (in *KCLRun) GetDependsOn() []meta.NamespacedObjectReference { 318 | return in.Spec.DependsOn 319 | } 320 | 321 | // UsePersistentClient returns the configured PersistentClient, or the default 322 | // of true. 323 | func (in *KCLRun) UsePersistentClient() bool { 324 | if in.Spec.PersistentClient == nil { 325 | return true 326 | } 327 | return *in.Spec.PersistentClient 328 | } 329 | -------------------------------------------------------------------------------- /config/crd/bases/krm.kcl.dev.fluxcd_kclruns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.16.1 7 | name: kclruns.krm.kcl.dev.fluxcd 8 | spec: 9 | group: krm.kcl.dev.fluxcd 10 | names: 11 | kind: KCLRun 12 | listKind: KCLRunList 13 | plural: kclruns 14 | singular: kclrun 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: KCLRun is the Schema for the kclruns API 21 | properties: 22 | apiVersion: 23 | description: |- 24 | APIVersion defines the versioned schema of this representation of an object. 25 | Servers should convert recognized schemas to the latest internal value, and 26 | may reject unrecognized values. 27 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 28 | type: string 29 | kind: 30 | description: |- 31 | Kind is a string value representing the REST resource this object represents. 32 | Servers may infer this from the endpoint the client submits requests to. 33 | Cannot be updated. 34 | In CamelCase. 35 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | description: KCLRunSpec defines the desired state of KCLRun 41 | properties: 42 | argumentsReferences: 43 | description: |- 44 | ConfigReference holds references to ConfigMaps and Secrets containing 45 | the KCL compile config. The ConfigMap and the Secret data keys represent the config names. 46 | items: 47 | description: ArgumentReference contains a reference to a resource 48 | containing the KCL compile config. 49 | properties: 50 | kind: 51 | description: Kind of the values referent, valid values are ('Secret', 52 | 'ConfigMap'). 53 | enum: 54 | - Secret 55 | - ConfigMap 56 | type: string 57 | name: 58 | description: |- 59 | Name of the values referent. Should reside in the same namespace as the 60 | referring resource. 61 | maxLength: 253 62 | minLength: 1 63 | type: string 64 | optional: 65 | default: false 66 | description: |- 67 | Optional indicates whether the referenced resource must exist, or whether to 68 | tolerate its absence. If true and the referenced resource is absent, proceed 69 | as if the resource was present but empty, without any variables defined. 70 | type: boolean 71 | required: 72 | - kind 73 | - name 74 | type: object 75 | type: array 76 | commonMetadata: 77 | description: |- 78 | CommonMetadata specifies the common labels and annotations that are 79 | applied to all resources. Any existing label or annotation will be 80 | overridden if its key matches a common one. 81 | properties: 82 | annotations: 83 | additionalProperties: 84 | type: string 85 | description: Annotations to be added to the object's metadata. 86 | type: object 87 | labels: 88 | additionalProperties: 89 | type: string 90 | description: Labels to be added to the object's metadata. 91 | type: object 92 | type: object 93 | config: 94 | description: Config is the KCL compile config. 95 | properties: 96 | arguments: 97 | description: Arguments is the list of top level dynamic arguments 98 | for the kcl option function, e.g., env="prod" 99 | items: 100 | type: string 101 | type: array 102 | disableNone: 103 | description: DisableNone denotes running kcl and disable dumping 104 | None values. 105 | type: boolean 106 | overrides: 107 | description: Overrides is the list of override paths and values, 108 | e.g., app.image="v2" 109 | items: 110 | type: string 111 | type: array 112 | pathSelectors: 113 | description: PathSelectors is the list of path selectors to select 114 | output result, e.g., a.b.c 115 | items: 116 | type: string 117 | type: array 118 | settings: 119 | description: Settings is the list of kcl setting files including 120 | all of the CLI config. 121 | items: 122 | type: string 123 | type: array 124 | showHidden: 125 | description: ShowHidden denotes output the hidden attribute in 126 | the result. 127 | type: boolean 128 | sortKeys: 129 | description: SortKeys denotes sorting the output result keys, 130 | e.g., `{b = 1, a = 2} => {a = 2, b = 1}`. 131 | type: boolean 132 | vendor: 133 | description: Vendor denotes running kcl in the vendor mode. 134 | type: boolean 135 | type: object 136 | dependsOn: 137 | description: |- 138 | DependsOn may contain a meta.NamespacedObjectReference slice 139 | with references to Kustomization resources that must be ready before this 140 | Kustomization can be reconciled. 141 | items: 142 | description: |- 143 | NamespacedObjectReference contains enough information to locate the referenced Kubernetes resource object in any 144 | namespace. 145 | properties: 146 | name: 147 | description: Name of the referent. 148 | type: string 149 | namespace: 150 | description: Namespace of the referent, when not specified it 151 | acts as LocalObjectReference. 152 | type: string 153 | required: 154 | - name 155 | type: object 156 | type: array 157 | force: 158 | default: false 159 | description: |- 160 | Force instructs the controller to recreate resources 161 | when patching fails due to an immutable field change. 162 | type: boolean 163 | healthChecks: 164 | description: A list of resources to be included in the health assessment. 165 | items: 166 | description: |- 167 | NamespacedObjectKindReference contains enough information to locate the typed referenced Kubernetes resource object 168 | in any namespace. 169 | properties: 170 | apiVersion: 171 | description: API version of the referent, if not specified the 172 | Kubernetes preferred version will be used. 173 | type: string 174 | kind: 175 | description: Kind of the referent. 176 | type: string 177 | name: 178 | description: Name of the referent. 179 | type: string 180 | namespace: 181 | description: Namespace of the referent, when not specified it 182 | acts as LocalObjectReference. 183 | type: string 184 | required: 185 | - kind 186 | - name 187 | type: object 188 | type: array 189 | interval: 190 | description: |- 191 | The interval at which to reconcile the KCL Module. 192 | This interval is approximate and may be subject to jitter to ensure 193 | efficient use of resources. 194 | pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ 195 | type: string 196 | kubeConfig: 197 | description: |- 198 | The KubeConfig for reconciling the controller on a remote cluster. 199 | When used in combination with `KCLRunSpec.ServiceAccountName`, 200 | forces the controller to act on behalf of that Service Account at the 201 | target cluster. 202 | If the --default-service-account flag is set, its value will be used as 203 | a controller level fallback for when `KCLRunSpec.ServiceAccountName` 204 | is empty. 205 | properties: 206 | secretRef: 207 | description: |- 208 | SecretRef holds the name of a secret that contains a key with 209 | the kubeconfig file as the value. If no key is set, the key will default 210 | to 'value'. 211 | It is recommended that the kubeconfig is self-contained, and the secret 212 | is regularly updated if credentials such as a cloud-access-token expire. 213 | Cloud specific `cmd-path` auth helpers will not function without adding 214 | binaries and credentials to the Pod that is responsible for reconciling 215 | Kubernetes resources. 216 | properties: 217 | key: 218 | description: Key in the Secret, when not specified an implementation-specific 219 | default key is used. 220 | type: string 221 | name: 222 | description: Name of the Secret. 223 | type: string 224 | required: 225 | - name 226 | type: object 227 | required: 228 | - secretRef 229 | type: object 230 | path: 231 | description: |- 232 | Path to the directory containing the kcl.mod file. 233 | Defaults to 'None', which translates to the root path of the SourceRef. 234 | type: string 235 | persistentClient: 236 | description: |- 237 | PersistentClient tells the controller to use a persistent Kubernetes 238 | client for this release. When enabled, the client will be reused for the 239 | duration of the reconciliation, instead of being created and destroyed 240 | for each (step of a). 241 | 242 | If not set, it defaults to true. 243 | type: boolean 244 | prune: 245 | description: Prune enables garbage collection. 246 | type: boolean 247 | retryInterval: 248 | description: |- 249 | The interval at which to retry a previously failed reconciliation. 250 | When not specified, the controller uses the KCLRunSpec.Interval 251 | value to retry failures. 252 | pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ 253 | type: string 254 | serviceAccountName: 255 | description: |- 256 | The name of the Kubernetes service account to impersonate 257 | when reconciling this KCL source. 258 | maxLength: 253 259 | minLength: 1 260 | type: string 261 | sourceRef: 262 | description: Reference of the source where the kcl file is. 263 | properties: 264 | apiVersion: 265 | description: API version of the referent. 266 | type: string 267 | kind: 268 | description: Kind of the referent. 269 | enum: 270 | - OCIRepository 271 | - GitRepository 272 | - Bucket 273 | type: string 274 | name: 275 | description: Name of the referent. 276 | type: string 277 | namespace: 278 | description: |- 279 | Namespace of the referent, defaults to the namespace of the Kubernetes 280 | resource object that contains the reference. 281 | type: string 282 | required: 283 | - kind 284 | - name 285 | type: object 286 | suspend: 287 | description: |- 288 | This flag tells the controller to suspend subsequent kustomize executions, 289 | it does not apply to already started executions. Defaults to false. 290 | type: boolean 291 | targetNamespace: 292 | description: |- 293 | TargetNamespace to target when performing operations for the KCL. 294 | Defaults to the namespace of the KCL source. 295 | maxLength: 63 296 | minLength: 1 297 | type: string 298 | timeout: 299 | description: |- 300 | Timeout is the time to wait for any individual Kubernetes operation (like Jobs 301 | for hooks) during the performance. Defaults to '5m0s'. 302 | pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ 303 | type: string 304 | wait: 305 | description: |- 306 | Wait instructs the controller to check the health of all the reconciled 307 | resources. When enabled, the HealthChecks are ignored. Defaults to false. 308 | type: boolean 309 | required: 310 | - interval 311 | - prune 312 | - sourceRef 313 | type: object 314 | status: 315 | default: 316 | observedGeneration: -1 317 | description: KCLRunStatus defines the observed state of KCLRun 318 | properties: 319 | conditions: 320 | items: 321 | description: Condition contains details for one aspect of the current 322 | state of this API Resource. 323 | properties: 324 | lastTransitionTime: 325 | description: |- 326 | lastTransitionTime is the last time the condition transitioned from one status to another. 327 | This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. 328 | format: date-time 329 | type: string 330 | message: 331 | description: |- 332 | message is a human readable message indicating details about the transition. 333 | This may be an empty string. 334 | maxLength: 32768 335 | type: string 336 | observedGeneration: 337 | description: |- 338 | observedGeneration represents the .metadata.generation that the condition was set based upon. 339 | For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date 340 | with respect to the current state of the instance. 341 | format: int64 342 | minimum: 0 343 | type: integer 344 | reason: 345 | description: |- 346 | reason contains a programmatic identifier indicating the reason for the condition's last transition. 347 | Producers of specific condition types may define expected values and meanings for this field, 348 | and whether the values are considered a guaranteed API. 349 | The value should be a CamelCase string. 350 | This field may not be empty. 351 | maxLength: 1024 352 | minLength: 1 353 | pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ 354 | type: string 355 | status: 356 | description: status of the condition, one of True, False, Unknown. 357 | enum: 358 | - "True" 359 | - "False" 360 | - Unknown 361 | type: string 362 | type: 363 | description: type of condition in CamelCase or in foo.example.com/CamelCase. 364 | maxLength: 316 365 | 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])$ 366 | type: string 367 | required: 368 | - lastTransitionTime 369 | - message 370 | - reason 371 | - status 372 | - type 373 | type: object 374 | type: array 375 | inventory: 376 | description: |- 377 | Inventory contains the list of Kubernetes resource object references that 378 | have been successfully applied. 379 | properties: 380 | entries: 381 | description: Entries of Kubernetes resource object references. 382 | items: 383 | description: ResourceRef contains the information necessary 384 | to locate a resource within a cluster. 385 | properties: 386 | id: 387 | description: |- 388 | ID is the string representation of the Kubernetes resource object's metadata, 389 | in the format '___'. 390 | type: string 391 | v: 392 | description: Version is the API version of the Kubernetes 393 | resource object's kind. 394 | type: string 395 | required: 396 | - id 397 | - v 398 | type: object 399 | type: array 400 | required: 401 | - entries 402 | type: object 403 | lastAppliedRevision: 404 | description: |- 405 | The last successfully applied revision. 406 | Equals the Revision of the applied Artifact from the referenced Source. 407 | type: string 408 | lastAttemptedRevision: 409 | description: LastAttemptedRevision is the revision of the last reconciliation 410 | attempt. 411 | type: string 412 | lastAttemptedRevisionDigest: 413 | description: |- 414 | LastAttemptedRevisionDigest is the digest of the last reconciliation attempt. 415 | This is only set for OCIRepository sources. 416 | type: string 417 | lastHandledReconcileAt: 418 | description: |- 419 | LastHandledReconcileAt holds the value of the most recent 420 | reconcile request value, so a change of the annotation value 421 | can be detected. 422 | type: string 423 | observedGeneration: 424 | description: ObservedGeneration is the last reconciled generation. 425 | format: int64 426 | type: integer 427 | type: object 428 | type: object 429 | served: true 430 | storage: true 431 | subresources: 432 | status: {} 433 | -------------------------------------------------------------------------------- /config/crd/bases/ocirepositories.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.15.0 7 | name: ocirepositories.source.toolkit.fluxcd.io 8 | spec: 9 | group: source.toolkit.fluxcd.io 10 | names: 11 | kind: OCIRepository 12 | listKind: OCIRepositoryList 13 | plural: ocirepositories 14 | shortNames: 15 | - ocirepo 16 | singular: ocirepository 17 | scope: Namespaced 18 | versions: 19 | - additionalPrinterColumns: 20 | - jsonPath: .spec.url 21 | name: URL 22 | type: string 23 | - jsonPath: .status.conditions[?(@.type=="Ready")].status 24 | name: Ready 25 | type: string 26 | - jsonPath: .status.conditions[?(@.type=="Ready")].message 27 | name: Status 28 | type: string 29 | - jsonPath: .metadata.creationTimestamp 30 | name: Age 31 | type: date 32 | name: v1beta2 33 | schema: 34 | openAPIV3Schema: 35 | description: OCIRepository is the Schema for the ocirepositories API 36 | properties: 37 | apiVersion: 38 | description: |- 39 | APIVersion defines the versioned schema of this representation of an object. 40 | Servers should convert recognized schemas to the latest internal value, and 41 | may reject unrecognized values. 42 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 43 | type: string 44 | kind: 45 | description: |- 46 | Kind is a string value representing the REST resource this object represents. 47 | Servers may infer this from the endpoint the client submits requests to. 48 | Cannot be updated. 49 | In CamelCase. 50 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 51 | type: string 52 | metadata: 53 | type: object 54 | spec: 55 | description: OCIRepositorySpec defines the desired state of OCIRepository 56 | properties: 57 | certSecretRef: 58 | description: |- 59 | CertSecretRef can be given the name of a Secret containing 60 | either or both of 61 | 62 | 63 | - a PEM-encoded client certificate (`tls.crt`) and private 64 | key (`tls.key`); 65 | - a PEM-encoded CA certificate (`ca.crt`) 66 | 67 | 68 | and whichever are supplied, will be used for connecting to the 69 | registry. The client cert and key are useful if you are 70 | authenticating with a certificate; the CA cert is useful if 71 | you are using a self-signed server certificate. The Secret must 72 | be of type `Opaque` or `kubernetes.io/tls`. 73 | 74 | 75 | Note: Support for the `caFile`, `certFile` and `keyFile` keys have 76 | been deprecated. 77 | properties: 78 | name: 79 | description: Name of the referent. 80 | type: string 81 | required: 82 | - name 83 | type: object 84 | ignore: 85 | description: |- 86 | Ignore overrides the set of excluded patterns in the .sourceignore format 87 | (which is the same as .gitignore). If not provided, a default will be used, 88 | consult the documentation for your version to find out what those are. 89 | type: string 90 | insecure: 91 | description: Insecure allows connecting to a non-TLS HTTP container 92 | registry. 93 | type: boolean 94 | interval: 95 | description: |- 96 | Interval at which the OCIRepository URL is checked for updates. 97 | This interval is approximate and may be subject to jitter to ensure 98 | efficient use of resources. 99 | pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ 100 | type: string 101 | layerSelector: 102 | description: |- 103 | LayerSelector specifies which layer should be extracted from the OCI artifact. 104 | When not specified, the first layer found in the artifact is selected. 105 | properties: 106 | mediaType: 107 | description: |- 108 | MediaType specifies the OCI media type of the layer 109 | which should be extracted from the OCI Artifact. The 110 | first layer matching this type is selected. 111 | type: string 112 | operation: 113 | description: |- 114 | Operation specifies how the selected layer should be processed. 115 | By default, the layer compressed content is extracted to storage. 116 | When the operation is set to 'copy', the layer compressed content 117 | is persisted to storage as it is. 118 | enum: 119 | - extract 120 | - copy 121 | type: string 122 | type: object 123 | provider: 124 | default: generic 125 | description: |- 126 | The provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'. 127 | When not specified, defaults to 'generic'. 128 | enum: 129 | - generic 130 | - aws 131 | - azure 132 | - gcp 133 | type: string 134 | ref: 135 | description: |- 136 | The OCI reference to pull and monitor for changes, 137 | defaults to the latest tag. 138 | properties: 139 | digest: 140 | description: |- 141 | Digest is the image digest to pull, takes precedence over SemVer. 142 | The value should be in the format 'sha256:'. 143 | type: string 144 | semver: 145 | description: |- 146 | SemVer is the range of tags to pull selecting the latest within 147 | the range, takes precedence over Tag. 148 | type: string 149 | semverFilter: 150 | description: SemverFilter is a regex pattern to filter the tags 151 | within the SemVer range. 152 | type: string 153 | tag: 154 | description: Tag is the image tag to pull, defaults to latest. 155 | type: string 156 | type: object 157 | secretRef: 158 | description: |- 159 | SecretRef contains the secret name containing the registry login 160 | credentials to resolve image metadata. 161 | The secret must be of type kubernetes.io/dockerconfigjson. 162 | properties: 163 | name: 164 | description: Name of the referent. 165 | type: string 166 | required: 167 | - name 168 | type: object 169 | serviceAccountName: 170 | description: |- 171 | ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate 172 | the image pull if the service account has attached pull secrets. For more information: 173 | https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account 174 | type: string 175 | suspend: 176 | description: This flag tells the controller to suspend the reconciliation 177 | of this source. 178 | type: boolean 179 | timeout: 180 | default: 60s 181 | description: The timeout for remote OCI Repository operations like 182 | pulling, defaults to 60s. 183 | pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ 184 | type: string 185 | url: 186 | description: |- 187 | URL is a reference to an OCI artifact repository hosted 188 | on a remote container registry. 189 | pattern: ^oci://.*$ 190 | type: string 191 | verify: 192 | description: |- 193 | Verify contains the secret name containing the trusted public keys 194 | used to verify the signature and specifies which provider to use to check 195 | whether OCI image is authentic. 196 | properties: 197 | matchOIDCIdentity: 198 | description: |- 199 | MatchOIDCIdentity specifies the identity matching criteria to use 200 | while verifying an OCI artifact which was signed using Cosign keyless 201 | signing. The artifact's identity is deemed to be verified if any of the 202 | specified matchers match against the identity. 203 | items: 204 | description: |- 205 | OIDCIdentityMatch specifies options for verifying the certificate identity, 206 | i.e. the issuer and the subject of the certificate. 207 | properties: 208 | issuer: 209 | description: |- 210 | Issuer specifies the regex pattern to match against to verify 211 | the OIDC issuer in the Fulcio certificate. The pattern must be a 212 | valid Go regular expression. 213 | type: string 214 | subject: 215 | description: |- 216 | Subject specifies the regex pattern to match against to verify 217 | the identity subject in the Fulcio certificate. The pattern must 218 | be a valid Go regular expression. 219 | type: string 220 | required: 221 | - issuer 222 | - subject 223 | type: object 224 | type: array 225 | provider: 226 | default: cosign 227 | description: Provider specifies the technology used to sign the 228 | OCI Artifact. 229 | enum: 230 | - cosign 231 | - notation 232 | type: string 233 | secretRef: 234 | description: |- 235 | SecretRef specifies the Kubernetes Secret containing the 236 | trusted public keys. 237 | properties: 238 | name: 239 | description: Name of the referent. 240 | type: string 241 | required: 242 | - name 243 | type: object 244 | required: 245 | - provider 246 | type: object 247 | required: 248 | - interval 249 | - url 250 | type: object 251 | status: 252 | default: 253 | observedGeneration: -1 254 | description: OCIRepositoryStatus defines the observed state of OCIRepository 255 | properties: 256 | artifact: 257 | description: Artifact represents the output of the last successful 258 | OCI Repository sync. 259 | properties: 260 | digest: 261 | description: Digest is the digest of the file in the form of ':'. 262 | pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ 263 | type: string 264 | lastUpdateTime: 265 | description: |- 266 | LastUpdateTime is the timestamp corresponding to the last update of the 267 | Artifact. 268 | format: date-time 269 | type: string 270 | metadata: 271 | additionalProperties: 272 | type: string 273 | description: Metadata holds upstream information such as OCI annotations. 274 | type: object 275 | path: 276 | description: |- 277 | Path is the relative file path of the Artifact. It can be used to locate 278 | the file in the root of the Artifact storage on the local file system of 279 | the controller managing the Source. 280 | type: string 281 | revision: 282 | description: |- 283 | Revision is a human-readable identifier traceable in the origin source 284 | system. It can be a Git commit SHA, Git tag, a Helm chart version, etc. 285 | type: string 286 | size: 287 | description: Size is the number of bytes in the file. 288 | format: int64 289 | type: integer 290 | url: 291 | description: |- 292 | URL is the HTTP address of the Artifact as exposed by the controller 293 | managing the Source. It can be used to retrieve the Artifact for 294 | consumption, e.g. by another controller applying the Artifact contents. 295 | type: string 296 | required: 297 | - lastUpdateTime 298 | - path 299 | - revision 300 | - url 301 | type: object 302 | conditions: 303 | description: Conditions holds the conditions for the OCIRepository. 304 | items: 305 | description: "Condition contains details for one aspect of the current 306 | state of this API Resource.\n---\nThis struct is intended for 307 | direct use as an array at the field path .status.conditions. For 308 | example,\n\n\n\ttype FooStatus struct{\n\t // Represents the 309 | observations of a foo's current state.\n\t // Known .status.conditions.type 310 | are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // 311 | +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t 312 | \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" 313 | patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t 314 | \ // other fields\n\t}" 315 | properties: 316 | lastTransitionTime: 317 | description: |- 318 | lastTransitionTime is the last time the condition transitioned from one status to another. 319 | This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. 320 | format: date-time 321 | type: string 322 | message: 323 | description: |- 324 | message is a human readable message indicating details about the transition. 325 | This may be an empty string. 326 | maxLength: 32768 327 | type: string 328 | observedGeneration: 329 | description: |- 330 | observedGeneration represents the .metadata.generation that the condition was set based upon. 331 | For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date 332 | with respect to the current state of the instance. 333 | format: int64 334 | minimum: 0 335 | type: integer 336 | reason: 337 | description: |- 338 | reason contains a programmatic identifier indicating the reason for the condition's last transition. 339 | Producers of specific condition types may define expected values and meanings for this field, 340 | and whether the values are considered a guaranteed API. 341 | The value should be a CamelCase string. 342 | This field may not be empty. 343 | maxLength: 1024 344 | minLength: 1 345 | pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ 346 | type: string 347 | status: 348 | description: status of the condition, one of True, False, Unknown. 349 | enum: 350 | - "True" 351 | - "False" 352 | - Unknown 353 | type: string 354 | type: 355 | description: |- 356 | type of condition in CamelCase or in foo.example.com/CamelCase. 357 | --- 358 | Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be 359 | useful (see .node.status.conditions), the ability to deconflict is important. 360 | The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) 361 | maxLength: 316 362 | 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])$ 363 | type: string 364 | required: 365 | - lastTransitionTime 366 | - message 367 | - reason 368 | - status 369 | - type 370 | type: object 371 | type: array 372 | contentConfigChecksum: 373 | description: |- 374 | ContentConfigChecksum is a checksum of all the configurations related to 375 | the content of the source artifact: 376 | - .spec.ignore 377 | - .spec.layerSelector 378 | observed in .status.observedGeneration version of the object. This can 379 | be used to determine if the content configuration has changed and the 380 | artifact needs to be rebuilt. 381 | It has the format of `:`, for example: `sha256:`. 382 | 383 | 384 | Deprecated: Replaced with explicit fields for observed artifact content 385 | config in the status. 386 | type: string 387 | lastHandledReconcileAt: 388 | description: |- 389 | LastHandledReconcileAt holds the value of the most recent 390 | reconcile request value, so a change of the annotation value 391 | can be detected. 392 | type: string 393 | observedGeneration: 394 | description: ObservedGeneration is the last observed generation. 395 | format: int64 396 | type: integer 397 | observedIgnore: 398 | description: |- 399 | ObservedIgnore is the observed exclusion patterns used for constructing 400 | the source artifact. 401 | type: string 402 | observedLayerSelector: 403 | description: |- 404 | ObservedLayerSelector is the observed layer selector used for constructing 405 | the source artifact. 406 | properties: 407 | mediaType: 408 | description: |- 409 | MediaType specifies the OCI media type of the layer 410 | which should be extracted from the OCI Artifact. The 411 | first layer matching this type is selected. 412 | type: string 413 | operation: 414 | description: |- 415 | Operation specifies how the selected layer should be processed. 416 | By default, the layer compressed content is extracted to storage. 417 | When the operation is set to 'copy', the layer compressed content 418 | is persisted to storage as it is. 419 | enum: 420 | - extract 421 | - copy 422 | type: string 423 | type: object 424 | url: 425 | description: URL is the download link for the artifact output of the 426 | last OCI Repository sync. 427 | type: string 428 | type: object 429 | type: object 430 | served: true 431 | storage: true 432 | subresources: 433 | status: {} 434 | --------------------------------------------------------------------------------