├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md ├── dependabot.yml └── workflows │ └── main.yaml ├── .gitignore ├── .golangci.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile-check ├── Dockerfile-dev ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── VERSION ├── api └── v1alpha1 │ ├── addons_types.go │ ├── groupversion_info.go │ └── zz_generated.deepcopy.go ├── bin ├── package.sh └── setup.sh ├── chart ├── Chart.yaml ├── readme.md ├── templates │ ├── _helpers.tpl │ ├── gotk │ │ ├── helm-controller │ │ │ ├── crd.yaml │ │ │ └── deployment.yaml │ │ ├── net.yaml │ │ ├── rbac.yaml │ │ └── source-controller │ │ │ ├── crds.yaml │ │ │ ├── deployment.yaml │ │ │ └── service.yaml │ └── kraan │ │ ├── crd.yaml │ │ ├── deployment.yaml │ │ └── rbac.yaml └── values.yaml ├── config ├── crd │ ├── bases │ │ └── kraan.io_addonslayers.yaml │ └── kustomization.yaml ├── default │ └── kustomization.yaml ├── manager │ ├── deployment.yaml │ └── kustomization.yaml ├── rbac │ ├── addons_editor_role.yaml │ ├── addons_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ └── role_binding.yaml └── samples │ └── addons.yaml ├── controllers └── addons_controller.go ├── docs ├── design │ └── README.md ├── dev-guide.md ├── diagrams │ ├── addon-layer-dependencies │ ├── addon-layer-dependencies.png │ └── custom-platform.png └── user-guide.md ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── main └── main.go ├── makefile.mk ├── pkg ├── apply │ ├── applier_test.go │ ├── export_test.go │ ├── layerApplier.go │ └── testdata │ │ ├── addons.json │ │ ├── all_layers │ │ └── addons.yaml │ │ ├── apply │ │ ├── double_release │ │ │ ├── microservice-one.yaml │ │ │ └── microservice-two.yaml │ │ ├── simple_ns │ │ │ └── addons-repo.yaml │ │ ├── single_layer │ │ │ └── test-layer.yaml │ │ ├── single_release │ │ │ └── microservice.yaml │ │ └── test_namespace │ │ │ └── namespace.yaml │ │ ├── crds │ │ └── test_crd.yaml │ │ ├── helmreleases.json │ │ ├── nolayerowner1-helmreleases.json │ │ ├── noowner1-helmreleases.json │ │ ├── notorphaned1-helmreleases.json │ │ ├── orphaned-bad-ts-helmreleases.json │ │ ├── orphaned1-helmreleases.json │ │ ├── orphaned2-helmreleases.json │ │ └── orphaned3-helmreleases.json ├── common │ └── common.go ├── internal │ ├── kubectl │ │ ├── execProvider.go │ │ ├── export_test.go │ │ ├── kubectl.go │ │ ├── kubectl_test.go │ │ └── testdata │ │ │ └── apply │ │ │ └── simpleapply │ │ │ └── simpleNamespace.yaml │ ├── mocks │ │ ├── kubectl │ │ │ ├── mockExecProvider.go │ │ │ └── mockKubectl.go │ │ ├── logr │ │ │ └── mockLogger.go │ │ └── tarconsumer │ │ │ └── mockTarconsumer.go │ ├── tarconsumer │ │ ├── export_test.go │ │ ├── tarconsumer.go │ │ ├── tarconsumer_test.go │ │ └── testdata │ │ │ ├── addons │ │ │ ├── base │ │ │ │ └── microservice1.yaml │ │ │ └── bootstrap │ │ │ │ ├── microservice1.yaml │ │ │ │ └── microservice2.yaml │ │ │ └── empty │ │ │ └── dummy │ └── testutils │ │ └── testutils.go ├── layers │ ├── layers.go │ ├── layers_test.go │ └── testdata │ │ ├── layersdata.json │ │ ├── layersdata1.json │ │ ├── layersdata2.json │ │ └── reposdata.json ├── logging │ └── logging.go ├── metrics │ └── metrics.go ├── mocks │ ├── apply │ │ └── mockLayerApplier.go │ ├── client │ │ └── mockClient.go │ ├── layers │ │ └── mockLayers.go │ ├── metrics │ │ └── mockmetrics.go │ └── repos │ │ └── mockRepos.go └── repos │ ├── export_test.go │ ├── repos.go │ ├── repos_test.go │ └── testdata │ ├── addons │ ├── base │ │ └── microservice1.yaml │ └── bootstrap │ │ ├── microservice1.yaml │ │ └── microservice2.yaml │ └── source-repo.yaml ├── project-name.mk ├── samples ├── datadog.yaml ├── local.yaml ├── namespace.yaml └── proxy.yaml ├── scripts ├── deploy.sh ├── kind.sh ├── run-controller.sh ├── test.sh └── validate.sh └── testdata ├── addons-orphan ├── addons-repo.yaml ├── addons-source.yaml ├── addons.yaml ├── apps │ ├── microservice1.yaml │ └── microservice2.yaml ├── base │ ├── kustomization.yaml │ └── microservice1.yaml ├── bootstrap │ ├── integration-test.yaml │ └── microservice1.yaml └── mgmt │ └── microservice1.yaml ├── addons ├── addons-repo.yaml ├── addons-source.yaml ├── addons.yaml ├── apps │ └── microservice1.yaml ├── base │ ├── kustomization.yaml │ └── microservice1.yaml ├── bootstrap │ ├── integration-test.yaml │ ├── microservice1.yaml │ └── microservice2.yaml └── mgmt │ └── microservice1.yaml ├── namespaces.yaml └── templates └── template-http.yaml /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Checklist 4 | 5 | 6 | 7 | - [ ] If any changes are made, bump VERSION file `chart/Chart.yaml`'s `version:` and `chart/Chart.yaml`'s `appVersion:`, since chart version and appVersion are unified. 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: "sigs.k8s.io/controller-runtime" 10 | - dependency-name: "k8s.io/apiextensions-apiserver" 11 | - dependency-name: "k8s.io/apimachinery" 12 | - dependency-name: "k8s.io/client-go" 13 | - dependency-name: "k8s.io/api" 14 | - dependency-name: "github.com/fluxcd/helm-controller/api" 15 | - dependency-name: "github.com/fluxcd/source-controller/api" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, build with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Kubernetes Generated files - skip generated files, except for vendored files 16 | 17 | !vendor/**/zz_generated.* 18 | 19 | # editor and IDE paraphernalia 20 | .idea 21 | *.swp 22 | *.swo 23 | *~ 24 | *.code-workspace 25 | 26 | .idea/ 27 | build/ 28 | **/._gometalinter 29 | **/**/*coverage/ 30 | #**/pkg/**/*.md 31 | vendor/ 32 | .vscode/ 33 | 34 | main/__debug_bin.DS_Store 35 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Community Code of Conduct 2 | 3 | kraan follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). 4 | 5 | Instances of abusive, harassing, or otherwise unacceptable behavior 6 | must be reported by contacting a _kraan_ project maintainer. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Kraan 2 | 3 | Thank you for considering to contribute to Kraan 🎉👍 4 | 5 | This document will provide help on how to go about this. You can contribute in the following ways: 6 | 7 | * Reporting bugs 8 | * Suggesting features 9 | * Contributing code 10 | 11 | All contributions and project spaces are subject to our [Code of Conduct](https://github.com/fidelity/.github/blob/main/CODE_OF_CONDUCT.md). 12 | 13 | ## Reporting Bugs 14 | 15 | Reporting bugs is an essential part in making kraan better for its end users. 16 | 17 | Please open an issue **unless** you are making a significant security disclosure. A new **Bug Report** can be raised [here](https://github.com/fidelity/kraan/issues/new?assignees=&labels=kind%2Fbug&template=bug_report.md&title=). 18 | 19 | When raising bugs please include as much information as possible including steps about how to reproduce the problem and what you expect the behavior to be. 20 | 21 | ## How to disclose security concerns responsibly 22 | 23 | Please follow the instructions in our [security policy](https://github.com/fidelity/.github/blob/main/SECURITY.md) (also visible in the Security tab on the project's repo). 24 | 25 | ## Suggesting features 26 | 27 | If there is a feature that you would like in kraan then please let us know about it. 28 | 29 | Features are also suggested using GitHub Issues. A new **Feature enhancement request** can be raised [here](https://github.com/fidelity/kraan/issues/new?labels=kind%2Ffeature&template=feature_request.md&title=). 30 | 31 | Include as much information as possible, understanding the problem that the feature is trying to solve will really help us in understanding the benefit. 32 | 33 | ## Contributing Code 34 | 35 | Code contributions to kraan are very welcome as long as you follow a few rules: 36 | 37 | * Your contribution must be received under the project's open source license. 38 | * You must have permission to make the contribution. We strongly recommend including a Signed-off-by line to indicate your adherence to the [Developer Certificate of Origin](https://developercertificate.org/). 39 | * All code contributions must be made via PR, and all checks must pass before merging. 40 | 41 | If you need a pointer on where to start you can look at the **good first issue** and **help wanted** issues: 42 | 43 | * [good first issue](https://github.com/fidelity/kraan/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) - small changes that are suitable for a beginner 44 | * [help wanted](https://github.com/fidelity/kraan/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) - more involved changes 45 | 46 | You can also choose your own issue to work on from this list of available issues. 47 | 48 | When choosing an issue to work on its preferable that you choose a issue that is planned for the next milestone and that has a higher priority....but this is just a nice to have and any contribution would be considered and welcomed. 49 | 50 | ### Getting started 51 | 52 | See the [Developer Guide](docs/dev-guide.md). 53 | 54 | Commit and push your branch. We follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for the commits and PRs. If the PR is a work in progress ensure that you prefix the title with WIP:. 55 | Create a pull request 56 | Check that the PR checks pass 57 | 58 | Once a PR has been created it will be reviwed by one of the maintainers of Kraan. 59 | 60 | ## Getting help 61 | 62 | If you have other questions about this project, please open an issue. To reach the Fidelity OSPO directly, please email [opensource@fmr.com](mailto:opensource@fmr.com). 63 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.22 as builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | # Copy the go source 9 | COPY pkg/ pkg 10 | COPY main/ main 11 | COPY controllers/ controllers 12 | COPY api/ api 13 | # cache deps before building and copying source so that we don't need to re-download as much 14 | # and so that source changes don't invalidate our downloaded layer 15 | RUN go mod download 16 | 17 | ARG TARGETARCH 18 | ARG TARGETOS 19 | 20 | # Build 21 | RUN mkdir bin 22 | RUN apt install -y curl tar 23 | RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.31.0/bin/${TARGETOS}/${TARGETARCH}/kubectl 24 | RUN chmod +x ./kubectl 25 | RUN mv kubectl bin 26 | RUN curl -LO https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v3.8.7/kustomize_v3.8.7_${TARGETOS}_${TARGETARCH}.tar.gz 27 | RUN tar xzvf ./kustomize_v3.8.7_${TARGETOS}_${TARGETARCH}.tar.gz 28 | RUN chmod +x ./kustomize 29 | RUN mv kustomize bin 30 | RUN rm ./kustomize_v3.8.7_${TARGETOS}_${TARGETARCH}.tar.gz 31 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH="${TARGETARCH}" GO111MODULE=on go build -a -o bin/kraan-controller main/main.go 32 | 33 | FROM gcr.io/distroless/static:latest 34 | WORKDIR / 35 | COPY --from=builder /workspace/bin/ /usr/local/bin/ 36 | USER 1000 37 | ENTRYPOINT ["/usr/local/bin/kraan-controller"] 38 | -------------------------------------------------------------------------------- /Dockerfile-check: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.22 as builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | # Copy the go source 9 | COPY pkg/ pkg 10 | COPY main/ main 11 | COPY controllers/ controllers 12 | COPY api/ api 13 | RUN mkdir bin 14 | # Copy makefiles and run tests 15 | COPY Makefile Makefile 16 | COPY makefile.mk makefile.mk 17 | COPY project-name.mk project-name.mk 18 | COPY .golangci.yml .golangci.yml 19 | COPY bin/ bin/ 20 | RUN apt install -y curl 21 | RUN bin/setup.sh 22 | # Temporary fix see https://github.com/fidelity/kraan/issues/114 23 | RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.31.0/bin/linux/amd64/kubectl 24 | RUN chmod +x ./kubectl 25 | RUN mv kubectl bin 26 | RUN cp bin/* /usr/local/bin 27 | # Make 28 | RUN make clean 29 | RUN make 30 | RUN make integration 31 | 32 | -------------------------------------------------------------------------------- /Dockerfile-dev: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.22 as builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | # Copy the go source 9 | COPY pkg/ pkg 10 | COPY main/ main 11 | COPY controllers/ controllers 12 | COPY api/ api 13 | # cache deps before building and copying source so that we don't need to re-download as much 14 | # and so that source changes don't invalidate our downloaded layer 15 | RUN go mod download 16 | 17 | # Build 18 | RUN mkdir bin 19 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o bin/kraan-controller main/main.go 20 | RUN apt install -y curl 21 | RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.31.0/bin/linux/amd64/kubectl 22 | RUN chmod +x ./kubectl 23 | RUN mv kubectl bin 24 | RUN curl -LO https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v3.8.7/kustomize_v3.8.7_linux_amd64.tar.gz 25 | RUN tar xzf ./kustomize_v3.8.7_linux_amd64.tar.gz 26 | RUN chmod +x ./kustomize 27 | RUN mv kustomize bin 28 | RUN rm ./kustomize_v3.8.7_linux_amd64.tar.gz 29 | 30 | FROM ubuntu:latest 31 | WORKDIR / 32 | COPY --from=builder /workspace/bin/ /usr/local/bin/ 33 | ENTRYPOINT ["/usr/local/bin/kraan-controller"] 34 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: io 2 | projectName: kraan-controller 3 | layout: 4 | - go.kubebuilder.io/v4 5 | repo: github.com/fidelity/kraan 6 | resources: 7 | - api: 8 | crdVersion: v1alpha1 9 | namespaced: false 10 | group: kraan 11 | kind: AddonsLayer 12 | version: v1alpha1 13 | version: "3" 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kraan - Building platforms on top of K8s 2 | 3 | *This project is currently in the early stages of development and expected to release 4 | beta versions by end of September*. 5 | 6 | ## What is kraan? 7 | 8 | kraan helps you deploy and manage *'layers'* on top of kubernetes. By applying *layers* 9 | on top of K8s clusters, you can build focused platforms on top of 10 | K8s e.g ML platforms, Data platform etc. Each *layer* is a collection of addons and 11 | can have dependencies established between the layers. i.e a "mgmt-layer" 12 | can depend on a "common-layer". Kraan will always ensure that the addons in the "common-layer" are deployed successfully before deploying 13 | the "mgmt-layer" addons. A layer is represented as a kubernetes custom resource and 14 | kraan is an [operator](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) that 15 | is deployed into the cluster and works constantly to reconcile the state of the 16 | layer custom resource. 17 | 18 | kraan is powered by [flux2](https://toolkit.fluxcd.io/) and builds on 19 | top of projects like [source-controller](https://github.com/fluxcd/source-controller) 20 | and [helm-controller](https://github.com/fluxcd/helm-controller). 21 | 22 | ## Use cases 23 | 24 | Kraan can be used wherever you have requirements to manage add-ons on top of k8s 25 | clusters and especially when you want to package the addons into dependant categories. 26 | If you have a mutating webhook injecting a side-car and set of security 27 | plugins which should always be deployed first before other addons,then *layers* 28 | concept in Kraan will help there. However, Kraan is even more powerful when it comes 29 | to building custom platforms on top of k8s like the one shown below. 30 | 31 | kraan promotes the idea of building model based platforms on top of k8s i.e you can 32 | build a "general purpose" k8s platform which might have a "common" and "security" 33 | layers which packages all the common tooling, applications inside that cluster might 34 | need as well as organization specific bits (e.g org specific security addons etc). 35 | You can also say that the "common-layer" *depends-on* "security-layer" to be deployed first. 36 | This "general purpose" k8s platform can then be *extended* by applying another "ml-layer" 37 | which can then be exposed as an ML platform to the development teams. The end result here 38 | is developers working on top of secure and custom-built platforms which adheres 39 | to organization specific policies etc. And rolling out updates to this ML 40 | platform is as simple as "kubectl apply -f " where new versions of the layers are 41 | deployed into the cluster and kraan operator will constantly work towards 42 | getting that platform to match the latest desired state! 43 | 44 | The below diagram shows how you can use kraan to build a focused multi-cloud 45 | platform where "common" and "security" layers are shared across clouds whereas 46 | other layers become cloud specific. 47 | 48 | ![custom-platform](docs/diagrams/custom-platform.png) 49 | 50 | ## Design 51 | 52 | Kraan is a kubernetes controller that is built on top of [k8s custom resources]( 53 | https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/). 54 | It works in tandem with [source-controller](https://github.com/fluxcd/source-controller) 55 | and [helm-controller](https://github.com/fluxcd/helm-controller) and hence they are always 56 | deployed together. The detailed design documentation can be found [here](docs/design/README.md) 57 | 58 | ## Usage 59 | 60 | See [User Guide](docs/user-guide.md) for usage instructions. 61 | 62 | See [Developer Guide](docs/dev-guide.md) for development. 63 | 64 | ## Contributions 65 | 66 | Contributions are very welcome. Please read the [contributing guide](CONTRIBUTING.md) or see the docs. 67 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | v0.3.47 -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | 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 kraan v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=kraan.io 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: "kraan.io", 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 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | /* 4 | 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 | "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *AddonsLayer) DeepCopyInto(out *AddonsLayer) { 30 | *out = *in 31 | out.TypeMeta = in.TypeMeta 32 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 33 | in.Spec.DeepCopyInto(&out.Spec) 34 | in.Status.DeepCopyInto(&out.Status) 35 | } 36 | 37 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonsLayer. 38 | func (in *AddonsLayer) DeepCopy() *AddonsLayer { 39 | if in == nil { 40 | return nil 41 | } 42 | out := new(AddonsLayer) 43 | in.DeepCopyInto(out) 44 | return out 45 | } 46 | 47 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 48 | func (in *AddonsLayer) DeepCopyObject() runtime.Object { 49 | if c := in.DeepCopy(); c != nil { 50 | return c 51 | } 52 | return nil 53 | } 54 | 55 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 56 | func (in *AddonsLayerList) DeepCopyInto(out *AddonsLayerList) { 57 | *out = *in 58 | out.TypeMeta = in.TypeMeta 59 | in.ListMeta.DeepCopyInto(&out.ListMeta) 60 | if in.Items != nil { 61 | in, out := &in.Items, &out.Items 62 | *out = make([]AddonsLayer, len(*in)) 63 | for i := range *in { 64 | (*in)[i].DeepCopyInto(&(*out)[i]) 65 | } 66 | } 67 | } 68 | 69 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonsLayerList. 70 | func (in *AddonsLayerList) DeepCopy() *AddonsLayerList { 71 | if in == nil { 72 | return nil 73 | } 74 | out := new(AddonsLayerList) 75 | in.DeepCopyInto(out) 76 | return out 77 | } 78 | 79 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 80 | func (in *AddonsLayerList) DeepCopyObject() runtime.Object { 81 | if c := in.DeepCopy(); c != nil { 82 | return c 83 | } 84 | return nil 85 | } 86 | 87 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 88 | func (in *AddonsLayerSpec) DeepCopyInto(out *AddonsLayerSpec) { 89 | *out = *in 90 | out.Source = in.Source 91 | in.PreReqs.DeepCopyInto(&out.PreReqs) 92 | if in.Interval != nil { 93 | in, out := &in.Interval, &out.Interval 94 | *out = new(v1.Duration) 95 | **out = **in 96 | } 97 | if in.Timeout != nil { 98 | in, out := &in.Timeout, &out.Timeout 99 | *out = new(v1.Duration) 100 | **out = **in 101 | } 102 | } 103 | 104 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonsLayerSpec. 105 | func (in *AddonsLayerSpec) DeepCopy() *AddonsLayerSpec { 106 | if in == nil { 107 | return nil 108 | } 109 | out := new(AddonsLayerSpec) 110 | in.DeepCopyInto(out) 111 | return out 112 | } 113 | 114 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 115 | func (in *AddonsLayerStatus) DeepCopyInto(out *AddonsLayerStatus) { 116 | *out = *in 117 | if in.Conditions != nil { 118 | in, out := &in.Conditions, &out.Conditions 119 | *out = make([]v1.Condition, len(*in)) 120 | for i := range *in { 121 | (*in)[i].DeepCopyInto(&(*out)[i]) 122 | } 123 | } 124 | if in.Resources != nil { 125 | in, out := &in.Resources, &out.Resources 126 | *out = make([]Resource, len(*in)) 127 | for i := range *in { 128 | (*in)[i].DeepCopyInto(&(*out)[i]) 129 | } 130 | } 131 | } 132 | 133 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonsLayerStatus. 134 | func (in *AddonsLayerStatus) DeepCopy() *AddonsLayerStatus { 135 | if in == nil { 136 | return nil 137 | } 138 | out := new(AddonsLayerStatus) 139 | in.DeepCopyInto(out) 140 | return out 141 | } 142 | 143 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 144 | func (in *PreReqs) DeepCopyInto(out *PreReqs) { 145 | *out = *in 146 | if in.DependsOn != nil { 147 | in, out := &in.DependsOn, &out.DependsOn 148 | *out = make([]string, len(*in)) 149 | copy(*out, *in) 150 | } 151 | } 152 | 153 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreReqs. 154 | func (in *PreReqs) DeepCopy() *PreReqs { 155 | if in == nil { 156 | return nil 157 | } 158 | out := new(PreReqs) 159 | in.DeepCopyInto(out) 160 | return out 161 | } 162 | 163 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 164 | func (in *Resource) DeepCopyInto(out *Resource) { 165 | *out = *in 166 | in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) 167 | } 168 | 169 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Resource. 170 | func (in *Resource) DeepCopy() *Resource { 171 | if in == nil { 172 | return nil 173 | } 174 | out := new(Resource) 175 | in.DeepCopyInto(out) 176 | return out 177 | } 178 | 179 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 180 | func (in Resources) DeepCopyInto(out *Resources) { 181 | { 182 | in := &in 183 | *out = make(Resources, len(*in)) 184 | for i := range *in { 185 | (*in)[i].DeepCopyInto(&(*out)[i]) 186 | } 187 | } 188 | } 189 | 190 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Resources. 191 | func (in Resources) DeepCopy() Resources { 192 | if in == nil { 193 | return nil 194 | } 195 | out := new(Resources) 196 | in.DeepCopyInto(out) 197 | return *out 198 | } 199 | 200 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 201 | func (in *SourceSpec) DeepCopyInto(out *SourceSpec) { 202 | *out = *in 203 | } 204 | 205 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceSpec. 206 | func (in *SourceSpec) DeepCopy() *SourceSpec { 207 | if in == nil { 208 | return nil 209 | } 210 | out := new(SourceSpec) 211 | in.DeepCopyInto(out) 212 | return out 213 | } 214 | -------------------------------------------------------------------------------- /bin/package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Utility to gofmt, goimports and test a package 4 | # Version: 1.0 5 | 6 | if [ -z "$1" ] ; then 7 | echo "please specify a package" 8 | exit 1 9 | fi 10 | 11 | package_name="$1" 12 | 13 | if [ ! -d "$package_name" ] ; then 14 | echo "package: $package_name does not exist" 15 | exit 1 16 | fi 17 | TOP=`git rev-parse --show-toplevel` 18 | make -C $TOP/$package_name --makefile=$TOP/makefile.mk gofmt 19 | make -C $TOP/$package_name --makefile=$TOP/makefile.mk goimports 20 | make -C $TOP/$package_name --makefile=$TOP/makefile.mk clean 21 | make -C $TOP/$package_name --makefile=$TOP/makefile.mk -------------------------------------------------------------------------------- /bin/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Set versions of software required 3 | linter_version=1.55.2 4 | mockgen_version=v1.6.0 5 | helm_version=v3.6.1 6 | kind_version=v0.11.1 7 | kubectl_version=v1.31.0 8 | kustomize_version=v3.8.7 9 | 10 | function usage() 11 | { 12 | echo "USAGE: ${0##*/} [--debug]" 13 | echo "Install software required for golang project" 14 | } 15 | 16 | function args() { 17 | while [ $# -gt 0 ] 18 | do 19 | case "$1" in 20 | "--help") usage; exit;; 21 | "-?") usage; exit;; 22 | "--debug") set -x;return;; 23 | "--skip-kind") installKind=0;return;; 24 | *) usage; exit;; 25 | esac 26 | done 27 | } 28 | 29 | function install_linter() { 30 | TARGET=$(go env GOPATH) 31 | curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b "${TARGET}/bin" v${linter_version} 32 | } 33 | 34 | function install_kubebuilder() { 35 | os=$(go env GOOS) 36 | arch=$(go env GOARCH) 37 | 38 | # download kubebuilder and extract it to tmp 39 | curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH) 40 | chmod +x kubebuilder && sudo mv kubebuilder /usr/local/bin/ 41 | } 42 | 43 | function install_helm() { 44 | curl -L https://get.helm.sh/helm-${helm_version}-linux-amd64.tar.gz | tar -xz -C /tmp/ 45 | $sudo mv /tmp/linux-amd64/helm /usr/local/bin 46 | } 47 | 48 | function install_kind() { 49 | curl -Lo ./kind https://kind.sigs.k8s.io/dl/${kind_version}/kind-linux-amd64 50 | chmod 755 kind 51 | $sudo mv kind /usr/local/bin 52 | } 53 | 54 | function install_kubectl() { 55 | curl -LO https://storage.googleapis.com/kubernetes-release/release/${kubectl_version}/bin/linux/amd64/kubectl 56 | chmod +x ./kubectl 57 | $sudo mv kubectl /usr/local/bin 58 | } 59 | 60 | function install_kustomize() { 61 | curl -LO https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/${kustomize_version}/kustomize_${kustomize_version}_linux_amd64.tar.gz 62 | tar xzf ./kustomize_${kustomize_version}_linux_amd64.tar.gz 63 | chmod +x ./kustomize 64 | $sudo mv kustomize /usr/local/bin 65 | rm ./kustomize_${kustomize_version}_linux_amd64.tar.gz 66 | } 67 | 68 | installKind=1 69 | 70 | args "${@}" 71 | 72 | sudo -E env >/dev/null 2>&1 73 | if [ $? -eq 0 ]; then 74 | sudo="sudo -E" 75 | fi 76 | 77 | echo "Running setup script to setup software" 78 | 79 | golangci-lint --version 2>&1 | grep $linter_version >/dev/null 80 | ret_code="${?}" 81 | if [[ "${ret_code}" != "0" ]] ; then 82 | echo "installing linter version: ${linter_version}" 83 | install_linter 84 | golangci-lint --version 2>&1 | grep $linter_version >/dev/null 85 | ret_code="${?}" 86 | if [ "${ret_code}" != "0" ] ; then 87 | echo "Failed to install linter" 88 | echo "version: `golangci-lint --version`" 89 | echo "expecting: $linter_version" 90 | exit 1 91 | fi 92 | else 93 | echo "linter version: `golangci-lint --version`" 94 | fi 95 | 96 | export PATH=$PATH:/usr/local/kubebuilder/bin 97 | kubebuilder version 2>&1 >/dev/null 98 | ret_code="${?}" 99 | if [[ "${ret_code}" != "0" ]] ; then 100 | echo "installing kubebuilder version: " 101 | install_kubebuilder 102 | kubebuilder version 2>&1 >/dev/null 103 | ret_code="${?}" 104 | if [ "${ret_code}" != "0" ] ; then 105 | echo "Failed to install kubebuilder" 106 | exit 1 107 | fi 108 | else 109 | echo "kubebuilder version: `kubebuilder version`" 110 | fi 111 | 112 | mockgen -version 2>&1 | grep ${mockgen_version} >/dev/null 113 | ret_code="${?}" 114 | if [[ "${ret_code}" != "0" ]] ; then 115 | echo "installing mockgen version: ${mockgen_version}" 116 | go install github.com/golang/mock/mockgen@${mockgen_version} 117 | mockgen -version 2>&1 | grep ${mockgen_version} >/dev/null 118 | ret_code="${?}" 119 | if [ "${ret_code}" != "0" ] ; then 120 | echo "Failed to install mockgen" 121 | exit 1 122 | fi 123 | else 124 | echo "mockgen version: `mockgen -version`" 125 | fi 126 | 127 | flux --version >/dev/null 2>&1 128 | ret_code="${?}" 129 | if [[ "${ret_code}" != "0" ]] ; then 130 | echo "Installing latest version of flux cli" 131 | curl -s https://fluxcd.io/install.sh | $sudo bash 132 | flux --version >/dev/null 2>&1 133 | ret_code="${?}" 134 | if [ "${ret_code}" != "0" ] ; then 135 | echo "Failed to install flux" 136 | exit 1 137 | fi 138 | else 139 | echo "flux version: `flux --version`" 140 | fi 141 | 142 | helm version 2>&1 | grep ${helm_version} >/dev/null 143 | ret_code="${?}" 144 | if [[ "${ret_code}" != "0" ]] ; then 145 | echo "installing helm version: ${helm_version}" 146 | install_helm 147 | helm version 2>&1 | grep ${helm_version} >/dev/null 148 | ret_code="${?}" 149 | if [ "${ret_code}" != "0" ] ; then 150 | echo "Failed to install helm" 151 | exit 1 152 | fi 153 | else 154 | echo "helm version: `helm version`" 155 | fi 156 | 157 | kind version 2>&1 | grep ${kind_version} >/dev/null 158 | ret_code="${?}" 159 | if [[ "${ret_code}" != "0" ]] && [ $installKind == 1 ] ; then 160 | echo "installing kind version: ${kind_version}" 161 | install_kind 162 | kind version 2>&1 | grep ${kind_version} >/dev/null 163 | ret_code="${?}" 164 | if [ "${ret_code}" != "0" ] ; then 165 | echo "Failed to install kind" 166 | exit 1 167 | fi 168 | else 169 | echo "kind version: `kind version`" 170 | fi 171 | 172 | kubectl version --client 2>&1 | grep ${kubectl_version} >/dev/null 173 | ret_code="${?}" 174 | if [[ "${ret_code}" != "0" ]] ; then 175 | echo "installing kubectl version: ${kubectl_version}" 176 | install_kubectl 177 | kubectl version --client 2>&1 | grep ${kubectl_version} >/dev/null 178 | ret_code="${?}" 179 | if [ "${ret_code}" != "0" ] ; then 180 | echo "kubectl version, required: ${kubectl_version}, actual: `kubectl version --client`" 181 | echo "Failed to install kubectl" 182 | exit 1 183 | fi 184 | else 185 | echo "kubectl version: `kubectl version --client`" 186 | fi 187 | 188 | kustomize version 2>&1 | grep ${kustomize_version} >/dev/null 189 | ret_code="${?}" 190 | if [[ "${ret_code}" != "0" ]] ; then 191 | echo "installing kustomize version: ${kustomize_version}" 192 | install_kustomize 193 | kustomize version 2>&1 | grep ${kustomize_version} >/dev/null 194 | ret_code="${?}" 195 | if [ "${ret_code}" != "0" ] ; then 196 | echo "kustomize version, required: ${kustomize_version}, actual: `kustomize version`" 197 | echo "Failed to install kustomize" 198 | exit 1 199 | fi 200 | else 201 | echo "kustomize version: `kustomize version`" 202 | fi 203 | -------------------------------------------------------------------------------- /chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | description: A Helm chart for kraan controller 3 | name: kraan-controller 4 | type: application 5 | appVersion: v0.3.47 6 | version: v0.3.47 7 | -------------------------------------------------------------------------------- /chart/readme.md: -------------------------------------------------------------------------------- 1 | # Helm Chart to install the Kraan Controller and gotk components it depends on. 2 | -------------------------------------------------------------------------------- /chart/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "kraan-controller.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" | replace "_" "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "kraan-controller.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" | replace "_" "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" | replace "_" "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "kraan-controller.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{- define "kraan-controller.gotk.adminClusterRoleBinding.name" -}} 35 | {{ $crbName := .Values.gotk.rbac.adminClusterRoleBinding.name }} 36 | {{- if eq $crbName "cluster-reconciler" -}} 37 | {{ $crbName }}-{{ .Release.Namespace }} 38 | {{- else -}} 39 | {{ $crbName }} 40 | {{- end -}} 41 | {{- end -}} -------------------------------------------------------------------------------- /chart/templates/gotk/helm-controller/deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.gotk.helmController.enabled -}} 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | labels: 6 | app.kubernetes.io/instance: {{.Release.Namespace}} 7 | app.kubernetes.io/version: latest 8 | control-plane: controller 9 | name: helm-controller 10 | namespace: {{.Release.Namespace}} 11 | spec: 12 | replicas: 1 13 | selector: 14 | matchLabels: 15 | app: helm-controller 16 | template: 17 | metadata: 18 | annotations: 19 | prometheus.io/port: "8080" 20 | prometheus.io/scrape: "true" 21 | labels: 22 | app: helm-controller 23 | {{- if .Values.global.extraLabels }} 24 | {{ toYaml .Values.global.extraLabels | indent 8 }} 25 | {{- end }} 26 | {{- if .Values.gotk.helmController.extraLabels }} 27 | {{ toYaml .Values.gotk.helmController.extraLabels | indent 8 }} 28 | {{- end }} 29 | annotations: 30 | {{- if .Values.global.extraPodAnnotations }} 31 | {{ toYaml .Values.global.extraPodAnnotations | indent 8 }} 32 | {{- end }} 33 | {{- if .Values.gotk.helmController.extraPodAnnotations }} 34 | {{ toYaml .Values.gotk.helmController.extraPodAnnotations | indent 8 }} 35 | {{- end }} 36 | {{- if .Values.gotk.helmController.prometheus.enabled }} 37 | {{ toYaml .Values.global.prometheusAnnotations | indent 8 }} 38 | {{- end }} 39 | spec: 40 | serviceAccountName: fluxcd 41 | securityContext: 42 | # Required for AWS IAM Role bindings 43 | # https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-technical-overview.html 44 | fsGroup: 1337 45 | containers: 46 | - name: manager 47 | args: 48 | - --events-addr= 49 | - --watch-all-namespaces=true 50 | - --log-level=info 51 | - --log-encoding=json 52 | - --enable-leader-election 53 | {{- if .Values.gotk.helmController.extraArgs }} 54 | {{- range .Values.gotk.helmController.extraArgs }} 55 | {{ cat "-" . }} 56 | {{- end }} 57 | {{- end }} 58 | env: 59 | - name: RUNTIME_NAMESPACE 60 | valueFrom: 61 | fieldRef: 62 | fieldPath: metadata.namespace 63 | {{- if .Values.gotk.helmController.proxy }} 64 | - name: HTTPS_PROXY 65 | value: {{ .Values.global.env.httpsProxy }} 66 | - name: NO_PROXY 67 | value: {{ .Values.global.env.noProxy }} 68 | {{- end }} 69 | image: {{ .Values.gotk.helmController.image.repository }}/{{ .Values.gotk.helmController.image.name }}:{{ .Values.gotk.helmController.image.tag }} 70 | imagePullPolicy: {{ .Values.gotk.helmController.image.imagePullPolicy | default "IfNotPresent" }} 71 | livenessProbe: 72 | httpGet: 73 | path: /healthz 74 | port: healthz 75 | ports: 76 | - containerPort: 8080 77 | name: http-prom 78 | protocol: TCP 79 | - containerPort: 9440 80 | name: healthz 81 | protocol: TCP 82 | readinessProbe: 83 | httpGet: 84 | path: /readyz 85 | port: healthz 86 | securityContext: 87 | allowPrivilegeEscalation: false 88 | readOnlyRootFilesystem: true 89 | runAsNonRoot: true 90 | capabilities: 91 | drop: ["ALL"] 92 | seccompProfile: 93 | type: RuntimeDefault 94 | volumeMounts: 95 | - mountPath: /tmp 96 | name: temp 97 | resources: 98 | {{ toYaml .Values.gotk.helmController.resources | indent 10 }} 99 | terminationGracePeriodSeconds: 10 100 | {{- if .Values.gotk.helmController.imagePullSecrets.name }} 101 | imagePullSecrets: 102 | - name: {{ .Values.gotk.helmController.imagePullSecrets.name }} 103 | {{- end }} 104 | {{- if .Values.gotk.helmController.priorityClassName }} 105 | priorityClassName: {{ .Values.gotk.helmController.priorityClassName }} 106 | {{- end }} 107 | {{- if .Values.gotk.helmController.preemptionPolicy }} 108 | preemptionPolicy: {{ .Values.gotk.helmController.preemptionPolicy }} 109 | {{- end }} 110 | {{- if .Values.gotk.helmController.priority }} 111 | priority: {{ .Values.gotk.helmController.priority }} 112 | {{- end }} 113 | {{- if .Values.gotk.helmController.nodeSelector }} 114 | nodeSelector: 115 | {{ toYaml .Values.gotk.helmController.nodeSelector | indent 8 }} 116 | {{- end }} 117 | {{- if .Values.gotk.helmController.tolerations }} 118 | tolerations: 119 | {{ toYaml .Values.gotk.helmController.tolerations | indent 8 }} 120 | {{- end }} 121 | {{- if .Values.gotk.helmController.affinity }} 122 | affinity: 123 | {{ toYaml .Values.gotk.helmController.affinity | indent 8 }} 124 | {{- end }} 125 | volumes: 126 | - emptyDir: {} 127 | name: temp 128 | {{- end }} -------------------------------------------------------------------------------- /chart/templates/gotk/net.yaml: -------------------------------------------------------------------------------- 1 | {{- if eq .Values.gotk.networkPolicy.enabled true -}} 2 | apiVersion: networking.k8s.io/v1 3 | kind: NetworkPolicy 4 | metadata: 5 | labels: 6 | app.kubernetes.io/instance: {{.Release.Namespace}} 7 | app.kubernetes.io/version: latest 8 | name: allow-scraping 9 | namespace: {{.Release.Namespace}} 10 | spec: 11 | ingress: 12 | - from: 13 | {{- if .Values.gotk.networkPolicy.namespaceSelector }} 14 | - namespaceSelector: {{ toYaml .Values.gotk.networkPolicy.namespaceSelector | trim | nindent 12 }} 15 | {{- else }} 16 | - namespaceSelector: {} 17 | {{- end }} 18 | ports: 19 | - port: 8080 20 | protocol: TCP 21 | podSelector: {} 22 | policyTypes: 23 | - Ingress 24 | --- 25 | apiVersion: networking.k8s.io/v1 26 | kind: NetworkPolicy 27 | metadata: 28 | labels: 29 | app.kubernetes.io/instance: {{.Release.Namespace}} 30 | app.kubernetes.io/version: latest 31 | name: deny-ingress 32 | namespace: {{.Release.Namespace}} 33 | spec: 34 | egress: 35 | - {} 36 | ingress: 37 | - from: 38 | - podSelector: {} 39 | {{- if .Values.gotk.networkPolicy.namespaceSelector }} 40 | - namespaceSelector: {{ toYaml .Values.gotk.networkPolicy.namespaceSelector | trim | nindent 12 }} 41 | {{- end }} 42 | podSelector: {} 43 | policyTypes: 44 | - Ingress 45 | - Egress 46 | {{- end -}} 47 | -------------------------------------------------------------------------------- /chart/templates/gotk/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: fluxcd 5 | namespace: {{.Release.Namespace}} 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: Role 9 | metadata: 10 | labels: 11 | app.kubernetes.io/instance: "{{ .Release.Namespace }}" 12 | app.kubernetes.io/version: latest 13 | name: crd-controller-{{.Release.Namespace}} 14 | namespace: "{{ .Release.Namespace }}" 15 | rules: 16 | - apiGroups: 17 | - source.toolkit.fluxcd.io 18 | resources: 19 | - buckets 20 | - gitrepositories 21 | - helmcharts 22 | - helmrepositories 23 | - ocirepositories 24 | verbs: 25 | - create 26 | - delete 27 | - get 28 | - list 29 | - patch 30 | - update 31 | - watch 32 | - apiGroups: 33 | - source.toolkit.fluxcd.io 34 | resources: 35 | - buckets/finalizers 36 | - gitrepositories/finalizers 37 | - helmcharts/finalizers 38 | - helmrepositories/finalizers 39 | - ocirepositories/finalizers 40 | verbs: 41 | - create 42 | - delete 43 | - get 44 | - patch 45 | - update 46 | - apiGroups: 47 | - source.toolkit.fluxcd.io 48 | resources: 49 | - buckets/status 50 | - gitrepositories/status 51 | - helmcharts/status 52 | - helmrepositories/status 53 | - ocirepositories/status 54 | verbs: 55 | - get 56 | - patch 57 | - update 58 | - apiGroups: 59 | - helm.toolkit.fluxcd.io 60 | resources: 61 | - helmreleases 62 | verbs: 63 | - create 64 | - delete 65 | - get 66 | - list 67 | - patch 68 | - update 69 | - watch 70 | - apiGroups: 71 | - helm.toolkit.fluxcd.io 72 | resources: 73 | - helmreleases/finalizers 74 | verbs: 75 | - create 76 | - delete 77 | - get 78 | - patch 79 | - update 80 | - apiGroups: 81 | - helm.toolkit.fluxcd.io 82 | resources: 83 | - helmreleases/status 84 | verbs: 85 | - get 86 | - patch 87 | - update 88 | - apiGroups: 89 | - "" 90 | resources: 91 | - secrets 92 | verbs: 93 | - get 94 | - list 95 | - watch 96 | - apiGroups: 97 | - "" 98 | resources: 99 | - events 100 | verbs: 101 | - create 102 | - patch 103 | - apiGroups: 104 | - "" 105 | resources: 106 | - configmaps 107 | - configmaps/status 108 | verbs: 109 | - get 110 | - list 111 | - watch 112 | - create 113 | - update 114 | - patch 115 | - delete 116 | - apiGroups: 117 | - coordination.k8s.io 118 | resources: 119 | - leases 120 | verbs: 121 | - get 122 | - list 123 | - watch 124 | - create 125 | - update 126 | - patch 127 | - delete 128 | --- 129 | apiVersion: rbac.authorization.k8s.io/v1 130 | kind: RoleBinding 131 | metadata: 132 | labels: 133 | app.kubernetes.io/instance: "{{ .Release.Namespace }}" 134 | app.kubernetes.io/version: latest 135 | name: crd-controller-{{.Release.Namespace}} 136 | namespace: "{{ .Release.Namespace }}" 137 | roleRef: 138 | apiGroup: rbac.authorization.k8s.io 139 | kind: Role 140 | name: crd-controller-{{.Release.Namespace}} 141 | subjects: 142 | - kind: ServiceAccount 143 | name: fluxcd 144 | namespace: "{{ .Release.Namespace }}" 145 | --- 146 | apiVersion: rbac.authorization.k8s.io/v1 147 | kind: ClusterRoleBinding 148 | metadata: 149 | labels: 150 | app.kubernetes.io/instance: "{{ .Release.Namespace }}" 151 | app.kubernetes.io/version: latest 152 | name: {{ template "kraan-controller.gotk.adminClusterRoleBinding.name" . }} 153 | roleRef: 154 | apiGroup: rbac.authorization.k8s.io 155 | kind: ClusterRole 156 | name: {{ .Values.gotk.rbac.adminClusterRole.name }} 157 | subjects: 158 | - kind: ServiceAccount 159 | name: fluxcd 160 | namespace: "{{ .Release.Namespace }}" 161 | {{ if .Values.gotk.rbac.adminClusterRole.rules }} 162 | --- 163 | apiVersion: rbac.authorization.k8s.io/v1 164 | kind: ClusterRole 165 | metadata: 166 | name: {{ .Values.gotk.rbac.adminClusterRole.name }} 167 | rules: 168 | {{ .Values.gotk.rbac.adminClusterRole.rules | toYaml }} 169 | {{- end }} -------------------------------------------------------------------------------- /chart/templates/gotk/source-controller/deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.gotk.sourceController.enabled }} 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | labels: 6 | app.kubernetes.io/instance: {{.Release.Namespace}} 7 | app.kubernetes.io/version: latest 8 | control-plane: controller 9 | name: source-controller 10 | namespace: {{.Release.Namespace}} 11 | spec: 12 | replicas: 1 13 | strategy: 14 | type: Recreate 15 | selector: 16 | matchLabels: 17 | app: source-controller 18 | template: 19 | metadata: 20 | annotations: 21 | prometheus.io/port: "8080" 22 | prometheus.io/scrape: "true" 23 | labels: 24 | app: source-controller 25 | {{- if .Values.global.extraLabels }} 26 | {{ toYaml .Values.global.extraLabels | indent 8 }} 27 | {{- end }} 28 | {{- if .Values.gotk.sourceController.extraLabels }} 29 | {{ toYaml .Values.gotk.sourceController.extraLabels | indent 8 }} 30 | {{- end }} 31 | annotations: 32 | {{- if .Values.global.extraPodAnnotations }} 33 | {{ toYaml .Values.global.extraPodAnnotations | indent 8 }} 34 | {{- end }} 35 | {{- if .Values.gotk.sourceController.extraPodAnnotations }} 36 | {{ toYaml .Values.gotk.sourceController.extraPodAnnotations | indent 8 }} 37 | {{- end }} 38 | {{- if .Values.gotk.sourceController.prometheus.enabled }} 39 | {{ toYaml .Values.global.prometheusAnnotations | indent 8 }} 40 | {{- end }} 41 | spec: 42 | securityContext: 43 | # Required for AWS IAM Role bindings 44 | # https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-technical-overview.html 45 | fsGroup: 1337 46 | serviceAccountName: fluxcd 47 | containers: 48 | - name: manager 49 | args: 50 | - --events-addr= 51 | - --watch-all-namespaces=true 52 | - --log-level=info 53 | - --log-encoding=json 54 | - --enable-leader-election 55 | - --storage-path=/data 56 | - --storage-adv-addr=source-controller.$(RUNTIME_NAMESPACE).svc.cluster.local. 57 | {{- if .Values.gotk.sourceController.extraArgs }} 58 | {{- range .Values.gotk.sourceController.extraArgs }} 59 | {{ cat "-" . }} 60 | {{- end }} 61 | {{- end }} 62 | env: 63 | - name: RUNTIME_NAMESPACE 64 | valueFrom: 65 | fieldRef: 66 | fieldPath: metadata.namespace 67 | - name: TUF_ROOT # store the Fulcio root CA file in tmp 68 | value: "/tmp/.sigstore" 69 | {{- if .Values.gotk.sourceController.proxy }} 70 | - name: HTTPS_PROXY 71 | value: {{ .Values.global.env.httpsProxy }} 72 | - name: NO_PROXY 73 | value: {{ .Values.global.env.noProxy }} 74 | {{- end }} 75 | image: {{ .Values.gotk.sourceController.image.repository }}/{{ .Values.gotk.sourceController.image.name }}:{{ .Values.gotk.sourceController.image.tag }} 76 | imagePullPolicy: {{ .Values.gotk.helmController.image.imagePullPolicy | default "IfNotPresent" }} 77 | livenessProbe: 78 | httpGet: 79 | path: /healthz 80 | port: healthz 81 | ports: 82 | - containerPort: 9090 83 | name: http 84 | protocol: TCP 85 | - containerPort: 8080 86 | name: http-prom 87 | protocol: TCP 88 | - containerPort: 9440 89 | name: healthz 90 | protocol: TCP 91 | readinessProbe: 92 | httpGet: 93 | path: / 94 | port: http 95 | securityContext: 96 | allowPrivilegeEscalation: false 97 | readOnlyRootFilesystem: true 98 | runAsNonRoot: true 99 | capabilities: 100 | drop: [ "ALL" ] 101 | seccompProfile: 102 | type: RuntimeDefault 103 | volumeMounts: 104 | - mountPath: /data 105 | name: data 106 | - mountPath: /tmp 107 | name: tmp 108 | resources: 109 | {{ toYaml .Values.gotk.sourceController.resources | indent 10 }} 110 | terminationGracePeriodSeconds: 10 111 | {{- if .Values.gotk.sourceController.imagePullSecrets.name }} 112 | imagePullSecrets: 113 | - name: {{ .Values.gotk.sourceController.imagePullSecrets.name }} 114 | {{- end }} 115 | {{- if .Values.gotk.sourceController.priorityClassName }} 116 | priorityClassName: {{ .Values.gotk.sourceController.priorityClassName }} 117 | {{- end }} 118 | {{- if .Values.gotk.sourceController.preemptionPolicy }} 119 | preemptionPolicy: {{ .Values.gotk.sourceController.preemptionPolicy }} 120 | {{- end }} 121 | {{- if .Values.gotk.sourceController.priority }} 122 | priority: {{ .Values.gotk.sourceController.priority }} 123 | {{- end }} 124 | {{- if .Values.gotk.sourceController.nodeSelector }} 125 | nodeSelector: 126 | {{ toYaml .Values.gotk.sourceController.nodeSelector | indent 8 }} 127 | {{- end }} 128 | {{- if .Values.gotk.sourceController.tolerations }} 129 | tolerations: 130 | {{ toYaml .Values.gotk.sourceController.tolerations | indent 8 }} 131 | {{- end }} 132 | {{- if .Values.gotk.sourceController.affinity }} 133 | affinity: 134 | {{ toYaml .Values.gotk.sourceController.affinity | indent 8 }} 135 | {{- end }} 136 | volumes: 137 | - emptyDir: {} 138 | name: data 139 | - emptyDir: {} 140 | name: tmp 141 | {{- end }} -------------------------------------------------------------------------------- /chart/templates/gotk/source-controller/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app.kubernetes.io/instance: {{.Release.Namespace}} 6 | app.kubernetes.io/version: latest 7 | control-plane: controller 8 | name: source-controller 9 | namespace: {{.Release.Namespace}} 10 | spec: 11 | ports: 12 | - name: http 13 | port: 80 14 | protocol: TCP 15 | targetPort: http 16 | selector: 17 | app: source-controller 18 | type: ClusterIP 19 | -------------------------------------------------------------------------------- /chart/templates/kraan/deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.kraan.kraanController.enabled }} 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: kraan-controller 6 | namespace: {{.Release.Namespace}} 7 | labels: 8 | control-plane: controller 9 | spec: 10 | selector: 11 | matchLabels: 12 | app: kraan-controller 13 | replicas: 1 14 | template: 15 | metadata: 16 | labels: 17 | app: kraan-controller 18 | {{- if .Values.global.extraLabels }} 19 | {{ toYaml .Values.global.extraLabels | indent 8 }} 20 | {{- end }} 21 | {{- if .Values.kraan.kraanController.extraLabels }} 22 | {{ toYaml .Values.kraan.kraanController.extraLabels | indent 8 }} 23 | {{- end }} 24 | annotations: 25 | {{- if .Values.global.extraPodAnnotations }} 26 | {{ toYaml .Values.global.extraPodAnnotations | indent 8 }} 27 | {{- end }} 28 | {{- if .Values.kraan.kraanController.extraPodAnnotations }} 29 | {{ toYaml .Values.kraan.kraanController.extraPodAnnotations | indent 8 }} 30 | {{- end }} 31 | {{- if .Values.kraan.kraanController.prometheus.enabled }} 32 | {{ toYaml .Values.global.prometheusAnnotations | indent 8 }} 33 | {{- end }} 34 | spec: 35 | serviceAccountName: kraan 36 | securityContext: 37 | fsGroup: 2000 38 | containers: 39 | - name: manager 40 | image: {{ .Values.kraan.kraanController.image.repository }}/{{ .Values.kraan.kraanController.image.name }}:{{ .Values.kraan.kraanController.image.tag | default .Chart.AppVersion }} 41 | imagePullPolicy: {{ .Values.kraan.kraanController.image.imagePullPolicy | default "IfNotPresent" }} 42 | securityContext: 43 | allowPrivilegeEscalation: false 44 | runAsNonRoot: {{ .Values.kraan.kraanController.runAsNonRoot }} 45 | readOnlyRootFilesystem: {{ .Values.kraan.kraanController.readOnly }} 46 | capabilities: 47 | drop: [ "ALL" ] 48 | seccompProfile: 49 | type: RuntimeDefault 50 | volumeMounts: 51 | - mountPath: /controller/data 52 | name: data 53 | - mountPath: /tmp 54 | name: tmp 55 | args: 56 | - --enable-leader-election 57 | {{ if ne (.Values.kraan.kraanController.args.logLevel | toString | atoi) 0 }} 58 | - --zap-log-level={{ .Values.kraan.kraanController.args.logLevel }} 59 | {{ end }} 60 | - --zap-encoder=json 61 | - --sync-period={{ .Values.kraan.kraanController.args.syncPeriod }} 62 | {{- if .Values.kraan.kraanController.extraArgs }} 63 | {{- range .Values.kraan.kraanController.extraArgs }} 64 | {{ cat "-" . }} 65 | {{- end }} 66 | {{- end }} 67 | env: 68 | - name: RUNTIME_NAMESPACE 69 | valueFrom: 70 | fieldRef: 71 | fieldPath: metadata.namespace 72 | - name: DATA_PATH 73 | value: /controller/data 74 | - name: KUBECACHEDIR 75 | value: /tmp/kube 76 | ports: 77 | - containerPort: 9440 78 | name: healthz 79 | protocol: TCP 80 | - containerPort: 8080 81 | name: http-prom 82 | protocol: TCP 83 | readinessProbe: 84 | httpGet: 85 | path: /readyz 86 | port: healthz 87 | initialDelaySeconds: 10 88 | periodSeconds: 10 89 | failureThreshold: 5 90 | timeoutSeconds: 15 91 | livenessProbe: 92 | failureThreshold: 3 93 | httpGet: 94 | path: /metrics 95 | port: http-prom 96 | scheme: HTTP 97 | periodSeconds: 10 98 | successThreshold: 1 99 | timeoutSeconds: 1 100 | resources: 101 | {{ toYaml .Values.kraan.kraanController.resources | indent 10 }} 102 | terminationGracePeriodSeconds: 10 103 | {{- if .Values.kraan.kraanController.imagePullSecrets.name }} 104 | imagePullSecrets: 105 | - name: {{ .Values.kraan.kraanController.imagePullSecrets.name }} 106 | {{- end }} 107 | {{- if .Values.kraan.kraanController.priorityClassName }} 108 | priorityClassName: {{ .Values.kraan.kraanController.priorityClassName }} 109 | {{- end }} 110 | {{- if .Values.kraan.kraanController.preemptionPolicy }} 111 | preemptionPolicy: {{ .Values.kraan.kraanController.preemptionPolicy }} 112 | {{- end }} 113 | {{- if .Values.kraan.kraanController.priority }} 114 | priority: {{ .Values.kraan.kraanController.priority }} 115 | {{- end }} 116 | {{- if .Values.kraan.kraanController.nodeSelector }} 117 | nodeSelector: 118 | {{ toYaml .Values.kraan.kraanController.nodeSelector | indent 8 }} 119 | {{- end }} 120 | {{- if .Values.kraan.kraanController.tolerations }} 121 | tolerations: 122 | {{ toYaml .Values.kraan.kraanController.tolerations | indent 8 }} 123 | {{- end }} 124 | {{- if .Values.kraan.kraanController.affinity }} 125 | affinity: 126 | {{ toYaml .Values.kraan.kraanController.affinity | indent 8 }} 127 | {{- end }} 128 | volumes: 129 | - name: data 130 | - name: tmp 131 | {{- end }} -------------------------------------------------------------------------------- /chart/templates/kraan/rbac.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.kraan.rbac.enabled -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: kraan 6 | namespace: {{.Release.Namespace}} 7 | --- 8 | # permissions to do leader election. 9 | apiVersion: rbac.authorization.k8s.io/v1 10 | kind: Role 11 | metadata: 12 | name: kraan-leader-election-role 13 | namespace: {{.Release.Namespace}} 14 | rules: 15 | - apiGroups: 16 | - "" 17 | resources: 18 | - configmaps 19 | verbs: 20 | - get 21 | - list 22 | - watch 23 | - create 24 | - update 25 | - patch 26 | - delete 27 | - apiGroups: 28 | - "" 29 | resources: 30 | - configmaps/status 31 | verbs: 32 | - get 33 | - update 34 | - patch 35 | - apiGroups: 36 | - "" 37 | resources: 38 | - events 39 | verbs: 40 | - create 41 | --- 42 | apiVersion: rbac.authorization.k8s.io/v1 43 | kind: RoleBinding 44 | metadata: 45 | name: kraan-leader-election-rolebinding 46 | namespace: {{.Release.Namespace}} 47 | roleRef: 48 | apiGroup: rbac.authorization.k8s.io 49 | kind: Role 50 | name: kraan-leader-election-role 51 | subjects: 52 | - kind: ServiceAccount 53 | name: kraan 54 | namespace: {{.Release.Namespace}} 55 | --- 56 | apiVersion: rbac.authorization.k8s.io/v1 57 | kind: ClusterRole 58 | metadata: 59 | name: kraan-manager 60 | rules: 61 | - apiGroups: 62 | - kraan.io 63 | resources: 64 | - addonslayers 65 | - addonslayers/status 66 | verbs: 67 | - create 68 | - delete 69 | - get 70 | - list 71 | - patch 72 | - update 73 | - watch 74 | --- 75 | apiVersion: rbac.authorization.k8s.io/v1 76 | kind: ClusterRole 77 | metadata: 78 | name: kraan-gitops-source 79 | rules: 80 | - apiGroups: 81 | - source.toolkit.fluxcd.io 82 | resources: 83 | - gitrepositories 84 | - gitrepositories/status 85 | - helmrepositories 86 | - helmrepositories/status 87 | - ocirepositories 88 | - ocirepositories/status 89 | verbs: 90 | - watch 91 | - get 92 | - list 93 | --- 94 | apiVersion: rbac.authorization.k8s.io/v1 95 | kind: ClusterRole 96 | metadata: 97 | name: kraan-helm-release 98 | rules: 99 | - apiGroups: 100 | - helm.toolkit.fluxcd.io 101 | resources: 102 | - helmreleases 103 | - helmreleases/status 104 | verbs: 105 | - create 106 | - delete 107 | - get 108 | - list 109 | - patch 110 | - update 111 | - watch 112 | --- 113 | apiVersion: rbac.authorization.k8s.io/v1 114 | kind: ClusterRoleBinding 115 | metadata: 116 | name: kraan-manager 117 | roleRef: 118 | apiGroup: rbac.authorization.k8s.io 119 | kind: ClusterRole 120 | name: kraan-manager 121 | subjects: 122 | - kind: ServiceAccount 123 | name: kraan 124 | namespace: {{.Release.Namespace}} 125 | --- 126 | apiVersion: rbac.authorization.k8s.io/v1 127 | kind: ClusterRoleBinding 128 | metadata: 129 | name: kraan-gitops-source 130 | roleRef: 131 | apiGroup: rbac.authorization.k8s.io 132 | kind: ClusterRole 133 | name: kraan-gitops-source 134 | subjects: 135 | - kind: ServiceAccount 136 | name: kraan 137 | namespace: {{.Release.Namespace}} 138 | --- 139 | apiVersion: rbac.authorization.k8s.io/v1 140 | kind: ClusterRoleBinding 141 | metadata: 142 | name: kraan-helm-release 143 | roleRef: 144 | apiGroup: rbac.authorization.k8s.io 145 | kind: ClusterRole 146 | name: kraan-helm-release 147 | subjects: 148 | - kind: ServiceAccount 149 | name: kraan 150 | namespace: {{.Release.Namespace}} 151 | --- 152 | apiVersion: rbac.authorization.k8s.io/v1 153 | kind: ClusterRoleBinding 154 | metadata: 155 | name: {{ .Values.kraan.rbac.adminClusterRoleBinding.name }} 156 | roleRef: 157 | apiGroup: rbac.authorization.k8s.io 158 | kind: ClusterRole 159 | name: {{ .Values.kraan.rbac.adminClusterRole.name }} 160 | subjects: 161 | - kind: ServiceAccount 162 | name: kraan 163 | namespace: {{.Release.Namespace}} 164 | {{ if .Values.kraan.rbac.adminClusterRole.rules }} 165 | --- 166 | apiVersion: rbac.authorization.k8s.io/v1 167 | kind: ClusterRole 168 | metadata: 169 | name: {{ .Values.kraan.rbac.adminClusterRole.name }} 170 | rules: 171 | {{ .Values.kraan.rbac.adminClusterRole.rules | toYaml }} 172 | {{- end }} 173 | {{- end }} -------------------------------------------------------------------------------- /chart/values.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | extraPodAnnotations: {} 3 | 4 | extraLabels: {} 5 | 6 | prometheusAnnotations: 7 | prometheus.io/scrape: "true" 8 | prometheus.io/port: "8080" 9 | 10 | env: 11 | httpsProxy: 12 | noProxy: 10.0.0.0/8,172.0.0.0/8 13 | 14 | kraan: 15 | crd: 16 | enabled: true 17 | rbac: 18 | enabled: true 19 | adminClusterRole: 20 | # admin ClusterRole to be used by the controller, default is cluster-admin 21 | name: "cluster-admin" 22 | # specify rules to create a ClusterRole 23 | # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#policyrule-v1-rbac-authorization-k8s-io 24 | rules: [] 25 | adminClusterRoleBinding: 26 | name: "kraan-deployer" 27 | netpolicy: 28 | enabled: true 29 | kraanController: 30 | enabled: true 31 | name: kraan-controller 32 | 33 | extraPodAnnotations: {} 34 | 35 | extraLabels: {} 36 | 37 | extraArgs: [] 38 | 39 | prometheus: 40 | enabled: true 41 | 42 | imagePullSecrets: 43 | name: 44 | 45 | image: 46 | repository: kraan 47 | name: kraan-controller 48 | tag: 49 | imagePullPolicy: 50 | 51 | args: 52 | logLevel: 0 53 | syncPeriod: 1m 54 | 55 | readOnly: true 56 | 57 | runAsNonRoot: false 58 | 59 | resources: 60 | limits: 61 | cpu: 1000m 62 | memory: 1Gi 63 | requests: 64 | cpu: 100m 65 | memory: 128Mi 66 | 67 | tolerations: 68 | [] 69 | # - key: "key" 70 | # operator: "Equal|Exists" 71 | # value: "value" 72 | # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" 73 | 74 | ## Node labels for alertmanager pod assignment 75 | ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ 76 | ## 77 | nodeSelector: {} 78 | 79 | ## Pod affinity 80 | ## 81 | affinity: {} 82 | 83 | ## https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ 84 | ## 85 | priorityClassName: {} 86 | preemptionPolicy: {} 87 | priority: {} 88 | 89 | gotk: 90 | rbac: 91 | enabled: true 92 | adminClusterRole: 93 | # admin ClusterRole to be used by the controller, default is cluster-admin 94 | name: "cluster-admin" 95 | # specify rules to create a ClusterRole 96 | # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#policyrule-v1-rbac-authorization-k8s-io 97 | rules: [] 98 | adminClusterRoleBinding: 99 | name: "cluster-reconciler" 100 | netpolicy: 101 | enabled: true 102 | 103 | sourceController: 104 | crd: 105 | enabled: true 106 | enabled: true 107 | 108 | name: source-controller 109 | 110 | extraPodAnnotations: {} 111 | 112 | extraLabels: {} 113 | 114 | extraArgs: [] 115 | 116 | prometheus: 117 | enabled: true 118 | 119 | image: 120 | repository: ghcr.io/fluxcd 121 | name: source-controller 122 | tag: v1.4.1 123 | imagePullPolicy: 124 | 125 | imagePullSecrets: 126 | name: 127 | 128 | proxy: true 129 | 130 | resources: 131 | limits: 132 | cpu: 1000m 133 | memory: 1Gi 134 | requests: 135 | cpu: 100m 136 | memory: 128Mi 137 | 138 | tolerations: 139 | [] 140 | # - key: "key" 141 | # operator: "Equal|Exists" 142 | # value: "value" 143 | # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" 144 | 145 | ## Node labels for alertmanager pod assignment 146 | ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ 147 | ## 148 | nodeSelector: {} 149 | 150 | ## Pod affinity 151 | ## 152 | affinity: {} 153 | 154 | ## https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ 155 | ## 156 | priorityClassName: {} 157 | preemptionPolicy: {} 158 | priority: {} 159 | 160 | helmController: 161 | crd: 162 | enabled: true 163 | enabled: true 164 | 165 | name: helm-controller 166 | 167 | extraPodAnnotations: {} 168 | 169 | extraLabels: {} 170 | 171 | extraArgs: [] 172 | 173 | prometheus: 174 | enabled: true 175 | 176 | imagePullSecrets: 177 | name: 178 | 179 | image: 180 | repository: ghcr.io/fluxcd 181 | name: helm-controller 182 | tag: v1.1.0 183 | imagePullPolicy: 184 | 185 | proxy: true 186 | 187 | resources: 188 | limits: 189 | cpu: 1000m 190 | memory: 1Gi 191 | requests: 192 | cpu: 100m 193 | memory: 128Mi 194 | 195 | tolerations: 196 | [] 197 | # - key: "key" 198 | # operator: "Equal|Exists" 199 | # value: "value" 200 | # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" 201 | 202 | ## Node labels for alertmanager pod assignment 203 | ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ 204 | ## 205 | nodeSelector: {} 206 | 207 | ## Pod affinity 208 | ## 209 | affinity: {} 210 | 211 | ## https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ 212 | ## 213 | priorityClassName: {} 214 | preemptionPolicy: {} 215 | priority: {} 216 | 217 | networkPolicy: 218 | enabled: true 219 | namespaceSelector: {} 220 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - bases/kraan.io_addonslayers.yaml 5 | # +kubebuilder:scaffold:crdkustomizeresource 6 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | bases: 4 | - ../crd 5 | - ../rbac 6 | - ../manager 7 | -------------------------------------------------------------------------------- /config/manager/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: kraan-controller 5 | namespace: kraan 6 | labels: 7 | control-plane: controller 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: kraan-controller 12 | replicas: 1 13 | template: 14 | metadata: 15 | labels: 16 | app: kraan-controller 17 | annotations: 18 | prometheus.io/scrape: "true" 19 | prometheus.io/port: "9090" 20 | spec: 21 | terminationGracePeriodSeconds: 10 22 | containers: 23 | - name: manager 24 | image: docker.pkg.github.com/fidelity/kraan/kraan-controller:master 25 | imagePullPolicy: Always 26 | securityContext: 27 | allowPrivilegeEscalation: false 28 | readOnlyRootFilesystem: true 29 | args: 30 | - --enable-leader-election 31 | - --zap-log-level=99 32 | - --zap-encoder=json 33 | env: 34 | - name: DATA_PATH 35 | value: /controller/data 36 | - name: RUNTIME_NAMESPACE 37 | valueFrom: 38 | fieldRef: 39 | fieldPath: metadata.namespace 40 | ports: 41 | - containerPort: 9440 42 | name: healthz 43 | protocol: TCP 44 | readinessProbe: 45 | httpGet: 46 | path: /readyz 47 | port: healthz 48 | initialDelaySeconds: 10 49 | periodSeconds: 10 50 | failureThreshold: 5 51 | timeoutSeconds: 15 52 | livenessProbe: 53 | httpGet: 54 | path: /healthz 55 | port: healthz 56 | initialDelaySeconds: 10 57 | periodSeconds: 10 58 | failureThreshold: 5 59 | timeoutSeconds: 15 60 | resources: 61 | limits: 62 | cpu: 1000m 63 | memory: 1Gi 64 | requests: 65 | cpu: 500m 66 | memory: 128Mi 67 | 68 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - deployment.yaml 5 | -------------------------------------------------------------------------------- /config/rbac/addons_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit addons. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: addons-editor-role 6 | rules: 7 | - apiGroups: 8 | - kraan.io 9 | resources: 10 | - addonslayers 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - kraan.io 21 | resources: 22 | - addons/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/addons_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view addons. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: addons-viewer-role 6 | rules: 7 | - apiGroups: 8 | - kraan.io 9 | resources: 10 | - addonslayers 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - kraan.io 17 | resources: 18 | - addons/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - role_binding.yaml 5 | - leader_election_role.yaml 6 | - leader_election_role_binding.yaml 7 | 8 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | name: manager-role 7 | rules: 8 | - apiGroups: 9 | - kraan.io 10 | resources: 11 | - addonslayers 12 | verbs: 13 | - create 14 | - delete 15 | - get 16 | - list 17 | - patch 18 | - update 19 | - watch 20 | - apiGroups: 21 | - kraan.io 22 | resources: 23 | - addonslayers/status 24 | verbs: 25 | - get 26 | - patch 27 | - update 28 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: cluster-admin 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/samples/addons.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kraan.io/v1alpha1 2 | kind: AddonsLayer 3 | metadata: 4 | name: bootstrap 5 | namespace: addons-config 6 | spec: 7 | source: 8 | name: addons 9 | namespace: gotk-system 10 | path: ./addons/bootstrap 11 | prereqs: 12 | k8sVersion: "1.16" 13 | --- 14 | apiVersion: kraan.io/v1alpha1 15 | kind: AddonsLayer 16 | metadata: 17 | name: base 18 | namespace: addons-config 19 | spec: 20 | source: 21 | name: addons 22 | namespace: gotk-system 23 | path: ./addons/base 24 | prereqs: 25 | k8sVersion: "1.16" 26 | dependsOn: 27 | - bootstrap 28 | --- 29 | apiVersion: kraan.io/v1alpha1 30 | kind: AddonsLayer 31 | metadata: 32 | name: mgmt 33 | namespace: addons-config 34 | spec: 35 | source: 36 | name: addons 37 | namespace: gotk-system 38 | path: ./addons/mgmt 39 | prereqs: 40 | k8sVersion: "1.16" 41 | dependsOn: 42 | - base 43 | --- 44 | apiVersion: kraan.io/v1alpha1 45 | kind: AddonsLayer 46 | metadata: 47 | name: apps 48 | namespace: addons-config 49 | spec: 50 | source: 51 | name: addons 52 | namespace: gotk-system 53 | path: ./addons/apps 54 | prereqs: 55 | k8sVersion: "1.16" 56 | dependsOn: 57 | - base 58 | - mgmt -------------------------------------------------------------------------------- /docs/design/README.md: -------------------------------------------------------------------------------- 1 | # Design 2 | 3 | Kraan is a kubernetes controller for processing "AddonLayer" custom resources. 4 | An AddonLayer has the following features. 5 | 6 | * Represents a collection of add-ons, managed as [HelmRelease](https://toolkit.fluxcd.io/guides/helmreleases/#define-a-helm-release) 7 | custom resources. 8 | * AddonLayer is a cluster scoped custom resource (non-namespaced). 9 | * Certify layers on specific k8s version. i.e All the add-ons in a layer is guaranteed 10 | to work without issues on a specified k8s version. Kraan will attempt to deploy 11 | a layer only when the cluster is at a specified version. 12 | * Establish dependency between layers. 13 | * Pause/Resume layer processing. 14 | 15 | ## Components 16 | 17 | Kraan is a combination of 3 tools. The installation of kraan will involve the 18 | installation of all these 3 tools. 19 | 20 | * kraan controller 21 | * [source-controller](https://github.com/fluxcd/source-controller) 22 | * [helm-controller](https://github.com/fluxcd/helm-controller) 23 | 24 | ![layers](../diagrams/addon-layer-dependencies.png) 25 | 26 | kraan controller is the main controller that is responsible to watching AddonLayer 27 | custom resources and bringing to its desired state. In attempting to do so, it 28 | relies on helm-controller to deploy the addon helm charts that are part of the layer. Helm operator watches 29 | and reconciles *HelmRelease* custom resource. Each *HelmRelease* custom resources 30 | represents a single addon. For more details on *HelmRelease*, check 31 | [here](https://toolkit.fluxcd.io/guides/helmreleases/#define-a-helm-release). 32 | source-controller is one of the components in [gitops-toolkit](https://toolkit.fluxcd.io/) which 33 | helps abstracting away git interaction from kraan. By design, an AddonLayer will 34 | point to a *directory in a git repository* which contains the list of 35 | HelmReleases. Whenever kraans' reconciliation logic is kicked off, in order to 36 | fetch the list of helm releases that are part of that addon layer, it will reach out 37 | to source-controller api to fetch repo files instead of reaching out to git directly. 38 | 39 | ***Note:*** *In future, kraan will support packaging addons in formats other 40 | than helm charts e.g kustomization* 41 | 42 | ## Spec 43 | 44 | The addons are first packaged as HelmRelease custom resources which represents 45 | a helm chart. A sample helm release custom resource is shown below. 46 | For more details on *HelmRelease*, check 47 | [here](https://toolkit.fluxcd.io/guides/helmreleases/#define-a-helm-release). 48 | 49 | ```yaml 50 | # podinfo.yaml 51 | --- 52 | apiVersion: helm.toolkit.fluxcd.io/v2 53 | kind: HelmRelease 54 | metadata: 55 | name: podinfo 56 | namespace: default 57 | spec: 58 | install: 59 | remediation: 60 | retries: -1 61 | upgrade: 62 | remediation: 63 | retries: -1 64 | chart: 65 | spec: 66 | chart: podinfo 67 | sourceRef: 68 | kind: HelmRepository 69 | name: podinfo 70 | namespace: gotk-system 71 | version: '>4.0.0' 72 | values: 73 | podinfo: 74 | service: 75 | enabled: true 76 | type: ClusterIP 77 | replicaCount: 1 78 | message: podinfo 79 | interval: 1m0s 80 | ``` 81 | 82 | ## Example 83 | 84 | A sample addon layer custom resource is shown below. 85 | 86 | ```yaml 87 | apiVersion: kraan.io/v1alpha1 88 | kind: AddonsLayer 89 | metadata: 90 | name: bootstrap 91 | 92 | spec: 93 | version: 1.0.0 94 | hold: true 95 | interval: 1m 96 | source: 97 | name: addons 98 | namespace: gotk-system 99 | path: ./addons/bootstrap 100 | prereqs: 101 | k8sVersion: "1.16" 102 | --- 103 | apiVersion: kraan.io/v1alpha1 104 | kind: AddonsLayer 105 | metadata: 106 | name: base 107 | 108 | spec: 109 | version: 1.0.1 110 | interval: 1m 111 | source: 112 | name: addons 113 | namespace: gotk-system 114 | path: ./addons/base 115 | prereqs: 116 | k8sVersion: "1.16" 117 | dependsOn: 118 | - bootstrap@1.0.0 119 | ``` 120 | 121 | ## Reconciliation Logic 122 | 123 | Kraan controller is built using the [kubebuilder](https://github.com/kubernetes-sigs/kubebuilder) project. 124 | From a controllers' perspective, reconciliation is where it attempts to bring the 125 | current state of the layer into its desired state. The reconciliation logic is 126 | kicked off whenever there is a change detected in the AddonLayer custom resource. 127 | It could be an addition, deletion or an update to the resource. Whenever an event is 128 | received for an AddonLayer, 129 | 130 | * Check whether the version of the Kubernetes API deployed on the cluster is greater than or equal to prereqs.K8sVersion. 131 | If the cluster is not yet in the expected version, return back without throwing an error. 132 | Controllers' periodic sync will trigger the reconcile next time. 133 | * execute a custom prune logic, where any addon that is present in the cluster 134 | but NOT in the repo will be removed from the cluster. Basically any addon that is 135 | not part of the current layer spec is removed from the cluster. Note that removal 136 | of an addon does not check for dependencies between layers. If the prune is 137 | not successful, then return an error, which should trigger an exponential 138 | back off. 139 | * Check if the dependent layers are succesfully deployed. This is a multi step process. 140 | 141 | * The dependent layer is identified by a combination of name and a version. 142 | i.e base@0.1.2 means that a layer with name "base" and spec.version = 0.1.2 is expected. 143 | * If the dependent layer isn't found, return nil and requeue the request 144 | to process after the configured interrval. 145 | * If the layer is found, check whether the status of the layer is set to 146 | successfully deployed by inspecting the status subresource of that custom 147 | resource. 148 | * If the layer is not successfully deployed yet, return to requeue the request 149 | after the configured interrval. 150 | * If the dependent layer is successfully deployed, then proceed to deploy this layers' 151 | addons. Return error on failure which will trigger an exponential backoff. 152 | 153 | At every stage, the status subresource on the layer custom resource will be 154 | updated with the latest status. 155 | -------------------------------------------------------------------------------- /docs/diagrams/addon-layer-dependencies: -------------------------------------------------------------------------------- 1 | 7Zltb5swEMc/DdI2qRNgSMjLNsnaF8u69UHTXjpwAa8Ojozz0H76nYMhQIjaqu2otlApMn/7bHy/O3JxLTKcb84lXSQTEQG3XDvaWGRkue7A6+GnFu5zoRe4uRBLFuWSsxOu2QMY0TPqkkWQ1QYqIbhii7oYijSFUNU0KqVY14fNBK+vuqAx7AnXIeX76k8WqSRXA9/e6RfA4qRY2bNNz5wWg42QJTQS64pExhYZSiFU3ppvhsC17wq/5HZfDvSWDyYhVU8xWAfslg/CH3Ip4Lw/+j1dZXcnBsaK8qXZsOX2OM53NpXYinXrqKByeMjwcjK5/IZO/Hr6a3xV9COHis2Wv7ovgkqKZRqB5uJg9zphCq4XNNS9a8wi1BI156Z7xjgfCi7k1pbMfP2HeqakuINKT297aQuRqoqeX+VzrEAq2BwMIqcMTUxpEHNQ8h6HFAZFNJt0dotwX++SwwmMllQSw+0ZkZqEjMu5dzGLDRO2zwhhcjCEWQHgAvj8CjjQDCp82BP42M/nMwvxauNDemRAolfi4DU4kBYObgsH760weO8MQxBCO4Zp4Hu+/UYYgq4x+EcMmAydZ0PviAExdJ4N/WN589LyZnI+uflrxc2UhkFE2uLUJZ7nv9KXJxm8uyIm+B/fF16zmHT9jt8XgyMGpNDvGoNjHznga6rzdHCcIwfk0H0+uC0cGn6FNDrV5114F3KaZSysuzI3gGjvuOtRt1S27bfsutAkcKrYqj59myvMCt8Fw4V3X8pBv+51z/vs1yfJxFKGYOzcyklXcyrbfmwqRWUMam+qLZ1y6y8AdvhQoiycPkSwQGjZZfrxSXUVBrSqI5WQsQc63Q7Qsb/Q29lu0D+z/BEqdKlElp+pagPKWZxim8NMT6WzhIWUnxpZCZ1+GWYjS+MbfTPCn/WvU3I5DST+fk4N2iquN0up/fOKT5ZerUFpTMME9dpbDkN9gb4H7Wx8tFR/RNGJSP99jm6Do9d39jiWVfILQeLt7nQ8z8zdvxjI+A8= -------------------------------------------------------------------------------- /docs/diagrams/addon-layer-dependencies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidelity/kraan/44b269fd53428dc0332bed504e2c3819a87d487b/docs/diagrams/addon-layer-dependencies.png -------------------------------------------------------------------------------- /docs/diagrams/custom-platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidelity/kraan/44b269fd53428dc0332bed504e2c3819a87d487b/docs/diagrams/custom-platform.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fidelity/kraan 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.22.4 6 | 7 | require ( 8 | github.com/fluxcd/helm-controller/api v1.1.0 9 | github.com/fluxcd/pkg/apis/meta v1.6.1 10 | github.com/fluxcd/pkg/untar v0.3.0 11 | github.com/fluxcd/source-controller/api v1.4.1 12 | github.com/go-logr/logr v1.4.2 13 | github.com/golang/mock v1.6.0 14 | github.com/google/go-cmp v0.6.0 15 | github.com/paulcarlton-ww/goutils/pkg/testutils v0.1.42 16 | github.com/pkg/errors v0.9.1 17 | github.com/prometheus/client_golang v1.20.5 18 | go.uber.org/zap v1.27.0 19 | golang.org/x/mod v0.23.0 20 | k8s.io/api v0.31.1 21 | k8s.io/apiextensions-apiserver v0.31.1 22 | k8s.io/apimachinery v0.31.1 23 | k8s.io/client-go v0.31.1 24 | sigs.k8s.io/controller-runtime v0.19.0 25 | ) 26 | 27 | require ( 28 | github.com/beorn7/perks v1.0.1 // indirect 29 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 30 | github.com/cyphar/filepath-securejoin v0.2.4 // indirect 31 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 32 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 33 | github.com/evanphx/json-patch v5.6.0+incompatible // indirect 34 | github.com/evanphx/json-patch/v5 v5.9.0 // indirect 35 | github.com/fluxcd/pkg/apis/acl v0.3.0 // indirect 36 | github.com/fluxcd/pkg/apis/kustomize v1.6.1 // indirect 37 | github.com/fluxcd/pkg/tar v0.2.0 // indirect 38 | github.com/fsnotify/fsnotify v1.7.0 // indirect 39 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 40 | github.com/go-logr/zapr v1.3.0 // indirect 41 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 42 | github.com/go-openapi/jsonreference v0.20.2 // indirect 43 | github.com/go-openapi/swag v0.22.4 // indirect 44 | github.com/gogo/protobuf v1.3.2 // indirect 45 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 46 | github.com/golang/protobuf v1.5.4 // indirect 47 | github.com/google/gnostic-models v0.6.8 // indirect 48 | github.com/google/gofuzz v1.2.0 // indirect 49 | github.com/google/uuid v1.6.0 // indirect 50 | github.com/imdario/mergo v0.3.12 // indirect 51 | github.com/josharian/intern v1.0.0 // indirect 52 | github.com/json-iterator/go v1.1.12 // indirect 53 | github.com/klauspost/compress v1.17.9 // indirect 54 | github.com/kylelemons/godebug v1.1.0 // indirect 55 | github.com/mailru/easyjson v0.7.7 // indirect 56 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 57 | github.com/modern-go/reflect2 v1.0.2 // indirect 58 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 59 | github.com/paulcarlton-ww/goutils/pkg/logging v0.0.3 // indirect 60 | github.com/prometheus/client_model v0.6.1 // indirect 61 | github.com/prometheus/common v0.55.0 // indirect 62 | github.com/prometheus/procfs v0.15.1 // indirect 63 | github.com/spf13/pflag v1.0.5 // indirect 64 | github.com/x448/float16 v0.8.4 // indirect 65 | go.uber.org/multierr v1.11.0 // indirect 66 | golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect 67 | golang.org/x/net v0.29.0 // indirect 68 | golang.org/x/oauth2 v0.21.0 // indirect 69 | golang.org/x/sys v0.25.0 // indirect 70 | golang.org/x/term v0.24.0 // indirect 71 | golang.org/x/text v0.18.0 // indirect 72 | golang.org/x/time v0.3.0 // indirect 73 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 74 | google.golang.org/protobuf v1.34.2 // indirect 75 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 76 | gopkg.in/inf.v0 v0.9.1 // indirect 77 | gopkg.in/yaml.v2 v2.4.0 // indirect 78 | gopkg.in/yaml.v3 v3.0.1 // indirect 79 | k8s.io/klog/v2 v2.130.1 // indirect 80 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect 81 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect 82 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 83 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 84 | sigs.k8s.io/yaml v1.4.0 // indirect 85 | ) 86 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | 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 | */ -------------------------------------------------------------------------------- /makefile.mk: -------------------------------------------------------------------------------- 1 | 2 | # Makes a recipe passed to a single invocation of the shell. 3 | .ONESHELL: 4 | 5 | MAKEFILE_PATH:=$(abspath $(dir $(lastword $(MAKEFILE_LIST)))) 6 | 7 | GO_SOURCES:=$(wildcard *.go) 8 | GO_TEST_SOURCES:=$(wildcard *test.go) 9 | 10 | COVERAGE_DIR:=$(CURDIR)/coverage 11 | COVERAGE_HTML_DIR:=$(COVERAGE_DIR)/html 12 | COVERAGE_ARTIFACT:=${COVERAGE_HTML_DIR}/main.html 13 | 14 | INTEGRATION_COVERAGE_DIR:=$(CURDIR)/integration-coverage 15 | INTEGRATION_COVERAGE_HTML_DIR:=$(INTEGRATION_COVERAGE_DIR)/html 16 | INTEGRATION_COVERAGE_ARTIFACT:=${INTEGRATION_COVERAGE_HTML_DIR}/main.html 17 | 18 | LINT_ARTIFACT:=._gometalinter 19 | 20 | TEST_ARTIFACT:=${COVERAGE_DIR}/coverage.out 21 | INTEGRATION_TEST_ARTIFACT:=${INTEGRATION_COVERAGE_DIR}/integration-coverage.out 22 | 23 | YELLOW:=\033[0;33m 24 | GREEN:=\033[0;32m 25 | RED:=\033[0;31m 26 | NC:=\033[0m 27 | NC_DIR:=: $(CURDIR)$(NC) 28 | # Controller Integration test setup 29 | export USE_EXISTING_CLUSTER?=true 30 | export ZAP_LOG_LEVEL?=0 31 | export KRAAN_NAMESPACE?=gotk-system 32 | export KUBECONFIG?=${HOME}/.kube/config 33 | export DATA_PATH?=$(shell mktemp -d -t kraan-XXXXXXXXXX) 34 | export SC_HOST?=localhost:8090 35 | 36 | .PHONY: all clean goimports gofmt clean-lint lint clean-test test \ 37 | clean-coverage coverage clean-integration integration integration-coverage clean-integration-coverage 38 | # Stop prints each line of the recipe. 39 | .SILENT: 40 | 41 | all: lint coverage 42 | clean: clean-lint clean-coverage clean-test 43 | 44 | integration: integration-coverage integration-test 45 | clean-integration: clean-integration-test clean-integration-coverage 46 | 47 | goimports: ${GO_SOURCES} 48 | echo "${YELLOW}Running goimports${NC_DIR}" && \ 49 | goimports -w --local github.com/fidelity/kraan $^ 50 | 51 | 52 | gofmt: ${GO_SOURCES} 53 | echo "${YELLOW}Running gofmt${NC_DIR}" && \ 54 | gofmt -w -s $^ 55 | 56 | 57 | clean-test: 58 | rm -rf $(dir ${TEST_ARTIFACT}) 59 | 60 | test: ${TEST_ARTIFACT} 61 | ${TEST_ARTIFACT}: ${GO_SOURCES} 62 | if [ -n "${GO_TEST_SOURCES}" ]; then \ 63 | { echo "${YELLOW}Running go test${NC_DIR}" && \ 64 | mkdir -p $(dir ${TEST_ARTIFACT}) && \ 65 | go test -coverprofile=$@ -v && \ 66 | echo "${GREEN}TEST PASSED${NC}"; } || \ 67 | { $(MAKE) --makefile=$(lastword $(MAKEFILE_LIST)) clean-test && \ 68 | echo "${RED}TEST FAILED${NC}" && \ 69 | exit 1; } \ 70 | fi 71 | 72 | clean-coverage: 73 | rm -rf $(dir ${COVERAGE_ARTIFACT}) 74 | 75 | coverage: ${COVERAGE_ARTIFACT} 76 | ${COVERAGE_ARTIFACT}: ${TEST_ARTIFACT} 77 | if [ -e "$<" ]; then \ 78 | echo "${YELLOW}Running go tool cover${NC_DIR}" && \ 79 | mkdir -p $(dir ${COVERAGE_ARTIFACT}) && \ 80 | go tool cover -html=$< -o $@ && \ 81 | echo "${GREEN}Generated: file://$@${NC}"; \ 82 | fi 83 | 84 | 85 | clean-integration-test: clean-integration-coverage 86 | rm -rf $(dir ${INTEGRATION_TEST_ARTIFACT}) 87 | 88 | 89 | integration-test: ${INTEGRATION_TEST_ARTIFACT} 90 | ${INTEGRATION_TEST_ARTIFACT}: ${GO_SOURCES} 91 | if [ -n "${GO_TEST_SOURCES}" ]; then \ 92 | { echo "${YELLOW}Running integration test${NC_DIR}" && \ 93 | mkdir -p $(dir ${INTEGRATION_TEST_ARTIFACT}) && \ 94 | go test -coverprofile=$@ --tags=integration && \ 95 | echo "${GREEN}TEST PASSED${NC}"; } || \ 96 | { echo "${RED}TEST FAILED${NC}" && \ 97 | exit 1; } \ 98 | fi 99 | 100 | 101 | clean-integration-coverage: 102 | rm -rf $(dir ${INTEGRATION_COVERAGE_ARTIFACT}) 103 | 104 | 105 | integration-coverage: ${INTEGRATION_COVERAGE_ARTIFACT} 106 | ${INTEGRATION_COVERAGE_ARTIFACT}: ${INTEGRATION_TEST_ARTIFACT} 107 | if [ -e "$<" ]; then \ 108 | echo "${YELLOW}Running go tool cover${NC_DIR}" && \ 109 | mkdir -p $(dir ${INTEGRATION_COVERAGE_ARTIFACT}) && \ 110 | go tool cover -html=$< -o $@ && \ 111 | echo "${GREEN}Generated: file://$@${NC}"; \ 112 | fi 113 | 114 | 115 | clean-lint: 116 | rm -f ${LINT_ARTIFACT} 117 | 118 | lint: ${LINT_ARTIFACT} 119 | ${LINT_ARTIFACT}: ${MAKEFILE_PATH}/.golangci.yml ${GO_SOURCES} 120 | echo "${YELLOW}Running go lint${NC_DIR}" && \ 121 | (cd ${MAKEFILE_PATH} && \ 122 | procs=$$(expr $$( \ 123 | (grep -c ^processor /proc/cpuinfo || \ 124 | sysctl -n hw.ncpu || \ 125 | echo 1) 2>/dev/null) '*' 2 '-' 1) && \ 126 | GOPROXY=https://proxy.golang.org,direct \ 127 | golangci-lint run \ 128 | --config ${MAKEFILE_PATH}/.golangci.yml \ 129 | --concurrency=$${procs} \ 130 | "$$(realpath --relative-to ${MAKEFILE_PATH} ${CURDIR})/.") && \ 131 | touch $@ 132 | -------------------------------------------------------------------------------- /pkg/apply/export_test.go: -------------------------------------------------------------------------------- 1 | package apply 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "unsafe" 7 | 8 | "github.com/go-logr/logr" 9 | 10 | "github.com/fidelity/kraan/pkg/internal/kubectl" 11 | ) 12 | 13 | func SetNewKubectlFunc(kubectlFunc func(logger logr.Logger) (kubectl.Kubectl, error)) { 14 | newKubectlFunc = kubectlFunc 15 | } 16 | 17 | var ( 18 | AddOwnerRefs = LayerApplier.addOwnerRefs 19 | OrphanLabel = LayerApplier.orphanLabel 20 | OrphanedLabel = orphanedLabel 21 | OwnerLabel = ownerLabel 22 | LayerOwner = layerOwner 23 | ChangeOwner = changeOwner 24 | GetTimestamp = getTimestamp 25 | LabelValue = labelValue 26 | GetObjLabel = getObjLabel 27 | ) 28 | 29 | func GetField(t *testing.T, obj interface{}, fieldName string) interface{} { 30 | o, ok := obj.(KubectlLayerApplier) 31 | if !ok { 32 | t.Fatalf("failed to cast to KubectlLayerApplier") 33 | 34 | return nil 35 | } 36 | 37 | rs := reflect.ValueOf(&o).Elem() 38 | typ := reflect.TypeOf(o) 39 | 40 | for i := 0; i < rs.NumField(); i++ { 41 | rf := rs.Field(i) 42 | fieldValue := reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem() 43 | 44 | if typ.Field(i).Name == fieldName { 45 | return fieldValue.Interface() 46 | } 47 | } 48 | t.Fatalf("failed to find field: %s in stuct config", fieldName) 49 | 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/apply/testdata/all_layers/addons.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kraan.io/v1alpha1 2 | kind: AddonsLayer 3 | metadata: 4 | name: bootstrap 5 | namespace: addons-config 6 | spec: 7 | version: 1.16-fideks-0.0.52 8 | hold: true 9 | source: 10 | name: addons 11 | namespace: gotk-system 12 | path: ./addons/bootstrap 13 | prereqs: 14 | k8sVersion: "1.16" 15 | --- 16 | apiVersion: kraan.io/v1alpha1 17 | kind: AddonsLayer 18 | metadata: 19 | name: base 20 | namespace: addons-config 21 | spec: 22 | version: 1.16-fideks-0.0.52 23 | source: 24 | name: addons 25 | namespace: gotk-system 26 | path: ./addons/base 27 | prereqs: 28 | k8sVersion: "1.16" 29 | dependsOn: 30 | - bootstrap@1.16-fideks-0.0.52 31 | --- 32 | apiVersion: kraan.io/v1alpha1 33 | kind: AddonsLayer 34 | metadata: 35 | name: mgmt 36 | namespace: addons-config 37 | spec: 38 | version: 1.16-fideks-0.0.52 39 | source: 40 | name: addons 41 | namespace: gotk-system 42 | path: ./addons/mgmt 43 | prereqs: 44 | k8sVersion: "1.16" 45 | dependsOn: 46 | - base@1.16-fideks-0.0.52 47 | --- 48 | apiVersion: kraan.io/v1alpha1 49 | kind: AddonsLayer 50 | metadata: 51 | name: apps 52 | namespace: addons-config 53 | spec: 54 | version: 1.16-fideks-0.0.52 55 | source: 56 | name: addons 57 | namespace: gotk-system 58 | path: ./addons/apps 59 | prereqs: 60 | k8sVersion: "1.16" 61 | dependsOn: 62 | - base@1.16-fideks-0.0.52 63 | - mgmt@1.16-fideks-0.0.52 -------------------------------------------------------------------------------- /pkg/apply/testdata/apply/double_release/microservice-one.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice 5 | namespace: kraan-test 6 | annotations: 7 | fluxcd.io/automated: "false" 8 | spec: 9 | interval: 1m0s 10 | chart: 11 | spec: 12 | chart: single-test 13 | sourceRef: 14 | kind: HelmRepository 15 | name: microservice 16 | namespace: simple 17 | version: '>4.0.0' 18 | releaseName: single-test 19 | test: 20 | enable: true 21 | ignoreFailures: false 22 | timeout: "5m" 23 | values: 24 | preHookBackoffLimit: 1 25 | preHookActiveDeadlineSeconds: 60 26 | preHookRestartPolicy: Never 27 | preHookDelaySeconds: 10 28 | preHookSucceed: "true" 29 | testHookBackoffLimit: 1 30 | testHookActiveDeadlineSeconds: 60 31 | testHookRestartPolicy: Never 32 | testHookDelaySeconds: 10 33 | testHookSucceed: "true" 34 | podinfo: 35 | service: 36 | type: NodePort 37 | nodePort: 31191 38 | replicaCount: 1 39 | message: Single Microservice Test 40 | -------------------------------------------------------------------------------- /pkg/apply/testdata/apply/double_release/microservice-two.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-two 5 | namespace: kraan-test 6 | annotations: 7 | fluxcd.io/automated: "false" 8 | spec: 9 | interval: 1m0s 10 | releaseName: double-test 11 | test: 12 | enable: true 13 | ignoreFailures: false 14 | timeout: "5m" 15 | chart: 16 | spec: 17 | chart: single-test 18 | sourceRef: 19 | kind: HelmRepository 20 | name: microservice 21 | namespace: simple 22 | version: '>4.0.0' 23 | values: 24 | preHookBackoffLimit: 1 25 | preHookActiveDeadlineSeconds: 60 26 | preHookRestartPolicy: Never 27 | preHookDelaySeconds: 5 28 | preHookSucceed: "true" 29 | testHookBackoffLimit: 1 30 | testHookActiveDeadlineSeconds: 60 31 | testHookRestartPolicy: Never 32 | testHookDelaySeconds: 5 33 | testHookSucceed: "true" 34 | podinfo: 35 | service: 36 | type: NodePort 37 | nodePort: 31192 38 | replicaCount: 1 39 | message: Double Microservice Test 40 | -------------------------------------------------------------------------------- /pkg/apply/testdata/apply/simple_ns/addons-repo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1 2 | kind: HelmRepository 3 | metadata: 4 | name: microservice 5 | namespace: simple 6 | spec: 7 | interval: 1m0s 8 | url: https://stefanprodan.github.io/podinfo 9 | -------------------------------------------------------------------------------- /pkg/apply/testdata/apply/single_layer/test-layer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kraan.io/v1alpha1 2 | kind: AddonsLayer 3 | metadata: 4 | name: test 5 | spec: 6 | version: test-version 7 | hold: true 8 | source: 9 | name: test 10 | namespace: kraan-test 11 | path: ./addons/bootstrap 12 | prereqs: 13 | k8sVersion: "1.16" -------------------------------------------------------------------------------- /pkg/apply/testdata/apply/single_release/microservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice 5 | namespace: kraan-test 6 | annotations: 7 | fluxcd.io/automated: "false" 8 | spec: 9 | releaseName: single-test 10 | test: 11 | enable: true 12 | ignoreFailures: false 13 | timeout: "5m" 14 | interval: 1m0s 15 | chart: 16 | spec: 17 | chart: single-test 18 | sourceRef: 19 | kind: HelmRepository 20 | name: microservice 21 | namespace: simple 22 | version: '>4.0.0' 23 | values: 24 | preHookBackoffLimit: 1 25 | preHookActiveDeadlineSeconds: 60 26 | preHookRestartPolicy: Never 27 | preHookDelaySeconds: 10 28 | preHookSucceed: "true" 29 | testHookBackoffLimit: 1 30 | testHookActiveDeadlineSeconds: 60 31 | testHookRestartPolicy: Never 32 | testHookDelaySeconds: 10 33 | testHookSucceed: "true" 34 | podinfo: 35 | service: 36 | type: NodePort 37 | nodePort: 31198 38 | replicaCount: 1 39 | message: Single Microservice Test 40 | -------------------------------------------------------------------------------- /pkg/apply/testdata/apply/test_namespace/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: kraan-created 5 | -------------------------------------------------------------------------------- /pkg/apply/testdata/crds/test_crd.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: apiextensions.k8s.io/v1beta1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: kraantests.kraan.io 6 | spec: 7 | group: kraan.io 8 | names: 9 | kind: KraanTest 10 | listKind: KraanTestList 11 | plural: kraantests 12 | shortNames: 13 | - ktest 14 | - test 15 | - kt 16 | singular: kraantest 17 | scope: Cluster 18 | subresources: 19 | status: {} 20 | validation: 21 | openAPIV3Schema: 22 | description: KraanTest is an arbitrary CRD to test applying CRDs with Kraan 23 | properties: 24 | apiVersion: 25 | description: 'APIVersion defines the versioned schema of this representation 26 | of an object. Servers should convert recognized schemas to the latest 27 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 28 | type: string 29 | kind: 30 | description: 'Kind is a string value representing the REST resource this 31 | object represents. Servers may infer this from the endpoint the client 32 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 33 | type: string 34 | metadata: 35 | type: object 36 | spec: 37 | description: KraanTest is a CRD created to test managing CRDs using Kraan. 38 | properties: 39 | ignoreFailure: 40 | description: This flag indicates if Kraan should continue to deploy when this test fails. 41 | type: boolean 42 | stuff: 43 | description: Some text data to keep in the test resource. 44 | type: string 45 | version: 46 | description: Version is the version of the test. 47 | type: string 48 | required: 49 | - stuff 50 | - version 51 | type: object 52 | status: 53 | description: KraanTestStatus defines the observed status. 54 | properties: 55 | conditions: 56 | description: Conditions history. 57 | items: 58 | description: "Condition contains condition information for a KraanTest. 59 | \n Copyright 2020 The Flux CD contributors. \n Licensed under the 60 | Apache License, Version 2.0 (the \"License\"); you may not use this 61 | file except in compliance with the License. You may obtain a copy 62 | of the License at \n http://www.apache.org/licenses/LICENSE-2.0 63 | \n Unless required by applicable law or agreed to in writing, software 64 | distributed under the License is distributed on an \"AS IS\" BASIS, 65 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 66 | implied. See the License for the specific language governing permissions 67 | and limitations under the License." 68 | properties: 69 | lastTransitionTime: 70 | description: LastTransitionTime is the timestamp corresponding 71 | to the last status change of this condition. 72 | format: date-time 73 | type: string 74 | message: 75 | description: Message is a human readable description of the details 76 | of the last transition, complementing reason. 77 | type: string 78 | reason: 79 | description: Reason is a brief machine readable explanation for 80 | the condition's last transition. 81 | type: string 82 | status: 83 | description: Status of the condition, one of ('True', 'False', 84 | 'Unknown'). 85 | type: string 86 | type: 87 | description: Type of the condition, currently ('Ready'). 88 | type: string 89 | version: 90 | description: Version, the version the status relates to. 91 | type: string 92 | required: 93 | - status 94 | - type 95 | type: object 96 | type: array 97 | state: 98 | description: State is the current state of the test. 99 | type: string 100 | version: 101 | description: Version, the version the state relates to. 102 | type: string 103 | type: object 104 | type: object 105 | version: v1alpha1 106 | versions: 107 | - name: v1alpha1 108 | served: true 109 | storage: true 110 | status: 111 | acceptedNames: 112 | kind: "" 113 | plural: "" 114 | conditions: [] 115 | storedVersions: [] 116 | -------------------------------------------------------------------------------- /pkg/apply/testdata/nolayerowner1-helmreleases.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "items": [ 4 | { 5 | "apiVersion": "helm.toolkit.fluxcd.io/v2", 6 | "kind": "HelmRelease", 7 | "metadata": { 8 | "finalizers": [ 9 | "finalizers.fluxcd.io" 10 | ], 11 | "generation": 1, 12 | "resourceVersion": "1", 13 | "name": "no-layer-owner", 14 | "namespace": "bootstrap", 15 | "ownerReferences": [ 16 | { 17 | "apiVersion": "something/else", 18 | "blockOwnerDeletion": true, 19 | "controller": true, 20 | "kind": "WhatEver", 21 | "name": "something" 22 | } 23 | ] 24 | }, 25 | "spec": { 26 | "chart": { 27 | "spec": { 28 | "chart": "podinfo", 29 | "sourceRef": { 30 | "kind": "HelmRepository", 31 | "name": "podinfo", 32 | "namespace": "gotk-system" 33 | }, 34 | "version": "\u003e4.0.0" 35 | } 36 | }, 37 | "install": { 38 | "remediation": { 39 | "retries": -1 40 | } 41 | }, 42 | "interval": "1m0s", 43 | "test": { 44 | "enable": true, 45 | "ignoreFailures": true, 46 | "timeout": "1m0s" 47 | }, 48 | "upgrade": { 49 | "remediation": { 50 | "retries": -1 51 | } 52 | }, 53 | "values": { 54 | "podinfo": { 55 | "message": "Orphan Test", 56 | "replicaCount": 1, 57 | "service": { 58 | "enabled": true, 59 | "type": "ClusterIP" 60 | } 61 | }, 62 | "preHookActiveDeadlineSeconds": 60, 63 | "preHookBackoffLimit": 1, 64 | "preHookDelaySeconds": 10, 65 | "preHookRestartPolicy": "Never", 66 | "preHookSucceed": "true", 67 | "testHookActiveDeadlineSeconds": 60, 68 | "testHookBackoffLimit": 1, 69 | "testHookDelaySeconds": 10, 70 | "testHookRestartPolicy": "Never", 71 | "testHookSucceed": "true" 72 | } 73 | }, 74 | "status": { 75 | "conditions": [ 76 | { 77 | "lastTransitionTime": "2021-01-01T00:00:00Z", 78 | "message": "Release reconciliation succeeded", 79 | "reason": "ReconciliationSucceeded", 80 | "status": "True", 81 | "type": "Ready" 82 | }, 83 | { 84 | "lastTransitionTime": "2021-01-01T00:00:00Z", 85 | "message": "Helm install succeeded", 86 | "reason": "InstallSucceeded", 87 | "status": "True", 88 | "type": "Released" 89 | } 90 | ], 91 | "helmChart": "gotk-system/bootstrap-orphan-test", 92 | "lastAppliedRevision": "5.1.4", 93 | "lastAttemptedRevision": "5.1.4", 94 | "lastAttemptedValuesChecksum": "xyz987", 95 | "lastReleaseRevision": 1, 96 | "observedGeneration": 1 97 | } 98 | } 99 | ], 100 | "kind": "List" 101 | } 102 | -------------------------------------------------------------------------------- /pkg/apply/testdata/noowner1-helmreleases.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "items": [ 4 | { 5 | "apiVersion": "helm.toolkit.fluxcd.io/v2", 6 | "kind": "HelmRelease", 7 | "metadata": { 8 | "finalizers": [ 9 | "finalizers.fluxcd.io" 10 | ], 11 | "generation": 1, 12 | "name": "no-owner", 13 | "namespace": "apps" 14 | }, 15 | "spec": { 16 | "chart": { 17 | "spec": { 18 | "chart": "podinfo", 19 | "sourceRef": { 20 | "kind": "HelmRepository", 21 | "name": "podinfo", 22 | "namespace": "gotk-system" 23 | }, 24 | "version": "\u003e4.0.0" 25 | } 26 | }, 27 | "install": { 28 | "remediation": { 29 | "retries": -1 30 | } 31 | }, 32 | "interval": "1m0s", 33 | "test": { 34 | "enable": true, 35 | "ignoreFailures": true, 36 | "timeout": "1m0s" 37 | }, 38 | "upgrade": { 39 | "remediation": { 40 | "retries": -1 41 | } 42 | }, 43 | "values": { 44 | "podinfo": { 45 | "message": "Orphan Test", 46 | "replicaCount": 1, 47 | "service": { 48 | "enabled": true, 49 | "type": "ClusterIP" 50 | } 51 | }, 52 | "preHookActiveDeadlineSeconds": 60, 53 | "preHookBackoffLimit": 1, 54 | "preHookDelaySeconds": 10, 55 | "preHookRestartPolicy": "Never", 56 | "preHookSucceed": "true", 57 | "testHookActiveDeadlineSeconds": 60, 58 | "testHookBackoffLimit": 1, 59 | "testHookDelaySeconds": 10, 60 | "testHookRestartPolicy": "Never", 61 | "testHookSucceed": "true" 62 | } 63 | }, 64 | "status": { 65 | "conditions": [ 66 | { 67 | "lastTransitionTime": "2021-01-01T00:00:00Z", 68 | "message": "Release reconciliation succeeded", 69 | "reason": "ReconciliationSucceeded", 70 | "status": "True", 71 | "type": "Ready" 72 | }, 73 | { 74 | "lastTransitionTime": "2021-01-01T00:00:00Z", 75 | "message": "Helm install succeeded", 76 | "reason": "InstallSucceeded", 77 | "status": "True", 78 | "type": "Released" 79 | } 80 | ], 81 | "helmChart": "gotk-system/apps-orphan-test", 82 | "lastAppliedRevision": "5.1.4", 83 | "lastAttemptedRevision": "5.1.4", 84 | "lastAttemptedValuesChecksum": "xyz987", 85 | "lastReleaseRevision": 1, 86 | "observedGeneration": 1 87 | } 88 | } 89 | ], 90 | "kind": "List" 91 | } 92 | -------------------------------------------------------------------------------- /pkg/apply/testdata/notorphaned1-helmreleases.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "items": [ 4 | { 5 | "apiVersion": "helm.toolkit.fluxcd.io/v2", 6 | "kind": "HelmRelease", 7 | "metadata": { 8 | "finalizers": [ 9 | "finalizers.fluxcd.io" 10 | ], 11 | "generation": 1, 12 | "resourceVersion": "1", 13 | "labels": { 14 | "kraan/layer": "bootstrap" 15 | }, 16 | "name": "orphaned1", 17 | "namespace": "bootstrap", 18 | "ownerReferences": [ 19 | { 20 | "apiVersion": "kraan.io/v1alpha1", 21 | "blockOwnerDeletion": true, 22 | "controller": true, 23 | "kind": "AddonsLayer", 24 | "name": "bootstrap" 25 | } 26 | ] 27 | }, 28 | "spec": { 29 | "chart": { 30 | "spec": { 31 | "chart": "podinfo", 32 | "sourceRef": { 33 | "kind": "HelmRepository", 34 | "name": "podinfo", 35 | "namespace": "gotk-system" 36 | }, 37 | "version": "\u003e4.0.0" 38 | } 39 | }, 40 | "install": { 41 | "remediation": { 42 | "retries": -1 43 | } 44 | }, 45 | "interval": "1m0s", 46 | "test": { 47 | "enable": true, 48 | "ignoreFailures": true, 49 | "timeout": "1m0s" 50 | }, 51 | "upgrade": { 52 | "remediation": { 53 | "retries": -1 54 | } 55 | }, 56 | "values": { 57 | "podinfo": { 58 | "message": "Orphan Test", 59 | "replicaCount": 1, 60 | "service": { 61 | "enabled": true, 62 | "type": "ClusterIP" 63 | } 64 | }, 65 | "preHookActiveDeadlineSeconds": 60, 66 | "preHookBackoffLimit": 1, 67 | "preHookDelaySeconds": 10, 68 | "preHookRestartPolicy": "Never", 69 | "preHookSucceed": "true", 70 | "testHookActiveDeadlineSeconds": 60, 71 | "testHookBackoffLimit": 1, 72 | "testHookDelaySeconds": 10, 73 | "testHookRestartPolicy": "Never", 74 | "testHookSucceed": "true" 75 | } 76 | }, 77 | "status": { 78 | "conditions": [ 79 | { 80 | "lastTransitionTime": "2021-01-01T00:00:00Z", 81 | "message": "Release reconciliation succeeded", 82 | "reason": "ReconciliationSucceeded", 83 | "status": "True", 84 | "type": "Ready" 85 | }, 86 | { 87 | "lastTransitionTime": "2021-01-01T00:00:00Z", 88 | "message": "Helm install succeeded", 89 | "reason": "InstallSucceeded", 90 | "status": "True", 91 | "type": "Released" 92 | } 93 | ], 94 | "helmChart": "gotk-system/bootstrap-orphan-test", 95 | "lastAppliedRevision": "5.1.4", 96 | "lastAttemptedRevision": "5.1.4", 97 | "lastAttemptedValuesChecksum": "xyz987", 98 | "lastReleaseRevision": 1, 99 | "observedGeneration": 1 100 | } 101 | } 102 | ], 103 | "kind": "List" 104 | } 105 | -------------------------------------------------------------------------------- /pkg/apply/testdata/orphaned-bad-ts-helmreleases.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "items": [ 4 | { 5 | "apiVersion": "helm.toolkit.fluxcd.io/v2", 6 | "kind": "HelmRelease", 7 | "metadata": { 8 | "finalizers": [ 9 | "finalizers.fluxcd.io" 10 | ], 11 | "generation": 1, 12 | "resourceVersion": "1", 13 | "labels": { 14 | "kraan/layer": "bootstrap", 15 | "orphaned": "not-a-ts" 16 | }, 17 | "name": "orphaned1", 18 | "namespace": "bootstrap", 19 | "ownerReferences": [ 20 | { 21 | "apiVersion": "kraan.io/v1alpha1", 22 | "blockOwnerDeletion": true, 23 | "controller": true, 24 | "kind": "AddonsLayer", 25 | "name": "bootstrap" 26 | } 27 | ] 28 | }, 29 | "spec": { 30 | "chart": { 31 | "spec": { 32 | "chart": "podinfo", 33 | "sourceRef": { 34 | "kind": "HelmRepository", 35 | "name": "podinfo", 36 | "namespace": "gotk-system" 37 | }, 38 | "version": "\u003e4.0.0" 39 | } 40 | }, 41 | "install": { 42 | "remediation": { 43 | "retries": -1 44 | } 45 | }, 46 | "interval": "1m0s", 47 | "test": { 48 | "enable": true, 49 | "ignoreFailures": true, 50 | "timeout": "1m0s" 51 | }, 52 | "upgrade": { 53 | "remediation": { 54 | "retries": -1 55 | } 56 | }, 57 | "values": { 58 | "podinfo": { 59 | "message": "Orphan Test", 60 | "replicaCount": 1, 61 | "service": { 62 | "enabled": true, 63 | "type": "ClusterIP" 64 | } 65 | }, 66 | "preHookActiveDeadlineSeconds": 60, 67 | "preHookBackoffLimit": 1, 68 | "preHookDelaySeconds": 10, 69 | "preHookRestartPolicy": "Never", 70 | "preHookSucceed": "true", 71 | "testHookActiveDeadlineSeconds": 60, 72 | "testHookBackoffLimit": 1, 73 | "testHookDelaySeconds": 10, 74 | "testHookRestartPolicy": "Never", 75 | "testHookSucceed": "true" 76 | } 77 | }, 78 | "status": { 79 | "conditions": [ 80 | { 81 | "lastTransitionTime": "2021-01-01T00:00:00Z", 82 | "message": "Release reconciliation succeeded", 83 | "reason": "ReconciliationSucceeded", 84 | "status": "True", 85 | "type": "Ready" 86 | }, 87 | { 88 | "lastTransitionTime": "2021-01-01T00:00:00Z", 89 | "message": "Helm install succeeded", 90 | "reason": "InstallSucceeded", 91 | "status": "True", 92 | "type": "Released" 93 | } 94 | ], 95 | "helmChart": "gotk-system/bootstrap-orphan-test", 96 | "lastAppliedRevision": "5.1.4", 97 | "lastAttemptedRevision": "5.1.4", 98 | "lastAttemptedValuesChecksum": "xyz987", 99 | "lastReleaseRevision": 1, 100 | "observedGeneration": 1 101 | } 102 | } 103 | ], 104 | "kind": "List" 105 | } 106 | -------------------------------------------------------------------------------- /pkg/apply/testdata/orphaned1-helmreleases.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "items": [ 4 | { 5 | "apiVersion": "helm.toolkit.fluxcd.io/v2", 6 | "kind": "HelmRelease", 7 | "metadata": { 8 | "finalizers": [ 9 | "finalizers.fluxcd.io" 10 | ], 11 | "generation": 1, 12 | "resourceVersion": "1", 13 | "labels": { 14 | "kraan/layer": "bootstrap", 15 | "orphaned": "2021-03-01T00:00.00-05.00" 16 | }, 17 | "name": "orphaned1", 18 | "namespace": "bootstrap", 19 | "ownerReferences": [ 20 | { 21 | "apiVersion": "kraan.io/v1alpha1", 22 | "blockOwnerDeletion": true, 23 | "controller": true, 24 | "kind": "AddonsLayer", 25 | "name": "bootstrap" 26 | } 27 | ] 28 | }, 29 | "spec": { 30 | "chart": { 31 | "spec": { 32 | "chart": "podinfo", 33 | "sourceRef": { 34 | "kind": "HelmRepository", 35 | "name": "podinfo", 36 | "namespace": "gotk-system" 37 | }, 38 | "version": "\u003e4.0.0" 39 | } 40 | }, 41 | "install": { 42 | "remediation": { 43 | "retries": -1 44 | } 45 | }, 46 | "interval": "1m0s", 47 | "test": { 48 | "enable": true, 49 | "ignoreFailures": true, 50 | "timeout": "1m0s" 51 | }, 52 | "upgrade": { 53 | "remediation": { 54 | "retries": -1 55 | } 56 | }, 57 | "values": { 58 | "podinfo": { 59 | "message": "Orphan Test", 60 | "replicaCount": 1, 61 | "service": { 62 | "enabled": true, 63 | "type": "ClusterIP" 64 | } 65 | }, 66 | "preHookActiveDeadlineSeconds": 60, 67 | "preHookBackoffLimit": 1, 68 | "preHookDelaySeconds": 10, 69 | "preHookRestartPolicy": "Never", 70 | "preHookSucceed": "true", 71 | "testHookActiveDeadlineSeconds": 60, 72 | "testHookBackoffLimit": 1, 73 | "testHookDelaySeconds": 10, 74 | "testHookRestartPolicy": "Never", 75 | "testHookSucceed": "true" 76 | } 77 | }, 78 | "status": { 79 | "conditions": [ 80 | { 81 | "lastTransitionTime": "2021-01-01T00:00:00Z", 82 | "message": "Release reconciliation succeeded", 83 | "reason": "ReconciliationSucceeded", 84 | "status": "True", 85 | "type": "Ready" 86 | }, 87 | { 88 | "lastTransitionTime": "2021-01-01T00:00:00Z", 89 | "message": "Helm install succeeded", 90 | "reason": "InstallSucceeded", 91 | "status": "True", 92 | "type": "Released" 93 | } 94 | ], 95 | "helmChart": "gotk-system/bootstrap-orphan-test", 96 | "lastAppliedRevision": "5.1.4", 97 | "lastAttemptedRevision": "5.1.4", 98 | "lastAttemptedValuesChecksum": "xyz987", 99 | "lastReleaseRevision": 1, 100 | "observedGeneration": 1 101 | } 102 | } 103 | ], 104 | "kind": "List" 105 | } 106 | -------------------------------------------------------------------------------- /pkg/apply/testdata/orphaned2-helmreleases.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "items": [ 4 | { 5 | "apiVersion": "helm.toolkit.fluxcd.io/v2", 6 | "kind": "HelmRelease", 7 | "metadata": { 8 | "finalizers": [ 9 | "finalizers.fluxcd.io" 10 | ], 11 | "generation": 1, 12 | "resourceVersion": "1", 13 | "labels": { 14 | "kraan/layer": "base", 15 | "orphaned": "2021-03-01T00:00.00-05.00" 16 | }, 17 | "name": "orphaned2", 18 | "namespace": "base", 19 | "ownerReferences": [ 20 | { 21 | "apiVersion": "kraan.io/v1alpha1", 22 | "blockOwnerDeletion": true, 23 | "controller": true, 24 | "kind": "AddonsLayer", 25 | "name": "base" 26 | } 27 | ] 28 | }, 29 | "spec": { 30 | "chart": { 31 | "spec": { 32 | "chart": "podinfo", 33 | "sourceRef": { 34 | "kind": "HelmRepository", 35 | "name": "podinfo", 36 | "namespace": "gotk-system" 37 | }, 38 | "version": "\u003e4.0.0" 39 | } 40 | }, 41 | "install": { 42 | "remediation": { 43 | "retries": -1 44 | } 45 | }, 46 | "interval": "1m0s", 47 | "test": { 48 | "enable": true, 49 | "ignoreFailures": true, 50 | "timeout": "1m0s" 51 | }, 52 | "upgrade": { 53 | "remediation": { 54 | "retries": -1 55 | } 56 | }, 57 | "values": { 58 | "podinfo": { 59 | "message": "Orphan Test", 60 | "replicaCount": 1, 61 | "service": { 62 | "enabled": true, 63 | "type": "ClusterIP" 64 | } 65 | }, 66 | "preHookActiveDeadlineSeconds": 60, 67 | "preHookBackoffLimit": 1, 68 | "preHookDelaySeconds": 10, 69 | "preHookRestartPolicy": "Never", 70 | "preHookSucceed": "true", 71 | "testHookActiveDeadlineSeconds": 60, 72 | "testHookBackoffLimit": 1, 73 | "testHookDelaySeconds": 10, 74 | "testHookRestartPolicy": "Never", 75 | "testHookSucceed": "true" 76 | } 77 | }, 78 | "status": { 79 | "conditions": [ 80 | { 81 | "lastTransitionTime": "2021-01-01T00:00:00Z", 82 | "message": "Release reconciliation succeeded", 83 | "reason": "ReconciliationSucceeded", 84 | "status": "True", 85 | "type": "Ready" 86 | }, 87 | { 88 | "lastTransitionTime": "2021-01-01T00:00:00Z", 89 | "message": "Helm install succeeded", 90 | "reason": "InstallSucceeded", 91 | "status": "True", 92 | "type": "Released" 93 | } 94 | ], 95 | "helmChart": "gotk-system/base-orphan-test", 96 | "lastAppliedRevision": "5.1.4", 97 | "lastAttemptedRevision": "5.1.4", 98 | "lastAttemptedValuesChecksum": "xyz987", 99 | "lastReleaseRevision": 1, 100 | "observedGeneration": 1 101 | } 102 | } 103 | ], 104 | "kind": "List" 105 | } 106 | -------------------------------------------------------------------------------- /pkg/apply/testdata/orphaned3-helmreleases.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "items": [ 4 | { 5 | "apiVersion": "helm.toolkit.fluxcd.io/v2", 6 | "kind": "HelmRelease", 7 | "metadata": { 8 | "finalizers": [ 9 | "finalizers.fluxcd.io" 10 | ], 11 | "generation": 1, 12 | "resourceVersion": "1", 13 | "labels": { 14 | "kraan/layer": "mgmt", 15 | "orphaned": "2021-03-01T00:00.00-05.00" 16 | }, 17 | "name": "orphaned3", 18 | "namespace": "mgmt", 19 | "ownerReferences": [ 20 | { 21 | "apiVersion": "kraan.io/v1alpha1", 22 | "blockOwnerDeletion": true, 23 | "controller": true, 24 | "kind": "AddonsLayer", 25 | "name": "mgmt" 26 | } 27 | ] 28 | }, 29 | "spec": { 30 | "chart": { 31 | "spec": { 32 | "chart": "podinfo", 33 | "sourceRef": { 34 | "kind": "HelmRepository", 35 | "name": "podinfo", 36 | "namespace": "gotk-system" 37 | }, 38 | "version": "\u003e4.0.0" 39 | } 40 | }, 41 | "install": { 42 | "remediation": { 43 | "retries": -1 44 | } 45 | }, 46 | "interval": "1m0s", 47 | "test": { 48 | "enable": true, 49 | "ignoreFailures": true, 50 | "timeout": "1m0s" 51 | }, 52 | "upgrade": { 53 | "remediation": { 54 | "retries": -1 55 | } 56 | }, 57 | "values": { 58 | "podinfo": { 59 | "message": "Orphan Test", 60 | "replicaCount": 1, 61 | "service": { 62 | "enabled": true, 63 | "type": "ClusterIP" 64 | } 65 | }, 66 | "preHookActiveDeadlineSeconds": 60, 67 | "preHookBackoffLimit": 1, 68 | "preHookDelaySeconds": 10, 69 | "preHookRestartPolicy": "Never", 70 | "preHookSucceed": "true", 71 | "testHookActiveDeadlineSeconds": 60, 72 | "testHookBackoffLimit": 1, 73 | "testHookDelaySeconds": 10, 74 | "testHookRestartPolicy": "Never", 75 | "testHookSucceed": "true" 76 | } 77 | }, 78 | "status": { 79 | "conditions": [ 80 | { 81 | "lastTransitionTime": "2021-01-01T00:00:00Z", 82 | "message": "Release reconciliation succeeded", 83 | "reason": "ReconciliationSucceeded", 84 | "status": "True", 85 | "type": "Ready" 86 | }, 87 | { 88 | "lastTransitionTime": "2021-01-01T00:00:00Z", 89 | "message": "Helm install succeeded", 90 | "reason": "InstallSucceeded", 91 | "status": "True", 92 | "type": "Released" 93 | } 94 | ], 95 | "helmChart": "gotk-system/mgmt-orphan-test", 96 | "lastAppliedRevision": "5.1.4", 97 | "lastAttemptedRevision": "5.1.4", 98 | "lastAttemptedValuesChecksum": "xyz987", 99 | "lastReleaseRevision": 1, 100 | "observedGeneration": 1 101 | } 102 | } 103 | ], 104 | "kind": "List" 105 | } 106 | -------------------------------------------------------------------------------- /pkg/common/common.go: -------------------------------------------------------------------------------- 1 | // Package for helper functions used in multiple packages 2 | package common 3 | 4 | import ( 5 | "os" 6 | ) 7 | 8 | // Following two functions copied from HelmController 9 | /* 10 | Copyright 2020 The Flux CD contributors. 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); 13 | you may not use this file except in compliance with the License. 14 | You may obtain a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, 20 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | See the License for the specific language governing permissions and 22 | limitations under the License. 23 | */ 24 | 25 | // ContainsString tests for a string in a slice of strings 26 | func ContainsString(slice []string, s string) bool { 27 | for _, item := range slice { 28 | if item == s { 29 | return true 30 | } 31 | } 32 | return false 33 | } 34 | 35 | // RemoveString removes an element from a slice of strings if present 36 | func RemoveString(slice []string, s string) (result []string) { 37 | for _, item := range slice { 38 | if item == s { 39 | continue 40 | } 41 | result = append(result, item) 42 | } 43 | return 44 | } 45 | 46 | // GetRuntimeNamespace returns the runtime namespace or empty string if environmental variable is not set. 47 | func GetRuntimeNamespace() string { 48 | return os.Getenv("RUNTIME_NAMESPACE") 49 | } 50 | 51 | // GetSourceNamespace retruns the namespace provided or the default if it is not set 52 | func GetSourceNamespace(nameSpace string) string { 53 | if len(nameSpace) > 0 { 54 | return nameSpace 55 | } 56 | return GetRuntimeNamespace() 57 | } 58 | -------------------------------------------------------------------------------- /pkg/internal/kubectl/execProvider.go: -------------------------------------------------------------------------------- 1 | // Package kubectl executes various kubectl sub-commands in a forked shell 2 | // 3 | //go:generate mockgen -destination=../mocks/kubectl/mockExecProvider.go -package=mocks -source=execProvider.go . ExecProvider 4 | package kubectl 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "os/exec" 10 | 11 | "github.com/fidelity/kraan/pkg/logging" 12 | ) 13 | 14 | // ExecProvider interface defines functions Kubectl uses to verify and execute a local command. 15 | type ExecProvider interface { 16 | FileExists(filePath string) bool 17 | FindOnPath(file string) (string, error) 18 | ExecCmd(name string, arg ...string) ([]byte, error) 19 | } 20 | 21 | // OsExecProvider implements the ExecProvider interface using the os/exec go module. 22 | type realExecProvider struct{} 23 | 24 | // NewExecProvider returns an instance of OsExecProvider to implement the ExecProvider interface. 25 | func newExecProvider() ExecProvider { 26 | return realExecProvider{} 27 | } 28 | 29 | func (p realExecProvider) FileExists(filePath string) bool { 30 | if _, err := os.Stat(filePath); os.IsNotExist(err) { 31 | return false 32 | } 33 | return true 34 | } 35 | 36 | func (p realExecProvider) FindOnPath(file string) (string, error) { 37 | return exec.LookPath(file) 38 | } 39 | 40 | func (p realExecProvider) ExecCmd(name string, arg ...string) ([]byte, error) { 41 | data, err := exec.Command(name, arg...).Output() 42 | if err != nil { 43 | exitError, ok := err.(*exec.ExitError) 44 | if ok { 45 | return nil, fmt.Errorf("%s - kubectl failed, %s, error\n%s", logging.CallerStr(logging.Me), exitError.ProcessState.String(), string(exitError.Stderr)) 46 | } 47 | return nil, err 48 | } 49 | return data, err 50 | } 51 | -------------------------------------------------------------------------------- /pkg/internal/kubectl/export_test.go: -------------------------------------------------------------------------------- 1 | //go:generate mockgen -destination=../mocks/logr/mockLogger.go -package=mocks github.com/go-logr/logr Logger 2 | package kubectl 3 | 4 | var ( 5 | KubectlCmd = kubectlCmd 6 | KustomizeCmd = kustomizeCmd 7 | NewCommandFactory = newCommandFactory 8 | GetFactoryPath = CommandFactory.getPath 9 | GetLogger = CommandFactory.getLogger 10 | GetExecProvider = CommandFactory.getExecProvider 11 | GetCommandPath = Command.getPath 12 | GetSubCmd = Command.getSubCmd 13 | GetArgs = Command.getArgs 14 | IsJSONOutput = Command.isJSONOutput 15 | AsCommandString = Command.asString 16 | ) 17 | 18 | func SetKubectlCmd(command string) { 19 | kubectlCmd = command 20 | } 21 | 22 | func SetNewExecProviderFunc(newFunc func() ExecProvider) { 23 | newExecProviderFunc = newFunc 24 | } 25 | 26 | func SetNewTempDirProviderFunc(newFunc func() (string, error)) { 27 | tempDirProviderFunc = newFunc 28 | } 29 | -------------------------------------------------------------------------------- /pkg/internal/kubectl/testdata/apply/simpleapply/simpleNamespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: simple 5 | -------------------------------------------------------------------------------- /pkg/internal/mocks/kubectl/mockExecProvider.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: execProvider.go 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | gomock "github.com/golang/mock/gomock" 9 | reflect "reflect" 10 | ) 11 | 12 | // MockExecProvider is a mock of ExecProvider interface 13 | type MockExecProvider struct { 14 | ctrl *gomock.Controller 15 | recorder *MockExecProviderMockRecorder 16 | } 17 | 18 | // MockExecProviderMockRecorder is the mock recorder for MockExecProvider 19 | type MockExecProviderMockRecorder struct { 20 | mock *MockExecProvider 21 | } 22 | 23 | // NewMockExecProvider creates a new mock instance 24 | func NewMockExecProvider(ctrl *gomock.Controller) *MockExecProvider { 25 | mock := &MockExecProvider{ctrl: ctrl} 26 | mock.recorder = &MockExecProviderMockRecorder{mock} 27 | return mock 28 | } 29 | 30 | // EXPECT returns an object that allows the caller to indicate expected use 31 | func (m *MockExecProvider) EXPECT() *MockExecProviderMockRecorder { 32 | return m.recorder 33 | } 34 | 35 | // FileExists mocks base method 36 | func (m *MockExecProvider) FileExists(filePath string) bool { 37 | m.ctrl.T.Helper() 38 | ret := m.ctrl.Call(m, "FileExists", filePath) 39 | ret0, _ := ret[0].(bool) 40 | return ret0 41 | } 42 | 43 | // FileExists indicates an expected call of FileExists 44 | func (mr *MockExecProviderMockRecorder) FileExists(filePath interface{}) *gomock.Call { 45 | mr.mock.ctrl.T.Helper() 46 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FileExists", reflect.TypeOf((*MockExecProvider)(nil).FileExists), filePath) 47 | } 48 | 49 | // FindOnPath mocks base method 50 | func (m *MockExecProvider) FindOnPath(file string) (string, error) { 51 | m.ctrl.T.Helper() 52 | ret := m.ctrl.Call(m, "FindOnPath", file) 53 | ret0, _ := ret[0].(string) 54 | ret1, _ := ret[1].(error) 55 | return ret0, ret1 56 | } 57 | 58 | // FindOnPath indicates an expected call of FindOnPath 59 | func (mr *MockExecProviderMockRecorder) FindOnPath(file interface{}) *gomock.Call { 60 | mr.mock.ctrl.T.Helper() 61 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOnPath", reflect.TypeOf((*MockExecProvider)(nil).FindOnPath), file) 62 | } 63 | 64 | // ExecCmd mocks base method 65 | func (m *MockExecProvider) ExecCmd(name string, arg ...string) ([]byte, error) { 66 | m.ctrl.T.Helper() 67 | varargs := []interface{}{name} 68 | for _, a := range arg { 69 | varargs = append(varargs, a) 70 | } 71 | ret := m.ctrl.Call(m, "ExecCmd", varargs...) 72 | ret0, _ := ret[0].([]byte) 73 | ret1, _ := ret[1].(error) 74 | return ret0, ret1 75 | } 76 | 77 | // ExecCmd indicates an expected call of ExecCmd 78 | func (mr *MockExecProviderMockRecorder) ExecCmd(name interface{}, arg ...interface{}) *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | varargs := append([]interface{}{name}, arg...) 81 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecCmd", reflect.TypeOf((*MockExecProvider)(nil).ExecCmd), varargs...) 82 | } 83 | -------------------------------------------------------------------------------- /pkg/internal/mocks/logr/mockLogger.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/go-logr/logr (interfaces: Logger) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | logr "github.com/go-logr/logr" 9 | gomock "github.com/golang/mock/gomock" 10 | reflect "reflect" 11 | ) 12 | 13 | // MockLogger is a mock of Logger interface 14 | type MockLogger struct { 15 | ctrl *gomock.Controller 16 | recorder *MockLoggerMockRecorder 17 | } 18 | 19 | // MockLoggerMockRecorder is the mock recorder for MockLogger 20 | type MockLoggerMockRecorder struct { 21 | mock *MockLogger 22 | } 23 | 24 | // NewMockLogger creates a new mock instance 25 | func NewMockLogger(ctrl *gomock.Controller) *MockLogger { 26 | mock := &MockLogger{ctrl: ctrl} 27 | mock.recorder = &MockLoggerMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use 32 | func (m *MockLogger) EXPECT() *MockLoggerMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // Enabled mocks base method 37 | func (m *MockLogger) Enabled() bool { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "Enabled") 40 | ret0, _ := ret[0].(bool) 41 | return ret0 42 | } 43 | 44 | // Enabled indicates an expected call of Enabled 45 | func (mr *MockLoggerMockRecorder) Enabled() *gomock.Call { 46 | mr.mock.ctrl.T.Helper() 47 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Enabled", reflect.TypeOf((*MockLogger)(nil).Enabled)) 48 | } 49 | 50 | // Error mocks base method 51 | func (m *MockLogger) Error(arg0 error, arg1 string, arg2 ...interface{}) { 52 | m.ctrl.T.Helper() 53 | varargs := []interface{}{arg0, arg1} 54 | for _, a := range arg2 { 55 | varargs = append(varargs, a) 56 | } 57 | m.ctrl.Call(m, "Error", varargs...) 58 | } 59 | 60 | // Error indicates an expected call of Error 61 | func (mr *MockLoggerMockRecorder) Error(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 62 | mr.mock.ctrl.T.Helper() 63 | varargs := append([]interface{}{arg0, arg1}, arg2...) 64 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), varargs...) 65 | } 66 | 67 | // Info mocks base method 68 | func (m *MockLogger) Info(arg0 string, arg1 ...interface{}) { 69 | m.ctrl.T.Helper() 70 | varargs := []interface{}{arg0} 71 | for _, a := range arg1 { 72 | varargs = append(varargs, a) 73 | } 74 | m.ctrl.Call(m, "Info", varargs...) 75 | } 76 | 77 | // Info indicates an expected call of Info 78 | func (mr *MockLoggerMockRecorder) Info(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | varargs := append([]interface{}{arg0}, arg1...) 81 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), varargs...) 82 | } 83 | 84 | // V mocks base method 85 | func (m *MockLogger) V(arg0 int) logr.Logger { 86 | m.ctrl.T.Helper() 87 | ret := m.ctrl.Call(m, "V", arg0) 88 | ret0, _ := ret[0].(logr.Logger) 89 | return ret0 90 | } 91 | 92 | // V indicates an expected call of V 93 | func (mr *MockLoggerMockRecorder) V(arg0 interface{}) *gomock.Call { 94 | mr.mock.ctrl.T.Helper() 95 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "V", reflect.TypeOf((*MockLogger)(nil).V), arg0) 96 | } 97 | 98 | // WithName mocks base method 99 | func (m *MockLogger) WithName(arg0 string) logr.Logger { 100 | m.ctrl.T.Helper() 101 | ret := m.ctrl.Call(m, "WithName", arg0) 102 | ret0, _ := ret[0].(logr.Logger) 103 | return ret0 104 | } 105 | 106 | // WithName indicates an expected call of WithName 107 | func (mr *MockLoggerMockRecorder) WithName(arg0 interface{}) *gomock.Call { 108 | mr.mock.ctrl.T.Helper() 109 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithName", reflect.TypeOf((*MockLogger)(nil).WithName), arg0) 110 | } 111 | 112 | // WithValues mocks base method 113 | func (m *MockLogger) WithValues(arg0 ...interface{}) logr.Logger { 114 | m.ctrl.T.Helper() 115 | varargs := []interface{}{} 116 | for _, a := range arg0 { 117 | varargs = append(varargs, a) 118 | } 119 | ret := m.ctrl.Call(m, "WithValues", varargs...) 120 | ret0, _ := ret[0].(logr.Logger) 121 | return ret0 122 | } 123 | 124 | // WithValues indicates an expected call of WithValues 125 | func (mr *MockLoggerMockRecorder) WithValues(arg0 ...interface{}) *gomock.Call { 126 | mr.mock.ctrl.T.Helper() 127 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithValues", reflect.TypeOf((*MockLogger)(nil).WithValues), arg0...) 128 | } 129 | -------------------------------------------------------------------------------- /pkg/internal/mocks/tarconsumer/mockTarconsumer.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: tarconsumer.go 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | context "context" 9 | gomock "github.com/golang/mock/gomock" 10 | http "net/http" 11 | reflect "reflect" 12 | ) 13 | 14 | // MockTarConsumer is a mock of TarConsumer interface 15 | type MockTarConsumer struct { 16 | ctrl *gomock.Controller 17 | recorder *MockTarConsumerMockRecorder 18 | } 19 | 20 | // MockTarConsumerMockRecorder is the mock recorder for MockTarConsumer 21 | type MockTarConsumerMockRecorder struct { 22 | mock *MockTarConsumer 23 | } 24 | 25 | // NewMockTarConsumer creates a new mock instance 26 | func NewMockTarConsumer(ctrl *gomock.Controller) *MockTarConsumer { 27 | mock := &MockTarConsumer{ctrl: ctrl} 28 | mock.recorder = &MockTarConsumerMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use 33 | func (m *MockTarConsumer) EXPECT() *MockTarConsumerMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // SetCtx mocks base method 38 | func (m *MockTarConsumer) SetCtx(ctx context.Context) { 39 | m.ctrl.T.Helper() 40 | m.ctrl.Call(m, "SetCtx", ctx) 41 | } 42 | 43 | // SetCtx indicates an expected call of SetCtx 44 | func (mr *MockTarConsumerMockRecorder) SetCtx(ctx interface{}) *gomock.Call { 45 | mr.mock.ctrl.T.Helper() 46 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCtx", reflect.TypeOf((*MockTarConsumer)(nil).SetCtx), ctx) 47 | } 48 | 49 | // SetURL mocks base method 50 | func (m *MockTarConsumer) SetURL(utl string) { 51 | m.ctrl.T.Helper() 52 | m.ctrl.Call(m, "SetURL", utl) 53 | } 54 | 55 | // SetURL indicates an expected call of SetURL 56 | func (mr *MockTarConsumerMockRecorder) SetURL(utl interface{}) *gomock.Call { 57 | mr.mock.ctrl.T.Helper() 58 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetURL", reflect.TypeOf((*MockTarConsumer)(nil).SetURL), utl) 59 | } 60 | 61 | // SetHTTPClient mocks base method 62 | func (m *MockTarConsumer) SetHTTPClient(httpClient *http.Client) { 63 | m.ctrl.T.Helper() 64 | m.ctrl.Call(m, "SetHTTPClient", httpClient) 65 | } 66 | 67 | // SetHTTPClient indicates an expected call of SetHTTPClient 68 | func (mr *MockTarConsumerMockRecorder) SetHTTPClient(httpClient interface{}) *gomock.Call { 69 | mr.mock.ctrl.T.Helper() 70 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHTTPClient", reflect.TypeOf((*MockTarConsumer)(nil).SetHTTPClient), httpClient) 71 | } 72 | 73 | // GetTar mocks base method 74 | func (m *MockTarConsumer) GetTar(ctx context.Context) ([]byte, error) { 75 | m.ctrl.T.Helper() 76 | ret := m.ctrl.Call(m, "GetTar", ctx) 77 | ret0, _ := ret[0].([]byte) 78 | ret1, _ := ret[1].(error) 79 | return ret0, ret1 80 | } 81 | 82 | // GetTar indicates an expected call of GetTar 83 | func (mr *MockTarConsumerMockRecorder) GetTar(ctx interface{}) *gomock.Call { 84 | mr.mock.ctrl.T.Helper() 85 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTar", reflect.TypeOf((*MockTarConsumer)(nil).GetTar), ctx) 86 | } 87 | -------------------------------------------------------------------------------- /pkg/internal/tarconsumer/export_test.go: -------------------------------------------------------------------------------- 1 | package tarconsumer 2 | -------------------------------------------------------------------------------- /pkg/internal/tarconsumer/tarconsumer.go: -------------------------------------------------------------------------------- 1 | // Package tarconsumer provides an interface for processing repositories. 2 | // 3 | //go:generate mockgen -destination=../mocks/tarconsumer/mockTarconsumer.go -package=mocks -source=tarconsumer.go . TarConsumer 4 | package tarconsumer 5 | 6 | import ( 7 | "bytes" 8 | "context" 9 | "fmt" 10 | "io" 11 | "net/http" 12 | 13 | "github.com/fluxcd/pkg/untar" 14 | "github.com/pkg/errors" 15 | ) 16 | 17 | type TarConsumer interface { 18 | SetCtx(ctx context.Context) 19 | SetURL(utl string) 20 | SetHTTPClient(httpClient *http.Client) 21 | GetTar(ctx context.Context) ([]byte, error) 22 | } 23 | 24 | type tarConsumerData struct { 25 | ctx context.Context 26 | url string 27 | httpClient *http.Client 28 | TarConsumer `json:"-"` 29 | } 30 | 31 | // NewTarConsumer creates a TarConsumer used to get tar file from source controller. 32 | func NewTarConsumer(ctx context.Context, client *http.Client, url string) TarConsumer { 33 | return &tarConsumerData{ 34 | ctx: ctx, 35 | url: url, 36 | httpClient: client, 37 | } 38 | } 39 | 40 | func (t *tarConsumerData) SetCtx(ctx context.Context) { 41 | t.ctx = ctx 42 | } 43 | 44 | func (t *tarConsumerData) SetHTTPClient(httpClient *http.Client) { 45 | t.httpClient = httpClient 46 | } 47 | 48 | func (t *tarConsumerData) SetURL(url string) { 49 | t.url = url 50 | } 51 | 52 | func (t *tarConsumerData) GetTar(ctx context.Context) ([]byte, error) { 53 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, t.url, nil) 54 | if err != nil { 55 | return nil, errors.Wrap(err, "failed to create HTTP new request") 56 | } 57 | 58 | resp, err := t.httpClient.Do(req) 59 | if err != nil { 60 | return nil, errors.Wrap(err, "failed to get tar data") 61 | } 62 | defer resp.Body.Close() 63 | return streamToByte(resp.Body) 64 | } 65 | 66 | // UnpackTar unpacks tar data to specified path 67 | func UnpackTar(tar []byte, path string) (err error) { 68 | r := bytes.NewReader(tar) 69 | _, err = untar.Untar(r, path) 70 | return errors.Wrap(err, "failed to unpack tar data") 71 | } 72 | 73 | func streamToByte(stream io.Reader) ([]byte, error) { 74 | buf := new(bytes.Buffer) 75 | n, err := buf.ReadFrom(stream) 76 | if err != nil { 77 | return nil, errors.Wrap(err, "failed to read tar data") 78 | } 79 | if n <= 0 { 80 | return nil, fmt.Errorf("tar data is empty") 81 | } 82 | return buf.Bytes(), nil 83 | } 84 | -------------------------------------------------------------------------------- /pkg/internal/tarconsumer/tarconsumer_test.go: -------------------------------------------------------------------------------- 1 | package tarconsumer_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "os" 8 | "reflect" 9 | "testing" 10 | 11 | "github.com/fidelity/kraan/pkg/internal/tarconsumer" 12 | "github.com/fidelity/kraan/pkg/internal/testutils" 13 | ) 14 | 15 | const ( 16 | someFiles = "testdata/addons" 17 | empty = "testdata/empty" 18 | ) 19 | 20 | func TestTarConsumer(t *testing.T) { 21 | type testsData struct { 22 | name string 23 | tarDataDir string 24 | expected []byte 25 | } 26 | 27 | tests := []testsData{{ 28 | name: "return tar file", 29 | tarDataDir: someFiles, 30 | expected: testutils.Compress(t, someFiles), 31 | }, { 32 | name: "return empty tar file", 33 | tarDataDir: empty, 34 | expected: testutils.Compress(t, empty), 35 | }, 36 | } 37 | 38 | doTest := func(t *testing.T, test testsData) { 39 | httpClient, host, teardown := testutils.StartHTTPServer(t, test.tarDataDir) 40 | defer teardown() 41 | 42 | tarConsumer := tarconsumer.NewTarConsumer(context.Background(), httpClient, 43 | fmt.Sprintf("http://%s/%s", host, test.tarDataDir)) 44 | 45 | data, err := tarConsumer.GetTar(context.Background()) 46 | if err != nil { 47 | t.Fatalf("error from %T.GetTar function %#v", tarConsumer, err) 48 | } 49 | 50 | if !bytes.Equal(data, test.expected) { 51 | t.Fatalf("data returned from %T.GetTar did not match expected data:\nWanted: %v\nGot: %v", tarConsumer, test.expected, data) 52 | } 53 | t.Logf("test: %s, successful", test.name) 54 | } 55 | 56 | for _, test := range tests { 57 | doTest(t, test) 58 | } 59 | } 60 | 61 | func TestTarUpack(t *testing.T) { 62 | type testsData struct { 63 | name string 64 | tarDataDir string 65 | expected error 66 | } 67 | 68 | tests := []testsData{{ 69 | name: "unpack tar file", 70 | tarDataDir: someFiles, 71 | expected: nil, 72 | }, { 73 | name: "unpack empty tar file", 74 | tarDataDir: empty, 75 | expected: nil, 76 | }, 77 | } 78 | 79 | doTest := func(t *testing.T, test testsData) { 80 | httpClient, host, teardown := testutils.StartHTTPServer(t, test.tarDataDir) 81 | defer teardown() 82 | 83 | tarConsumer := tarconsumer.NewTarConsumer(context.Background(), httpClient, 84 | fmt.Sprintf("http://%s/%s", host, test.tarDataDir)) 85 | 86 | data, err := tarConsumer.GetTar(context.Background()) 87 | if err != nil { 88 | t.Fatalf(err.Error()) 89 | return 90 | } 91 | 92 | dir, err := os.MkdirTemp("", "test-*") 93 | if err != nil { 94 | t.Fatalf(err.Error()) 95 | return 96 | } 97 | err = tarconsumer.UnpackTar(data, dir) 98 | if !reflect.DeepEqual(err, test.expected) { 99 | t.Fatalf("error returned from %T.UnpackTar did not match expected error:\nWanted: %#v\nGot: %#v", tarConsumer, test.expected, err) 100 | } 101 | t.Logf("test: %s, successful", test.name) 102 | } 103 | 104 | for _, test := range tests { 105 | doTest(t, test) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /pkg/internal/tarconsumer/testdata/addons/base/microservice1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.fluxcd.io/v1 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-1 5 | namespace: base 6 | annotations: 7 | fluxcd.io/automated: "false" 8 | spec: 9 | releaseName: microservice-test-1 10 | test: 11 | enable: true 12 | ignoreFailures: false 13 | timeout: 300 14 | chart: 15 | git: https://github.com/fidelity/kraan 16 | path: testdata/charts/podinfo 17 | ref: master 18 | values: 19 | preHookBackoffLimit: 1 20 | preHookActiveDeadlineSeconds: 60 21 | preHookRestartPolicy: Never 22 | preHookDelaySeconds: 10 23 | preHookSucceed: "true" 24 | testHookBackoffLimit: 1 25 | testHookActiveDeadlineSeconds: 60 26 | testHookRestartPolicy: Never 27 | testHookDelaySeconds: 10 28 | testHookSucceed: "true" 29 | podinfo: 30 | service: 31 | enabled: true 32 | type: ClusterIP 33 | replicaCount: 1 34 | message: -Microservice Test 1 35 | -------------------------------------------------------------------------------- /pkg/internal/tarconsumer/testdata/addons/bootstrap/microservice1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.fluxcd.io/v1 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-1 5 | namespace: bootstrap 6 | annotations: 7 | fluxcd.io/automated: "false" 8 | spec: 9 | releaseName: microservice-test-1 10 | test: 11 | enable: true 12 | ignoreFailures: false 13 | timeout: 300 14 | chart: 15 | git: https://github.com/fidelity/kraan 16 | path: testdata/charts/podinfo 17 | ref: master 18 | values: 19 | preHookBackoffLimit: 1 20 | preHookActiveDeadlineSeconds: 60 21 | preHookRestartPolicy: Never 22 | preHookDelaySeconds: 10 23 | preHookSucceed: "true" 24 | testHookBackoffLimit: 1 25 | testHookActiveDeadlineSeconds: 60 26 | testHookRestartPolicy: Never 27 | testHookDelaySeconds: 10 28 | testHookSucceed: "true" 29 | podinfo: 30 | service: 31 | enabled: true 32 | type: ClusterIP 33 | replicaCount: 1 34 | message: -Microservice Test 1 35 | -------------------------------------------------------------------------------- /pkg/internal/tarconsumer/testdata/addons/bootstrap/microservice2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.fluxcd.io/v1 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-2 5 | namespace: bootstrap 6 | annotations: 7 | fluxcd.io/automated: "false" 8 | spec: 9 | releaseName: microservice-test-2 10 | test: 11 | enable: true 12 | ignoreFailures: false 13 | timeout: 300 14 | chart: 15 | git: https://github.com/fidelity/kraan 16 | path: testdata/charts/podinfo 17 | ref: master 18 | values: 19 | preHookBackoffLimit: 1 20 | preHookActiveDeadlineSeconds: 60 21 | preHookRestartPolicy: Never 22 | preHookDelaySeconds: 10 23 | preHookSucceed: "true" 24 | testHookBackoffLimit: 1 25 | testHookActiveDeadlineSeconds: 60 26 | testHookRestartPolicy: Never 27 | testHookDelaySeconds: 10 28 | testHookSucceed: "true" 29 | podinfo: 30 | service: 31 | enabled: true 32 | type: ClusterIP 33 | replicaCount: 1 34 | message: Microservice Test 2 35 | -------------------------------------------------------------------------------- /pkg/internal/tarconsumer/testdata/empty/dummy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidelity/kraan/44b269fd53428dc0332bed504e2c3819a87d487b/pkg/internal/tarconsumer/testdata/empty/dummy -------------------------------------------------------------------------------- /pkg/internal/testutils/testutils.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "archive/tar" 5 | "bytes" 6 | "compress/gzip" 7 | "context" 8 | "encoding/json" 9 | "io" 10 | "net" 11 | "net/http" 12 | "net/http/httptest" 13 | "os" 14 | "path/filepath" 15 | "testing" 16 | ) 17 | 18 | // ToJSON is used to convert a data structure into JSON format. 19 | func ToJSON(data interface{}) (string, error) { 20 | jsonData, err := json.Marshal(data) 21 | if err != nil { 22 | return "", err 23 | } 24 | var prettyJSON bytes.Buffer 25 | err = json.Indent(&prettyJSON, jsonData, "", "\t") 26 | if err != nil { 27 | return "", err 28 | } 29 | return prettyJSON.String(), nil 30 | } 31 | 32 | func Compress(t *testing.T, srcDir string) []byte { 33 | var buf bytes.Buffer 34 | zr := gzip.NewWriter(&buf) 35 | tw := tar.NewWriter(zr) 36 | 37 | walkFunc := func(file string, fi os.FileInfo, err error) error { //nolint:staticcheck // ok 38 | // generate tar header 39 | var header *tar.Header 40 | header, err = tar.FileInfoHeader(fi, file) //nolint:staticcheck // ok 41 | if err != nil { 42 | return err 43 | } 44 | 45 | // must provide real name 46 | // (see https://golang.org/src/archive/tar/common.go?#L626) 47 | header.Name = filepath.ToSlash(file) 48 | 49 | // write header 50 | if err = tw.WriteHeader(header); err != nil { 51 | return err 52 | } 53 | // if not a dir, write file content 54 | if !fi.IsDir() { 55 | var data *os.File 56 | data, err = os.Open(file) 57 | if err != nil { 58 | return err 59 | } 60 | if _, err = io.Copy(tw, data); err != nil { 61 | return err 62 | } 63 | } 64 | return err 65 | } 66 | // walk through every file in the folder 67 | e := filepath.Walk(srcDir, walkFunc) 68 | if e != nil { 69 | t.Fatalf(e.Error()) 70 | return nil 71 | } 72 | // produce tar 73 | if err := tw.Close(); err != nil { 74 | t.Fatalf(err.Error()) 75 | return nil 76 | } 77 | // produce gzip 78 | if err := zr.Close(); err != nil { 79 | t.Fatalf(err.Error()) 80 | return nil 81 | } 82 | // 83 | return buf.Bytes() 84 | } 85 | 86 | func StartHTTPServer(t *testing.T, testDir string) (*http.Client, string, func()) { 87 | h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 88 | n, err := w.Write(Compress(t, testDir)) 89 | if err != nil { 90 | t.Fatalf("failed to write tar data, %s", err.Error()) 91 | } 92 | if n <= 0 { 93 | t.Fatalf("empty tar data") 94 | } 95 | }) 96 | s := httptest.NewServer(h) 97 | 98 | cli := &http.Client{ 99 | Transport: &http.Transport{ 100 | DialContext: func(_ context.Context, network, _ string) (net.Conn, error) { 101 | return net.Dial(network, s.Listener.Addr().String()) 102 | }, 103 | }, 104 | } 105 | 106 | return cli, s.Listener.Addr().String(), s.Close 107 | } 108 | -------------------------------------------------------------------------------- /pkg/layers/testdata/layersdata.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "items": [ 4 | { 5 | "apiVersion": "kraan.io/v1alpha1", 6 | "kind": "AddonsLayer", 7 | "metadata": { 8 | "name": "k8s-pending" 9 | }, 10 | "spec": { 11 | "prereqs": { 12 | "k8sVersion": "v1.18" 13 | }, 14 | "source": { 15 | "name": "global-config", 16 | "namespace": "gotk-system", 17 | "path": "./addons/apps" 18 | }, 19 | "version": "0.1.01" 20 | }, 21 | "status": { 22 | "conditions": [ 23 | { 24 | "type": "K8sVersion", 25 | "status": "True", 26 | "lastTransitionTime": null, 27 | "message": "AddonsLayer is waiting for the required K8sVersion", 28 | "reason": "K8sVersion" 29 | } 30 | ], 31 | "state": "K8sVersion", 32 | "version": "0.1.01" 33 | } 34 | }, 35 | { 36 | "apiVersion": "kraan.io/v1alpha1", 37 | "kind": "AddonsLayer", 38 | "metadata": { 39 | "name": "empty-status" 40 | }, 41 | "spec": { 42 | "prereqs": { 43 | "dependsOn": [ 44 | "test-layer2@0.1.01", 45 | "test-layer3@0.1.01" 46 | ], 47 | "k8sVersion": "v1.16.0" 48 | }, 49 | "source": { 50 | "name": "global-config", 51 | "namespace": "gotk-system", 52 | "path": "./addons/apps" 53 | }, 54 | "version": "0.1.01" 55 | } 56 | }, 57 | { 58 | "apiVersion": "kraan.io/v1alpha1", 59 | "kind": "AddonsLayer", 60 | "metadata": { 61 | "name": "hold-set" 62 | }, 63 | "spec": { 64 | "prereqs": { 65 | "dependsOn": [ 66 | "test-layer2@0.1.01", 67 | "test-layer3@0.1.01" 68 | ], 69 | "k8sVersion": "v1.16" 70 | }, 71 | "hold": true, 72 | "source": { 73 | "name": "global-config", 74 | "namespace": "gotk-system", 75 | "path": "./addons/apps" 76 | }, 77 | "version": "0.1.01" 78 | } 79 | },{ 80 | "apiVersion": "kraan.io/v1alpha1", 81 | "kind": "AddonsLayer", 82 | "metadata": { 83 | "name": "k8s-v16" 84 | }, 85 | "spec": { 86 | "prereqs": { 87 | "k8sVersion": "v1.16" 88 | }, 89 | "source": { 90 | "name": "global-config", 91 | "namespace": "gotk-system", 92 | "path": "./addons/apps" 93 | }, 94 | "version": "0.1.01" 95 | } 96 | },{ 97 | "apiVersion": "kraan.io/v1alpha1", 98 | "kind": "AddonsLayer", 99 | "metadata": { 100 | "name": "k8s-v16-2" 101 | }, 102 | "spec": { 103 | "prereqs": { 104 | "k8sVersion": "v1.16.2" 105 | }, 106 | "source": { 107 | "name": "global-config", 108 | "namespace": "gotk-system", 109 | "path": "./addons/apps" 110 | }, 111 | "version": "0.1.01" 112 | } 113 | },{ 114 | "apiVersion": "kraan.io/v1alpha1", 115 | "kind": "AddonsLayer", 116 | "metadata": { 117 | "name": "max-conditions" 118 | }, 119 | "spec": { 120 | "prereqs": { 121 | "dependsOn": [ 122 | "test-layer2@0.1.01" 123 | ], 124 | "k8sVersion": "v1.18" 125 | }, 126 | "source": { 127 | "name": "global-config", 128 | "namespace": "gotk-system", 129 | "path": "./addons/apps" 130 | }, 131 | "version": "0.1.01" 132 | }, 133 | "status": { 134 | "conditions": [ 135 | { 136 | "lastTransitionTime": null, 137 | "reason": "Deployed", 138 | "status": "True", 139 | "type": "Deployed", 140 | "message": "All HelmReleases deployed" 141 | } 142 | ], 143 | "state": "K8sVersion", 144 | "version": "0.1.01" 145 | } 146 | } 147 | ] 148 | } -------------------------------------------------------------------------------- /pkg/layers/testdata/layersdata2.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "items": [ 4 | { 5 | "apiVersion": "kraan.io/v1alpha1", 6 | "kind": "AddonsLayer", 7 | "metadata": { 8 | "name": "two-depends" 9 | }, 10 | "spec": { 11 | "prereqs": { 12 | "dependsOn": [ 13 | "no-depends@0.1.01", 14 | "one-depends@0.1.01" 15 | ] 16 | }, 17 | "source": { 18 | "name": "global-config", 19 | "namespace": "gotk-system", 20 | "path": "./addons/apps" 21 | }, 22 | "version": "0.1.01" 23 | } 24 | }, 25 | { 26 | "apiVersion": "kraan.io/v1alpha1", 27 | "kind": "AddonsLayer", 28 | "metadata": { 29 | "name": "no-depends" 30 | }, 31 | "spec": { 32 | "hold": false, 33 | "interval": "1m", 34 | "source": { 35 | "name": "global-config", 36 | "namespace": "gotk-system", 37 | "path": "./addons/bootstrap" 38 | }, 39 | "version": "0.1.01" 40 | } 41 | }, 42 | { 43 | "apiVersion": "kraan.io/v1alpha1", 44 | "kind": "AddonsLayer", 45 | "metadata": { 46 | "name": "one-depends" 47 | }, 48 | "spec": { 49 | "interval": "1m", 50 | "prereqs": { 51 | "dependsOn": [ 52 | "no-depends@0.1.01" 53 | ] 54 | }, 55 | "source": { 56 | "name": "global-config", 57 | "namespace": "gotk-system", 58 | "path": "./addons/mgmt" 59 | }, 60 | "version": "0.1.01" 61 | }, 62 | "status": { 63 | "conditions": [ 64 | { 65 | "lastTransitionTime": "2020-08-26T13:10:13Z", 66 | "reason": "AddonsLayer is Deployed", 67 | "status": "True", 68 | "type": "Deployed", 69 | "message": "All HelmReleases deployed" 70 | } 71 | ], 72 | "state": "Deployed", 73 | "version": "1.16-fideks-0.0.79" 74 | } 75 | } 76 | ], 77 | "kind": "List" 78 | } 79 | -------------------------------------------------------------------------------- /pkg/logging/logging.go: -------------------------------------------------------------------------------- 1 | // Package logging contains logging related functions used by multiple packages 2 | package logging 3 | 4 | import ( 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "path/filepath" 9 | "runtime" 10 | 11 | sourcev1 "github.com/fluxcd/source-controller/api/v1" 12 | "github.com/go-logr/logr" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | k8sruntime "k8s.io/apimachinery/pkg/runtime" 15 | 16 | kraanv1alpha1 "github.com/fidelity/kraan/api/v1alpha1" 17 | ) 18 | 19 | const ( 20 | Me = 3 21 | MyCaller = 4 22 | ) 23 | 24 | // LogJSON is used log an item in JSON format. 25 | func LogJSON(data interface{}) string { 26 | jsonData, err := json.Marshal(data) 27 | if err != nil { 28 | return err.Error() 29 | } 30 | var prettyJSON bytes.Buffer 31 | err = json.Indent(&prettyJSON, jsonData, "", " ") 32 | if err != nil { 33 | return err.Error() 34 | } 35 | return prettyJSON.String() 36 | } 37 | 38 | // GetObjNamespaceName gets object namespace and name for logging 39 | func GetObjNamespaceName(obj k8sruntime.Object) (result []interface{}) { 40 | mobj, ok := (obj).(metav1.Object) 41 | if !ok { 42 | result = append(result, "namespace", "unavailable", "name", "unavailable") 43 | return result 44 | } 45 | result = append(result, "namespace", mobj.GetNamespace(), "name", mobj.GetName()) 46 | return result 47 | } 48 | 49 | // GetLayer gets layer, returning object name for AddonsLayer.kraan.io objects or owner name for others 50 | func GetLayer(obj k8sruntime.Object) string { 51 | gvk := obj.GetObjectKind().GroupVersionKind() 52 | kind := fmt.Sprintf("%s.%s", gvk.Kind, gvk.Group) 53 | mobj, ok := (obj).(metav1.Object) 54 | if !ok { 55 | return "layer not available" 56 | } 57 | if kind == "AddonsLayer.kraan.io" { 58 | return mobj.GetName() 59 | } 60 | owners := mobj.GetOwnerReferences() 61 | for _, owner := range owners { 62 | if owner.Kind == "AddonsLayer" && owner.APIVersion == "kraan.io/v1alpha1" { 63 | return owner.Name 64 | } 65 | } 66 | return "layer not available" 67 | } 68 | 69 | // GetObjKindNamespaceName gets object kind namespace and name for logging 70 | func GetObjKindNamespaceName(obj k8sruntime.Object) (result []interface{}) { 71 | gvk := obj.GetObjectKind().GroupVersionKind() 72 | result = append(result, "kind", fmt.Sprintf("%s.%s", gvk.Kind, gvk.Group)) 73 | result = append(result, GetObjNamespaceName(obj)...) 74 | return result 75 | } 76 | 77 | // GetGitRepoInfo gets GitRepository details for logging 78 | func GetGitRepoInfo(srcRepo *sourcev1.GitRepository) (result []interface{}) { 79 | result = append(result, "kind", GitRepoSourceKind()) 80 | result = append(result, GetObjNamespaceName(srcRepo)...) 81 | result = append(result, "generation", srcRepo.Generation, "observed", srcRepo.Status.ObservedGeneration) 82 | if srcRepo.Status.Artifact != nil { 83 | result = append(result, "revision", srcRepo.Status.Artifact.Revision) 84 | } 85 | return result 86 | } 87 | 88 | // GetLayerInfo gets AddonsLayer details for logging 89 | func GetLayerInfo(src *kraanv1alpha1.AddonsLayer) (result []interface{}) { 90 | result = append(result, "kind", LayerKind()) 91 | result = append(result, GetObjNamespaceName(src)...) 92 | result = append(result, "status", src.Status.State) 93 | return result 94 | } 95 | 96 | // GitRepoSourceKind returns the gitrepository kind 97 | func GitRepoSourceKind() string { 98 | return fmt.Sprintf("%s.%s", sourcev1.GitRepositoryKind, sourcev1.GroupVersion) 99 | } 100 | 101 | // LayerKind returns the AddonsLayer kind 102 | func LayerKind() string { 103 | return fmt.Sprintf("%s.%s", kraanv1alpha1.AddonsLayerKind, kraanv1alpha1.GroupVersion) 104 | } 105 | 106 | // CallerInfo hold the function name and source file/line from which a call was made 107 | type CallerInfo struct { 108 | FunctionName string 109 | SourceFile string 110 | SourceLine int 111 | } 112 | 113 | // Callers returns an array of strings containing the function name, source filename and line 114 | // number for the caller of this function and its caller moving up the stack for as many levels as 115 | // are available or the number of levels specified by the levels parameter. 116 | // Set the short parameter to true to only return final element of Function and source file name. 117 | func Callers(levels uint, short bool) ([]CallerInfo, error) { 118 | var callers []CallerInfo 119 | if levels == 0 { 120 | return callers, nil 121 | } 122 | // we get the callers as uintptrs 123 | fpcs := make([]uintptr, levels) 124 | 125 | // skip 1 levels to get to the caller of whoever called Callers() 126 | n := runtime.Callers(1, fpcs) 127 | if n == 0 { 128 | return nil, fmt.Errorf("caller not availalble") 129 | } 130 | 131 | frames := runtime.CallersFrames(fpcs) 132 | for { 133 | frame, more := frames.Next() 134 | if frame.Line == 0 { 135 | break 136 | } 137 | funcName := frame.Function 138 | sourceFile := frame.File 139 | lineNumber := frame.Line 140 | if short { 141 | funcName = filepath.Base(funcName) 142 | sourceFile = filepath.Base(sourceFile) 143 | } 144 | caller := CallerInfo{FunctionName: funcName, SourceFile: sourceFile, SourceLine: lineNumber} 145 | callers = append(callers, caller) 146 | if !more { 147 | break 148 | } 149 | } 150 | return callers, nil 151 | } 152 | 153 | // GetCaller returns the caller of GetCaller 'skip' levels back 154 | // Set the short parameter to true to only return final element of Function and source file name 155 | func GetCaller(skip uint, short bool) CallerInfo { 156 | callers, err := Callers(skip, short) 157 | if err != nil { 158 | return CallerInfo{FunctionName: "not available", SourceFile: "not available", SourceLine: 0} 159 | } 160 | if skip == 0 { 161 | return CallerInfo{FunctionName: "not available", SourceFile: "not available", SourceLine: 0} 162 | } 163 | if int(skip) > len(callers) { 164 | return CallerInfo{FunctionName: "not available", SourceFile: "not available", SourceLine: 0} 165 | } 166 | return callers[skip-1] 167 | } 168 | 169 | // CallerStr returns the caller's function, source file and line number as a string 170 | func CallerStr(skip uint) string { 171 | callerInfo := GetCaller(skip+1, true) 172 | return fmt.Sprintf("%s - %s(%d)", callerInfo.FunctionName, callerInfo.SourceFile, callerInfo.SourceLine) 173 | } 174 | 175 | // Trace traces calls and exit for functions 176 | func TraceCall(log logr.Logger) { 177 | callerInfo := GetCaller(MyCaller, true) 178 | log.V(4).Info("Entering function", "function", callerInfo.FunctionName, "source", callerInfo.SourceFile, "line", callerInfo.SourceLine) 179 | } 180 | 181 | // Trace traces calls and exit for functions 182 | func TraceExit(log logr.Logger) { 183 | callerInfo := GetCaller(MyCaller, true) 184 | log.V(4).Info("Exiting function", "function", callerInfo.FunctionName, "source", callerInfo.SourceFile, "line", callerInfo.SourceLine) 185 | } 186 | 187 | // GetFunctionAndSource gets function name and source line for logging 188 | func GetFunctionAndSource(skip uint) (result []interface{}) { 189 | callerInfo := GetCaller(skip, true) 190 | result = append(result, "function", callerInfo.FunctionName, "source", callerInfo.SourceFile, "line", callerInfo.SourceLine) 191 | return result 192 | } 193 | -------------------------------------------------------------------------------- /pkg/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | // Package metrics contains definition of metrics and helper functions related to metrics 2 | 3 | //go:generate mockgen -destination=../mocks/metrics/mockmetrics.go -package=mocks -source=metrics.go . Metrics 4 | package metrics 5 | 6 | import ( 7 | "fmt" 8 | "time" 9 | 10 | "github.com/prometheus/client_golang/prometheus" 11 | corev1 "k8s.io/api/core/v1" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/apimachinery/pkg/runtime" 14 | ctlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics" 15 | ) 16 | 17 | type Metrics interface { 18 | Init() 19 | RecordCondition(obj runtime.Object, condition metav1.Condition, deleted bool) 20 | RecordDuration(obj runtime.Object, start time.Time) 21 | } 22 | 23 | type metricsData struct { 24 | Metrics 25 | durationHistogram *prometheus.HistogramVec 26 | conditionGauge *prometheus.GaugeVec 27 | } 28 | 29 | const ( 30 | ConditionDeleted = "Deleted" 31 | ) 32 | 33 | // NewMetrics returns a metrics interface 34 | func NewMetrics() Metrics { 35 | m := &metricsData{} 36 | m.Init() 37 | return m 38 | } 39 | 40 | // Init initialized the metrics 41 | func (m *metricsData) Init() { 42 | m.conditionGauge = prometheus.NewGaugeVec( 43 | prometheus.GaugeOpts{ 44 | Name: "reconcile_condition", 45 | Help: "The current condition status of a GitOps Toolkit resource reconciliation.", 46 | }, 47 | []string{"kind", "name", "namespace", "type", "status"}, 48 | ) 49 | ctlmetrics.Registry.MustRegister(m.conditionGauge) 50 | 51 | m.durationHistogram = prometheus.NewHistogramVec( 52 | prometheus.HistogramOpts{ 53 | Name: "reconcile_duration_seconds", 54 | Help: "The duration in seconds of a GitOps Toolkit resource reconciliation.", 55 | Buckets: prometheus.ExponentialBuckets(10e-9, 10, 10), 56 | }, 57 | []string{"kind", "name", "namespace"}, 58 | ) 59 | ctlmetrics.Registry.MustRegister(m.durationHistogram) 60 | } 61 | 62 | // RecordCondition records condition metrics 63 | func (m *metricsData) RecordCondition(obj runtime.Object, condition metav1.Condition, deleted bool) { 64 | for _, status := range []string{string(corev1.ConditionTrue), string(corev1.ConditionFalse), string(corev1.ConditionUnknown), ConditionDeleted} { 65 | var value float64 66 | if deleted { 67 | if status == ConditionDeleted { 68 | value = 1 69 | } 70 | } else { 71 | if status == string(condition.Status) { 72 | value = 1 73 | } 74 | } 75 | args := append(getObjKindNamespaceName(obj), condition.Type, status) 76 | m.conditionGauge.WithLabelValues(args...).Set(value) 77 | } 78 | } 79 | 80 | // RecordDuration records duration metrics 81 | func (m *metricsData) RecordDuration(obj runtime.Object, start time.Time) { 82 | m.durationHistogram.WithLabelValues(getObjKindNamespaceName(obj)...).Observe(time.Since(start).Seconds()) 83 | } 84 | 85 | func getObjKindNamespaceName(obj runtime.Object) []string { 86 | mobj, ok := (obj).(metav1.Object) 87 | if !ok { 88 | return []string{"unavailable", "unavailable", "unavailable"} 89 | } 90 | gvk := obj.GetObjectKind().GroupVersionKind() 91 | return []string{fmt.Sprintf("%s.%s", gvk.Kind, gvk.Group), mobj.GetNamespace(), mobj.GetName()} 92 | } 93 | -------------------------------------------------------------------------------- /pkg/mocks/client/mockClient.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: sigs.k8s.io/controller-runtime/pkg/client (interfaces: Client) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | context "context" 9 | gomock "github.com/golang/mock/gomock" 10 | runtime "k8s.io/apimachinery/pkg/runtime" 11 | types "k8s.io/apimachinery/pkg/types" 12 | reflect "reflect" 13 | client "sigs.k8s.io/controller-runtime/pkg/client" 14 | ) 15 | 16 | // MockClient is a mock of Client interface 17 | type MockClient struct { 18 | ctrl *gomock.Controller 19 | recorder *MockClientMockRecorder 20 | } 21 | 22 | // MockClientMockRecorder is the mock recorder for MockClient 23 | type MockClientMockRecorder struct { 24 | mock *MockClient 25 | } 26 | 27 | // NewMockClient creates a new mock instance 28 | func NewMockClient(ctrl *gomock.Controller) *MockClient { 29 | mock := &MockClient{ctrl: ctrl} 30 | mock.recorder = &MockClientMockRecorder{mock} 31 | return mock 32 | } 33 | 34 | // EXPECT returns an object that allows the caller to indicate expected use 35 | func (m *MockClient) EXPECT() *MockClientMockRecorder { 36 | return m.recorder 37 | } 38 | 39 | // Create mocks base method 40 | func (m *MockClient) Create(arg0 context.Context, arg1 runtime.Object, arg2 ...client.CreateOption) error { 41 | m.ctrl.T.Helper() 42 | varargs := []interface{}{arg0, arg1} 43 | for _, a := range arg2 { 44 | varargs = append(varargs, a) 45 | } 46 | ret := m.ctrl.Call(m, "Create", varargs...) 47 | ret0, _ := ret[0].(error) 48 | return ret0 49 | } 50 | 51 | // Create indicates an expected call of Create 52 | func (mr *MockClientMockRecorder) Create(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 53 | mr.mock.ctrl.T.Helper() 54 | varargs := append([]interface{}{arg0, arg1}, arg2...) 55 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockClient)(nil).Create), varargs...) 56 | } 57 | 58 | // Delete mocks base method 59 | func (m *MockClient) Delete(arg0 context.Context, arg1 runtime.Object, arg2 ...client.DeleteOption) error { 60 | m.ctrl.T.Helper() 61 | varargs := []interface{}{arg0, arg1} 62 | for _, a := range arg2 { 63 | varargs = append(varargs, a) 64 | } 65 | ret := m.ctrl.Call(m, "Delete", varargs...) 66 | ret0, _ := ret[0].(error) 67 | return ret0 68 | } 69 | 70 | // Delete indicates an expected call of Delete 71 | func (mr *MockClientMockRecorder) Delete(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 72 | mr.mock.ctrl.T.Helper() 73 | varargs := append([]interface{}{arg0, arg1}, arg2...) 74 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockClient)(nil).Delete), varargs...) 75 | } 76 | 77 | // DeleteAllOf mocks base method 78 | func (m *MockClient) DeleteAllOf(arg0 context.Context, arg1 runtime.Object, arg2 ...client.DeleteAllOfOption) error { 79 | m.ctrl.T.Helper() 80 | varargs := []interface{}{arg0, arg1} 81 | for _, a := range arg2 { 82 | varargs = append(varargs, a) 83 | } 84 | ret := m.ctrl.Call(m, "DeleteAllOf", varargs...) 85 | ret0, _ := ret[0].(error) 86 | return ret0 87 | } 88 | 89 | // DeleteAllOf indicates an expected call of DeleteAllOf 90 | func (mr *MockClientMockRecorder) DeleteAllOf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 91 | mr.mock.ctrl.T.Helper() 92 | varargs := append([]interface{}{arg0, arg1}, arg2...) 93 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllOf", reflect.TypeOf((*MockClient)(nil).DeleteAllOf), varargs...) 94 | } 95 | 96 | // Get mocks base method 97 | func (m *MockClient) Get(arg0 context.Context, arg1 types.NamespacedName, arg2 runtime.Object) error { 98 | m.ctrl.T.Helper() 99 | ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2) 100 | ret0, _ := ret[0].(error) 101 | return ret0 102 | } 103 | 104 | // Get indicates an expected call of Get 105 | func (mr *MockClientMockRecorder) Get(arg0, arg1, arg2 interface{}) *gomock.Call { 106 | mr.mock.ctrl.T.Helper() 107 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockClient)(nil).Get), arg0, arg1, arg2) 108 | } 109 | 110 | // List mocks base method 111 | func (m *MockClient) List(arg0 context.Context, arg1 runtime.Object, arg2 ...client.ListOption) error { 112 | m.ctrl.T.Helper() 113 | varargs := []interface{}{arg0, arg1} 114 | for _, a := range arg2 { 115 | varargs = append(varargs, a) 116 | } 117 | ret := m.ctrl.Call(m, "List", varargs...) 118 | ret0, _ := ret[0].(error) 119 | return ret0 120 | } 121 | 122 | // List indicates an expected call of List 123 | func (mr *MockClientMockRecorder) List(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 124 | mr.mock.ctrl.T.Helper() 125 | varargs := append([]interface{}{arg0, arg1}, arg2...) 126 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockClient)(nil).List), varargs...) 127 | } 128 | 129 | // Patch mocks base method 130 | func (m *MockClient) Patch(arg0 context.Context, arg1 runtime.Object, arg2 client.Patch, arg3 ...client.PatchOption) error { 131 | m.ctrl.T.Helper() 132 | varargs := []interface{}{arg0, arg1, arg2} 133 | for _, a := range arg3 { 134 | varargs = append(varargs, a) 135 | } 136 | ret := m.ctrl.Call(m, "Patch", varargs...) 137 | ret0, _ := ret[0].(error) 138 | return ret0 139 | } 140 | 141 | // Patch indicates an expected call of Patch 142 | func (mr *MockClientMockRecorder) Patch(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { 143 | mr.mock.ctrl.T.Helper() 144 | varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) 145 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockClient)(nil).Patch), varargs...) 146 | } 147 | 148 | // Status mocks base method 149 | func (m *MockClient) Status() client.StatusWriter { 150 | m.ctrl.T.Helper() 151 | ret := m.ctrl.Call(m, "Status") 152 | ret0, _ := ret[0].(client.StatusWriter) 153 | return ret0 154 | } 155 | 156 | // Status indicates an expected call of Status 157 | func (mr *MockClientMockRecorder) Status() *gomock.Call { 158 | mr.mock.ctrl.T.Helper() 159 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status)) 160 | } 161 | 162 | // Update mocks base method 163 | func (m *MockClient) Update(arg0 context.Context, arg1 runtime.Object, arg2 ...client.UpdateOption) error { 164 | m.ctrl.T.Helper() 165 | varargs := []interface{}{arg0, arg1} 166 | for _, a := range arg2 { 167 | varargs = append(varargs, a) 168 | } 169 | ret := m.ctrl.Call(m, "Update", varargs...) 170 | ret0, _ := ret[0].(error) 171 | return ret0 172 | } 173 | 174 | // Update indicates an expected call of Update 175 | func (mr *MockClientMockRecorder) Update(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 176 | mr.mock.ctrl.T.Helper() 177 | varargs := append([]interface{}{arg0, arg1}, arg2...) 178 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockClient)(nil).Update), varargs...) 179 | } 180 | -------------------------------------------------------------------------------- /pkg/mocks/metrics/mockmetrics.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: metrics.go 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | gomock "github.com/golang/mock/gomock" 9 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | runtime "k8s.io/apimachinery/pkg/runtime" 11 | reflect "reflect" 12 | time "time" 13 | ) 14 | 15 | // MockMetrics is a mock of Metrics interface 16 | type MockMetrics struct { 17 | ctrl *gomock.Controller 18 | recorder *MockMetricsMockRecorder 19 | } 20 | 21 | // MockMetricsMockRecorder is the mock recorder for MockMetrics 22 | type MockMetricsMockRecorder struct { 23 | mock *MockMetrics 24 | } 25 | 26 | // NewMockMetrics creates a new mock instance 27 | func NewMockMetrics(ctrl *gomock.Controller) *MockMetrics { 28 | mock := &MockMetrics{ctrl: ctrl} 29 | mock.recorder = &MockMetricsMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use 34 | func (m *MockMetrics) EXPECT() *MockMetricsMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // Init mocks base method 39 | func (m *MockMetrics) Init() { 40 | m.ctrl.T.Helper() 41 | m.ctrl.Call(m, "Init") 42 | } 43 | 44 | // Init indicates an expected call of Init 45 | func (mr *MockMetricsMockRecorder) Init() *gomock.Call { 46 | mr.mock.ctrl.T.Helper() 47 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockMetrics)(nil).Init)) 48 | } 49 | 50 | // RecordCondition mocks base method 51 | func (m *MockMetrics) RecordCondition(obj runtime.Object, condition v1.Condition, deleted bool) { 52 | m.ctrl.T.Helper() 53 | m.ctrl.Call(m, "RecordCondition", obj, condition, deleted) 54 | } 55 | 56 | // RecordCondition indicates an expected call of RecordCondition 57 | func (mr *MockMetricsMockRecorder) RecordCondition(obj, condition, deleted interface{}) *gomock.Call { 58 | mr.mock.ctrl.T.Helper() 59 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCondition", reflect.TypeOf((*MockMetrics)(nil).RecordCondition), obj, condition, deleted) 60 | } 61 | 62 | // RecordDuration mocks base method 63 | func (m *MockMetrics) RecordDuration(obj runtime.Object, start time.Time) { 64 | m.ctrl.T.Helper() 65 | m.ctrl.Call(m, "RecordDuration", obj, start) 66 | } 67 | 68 | // RecordDuration indicates an expected call of RecordDuration 69 | func (mr *MockMetricsMockRecorder) RecordDuration(obj, start interface{}) *gomock.Call { 70 | mr.mock.ctrl.T.Helper() 71 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordDuration", reflect.TypeOf((*MockMetrics)(nil).RecordDuration), obj, start) 72 | } 73 | -------------------------------------------------------------------------------- /pkg/repos/export_test.go: -------------------------------------------------------------------------------- 1 | package repos 2 | 3 | var ( 4 | FetchRepoArtifact = Repo.fetchArtifact 5 | ) 6 | -------------------------------------------------------------------------------- /pkg/repos/testdata/addons/base/microservice1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.fluxcd.io/v1 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-1 5 | namespace: base 6 | annotations: 7 | fluxcd.io/automated: "false" 8 | spec: 9 | releaseName: microservice-test-1 10 | test: 11 | enable: true 12 | ignoreFailures: false 13 | timeout: 300 14 | chart: 15 | git: https://github.com/fidelity/kraan 16 | path: testdata/charts/podinfo 17 | ref: master 18 | values: 19 | preHookBackoffLimit: 1 20 | preHookActiveDeadlineSeconds: 60 21 | preHookRestartPolicy: Never 22 | preHookDelaySeconds: 10 23 | preHookSucceed: "true" 24 | testHookBackoffLimit: 1 25 | testHookActiveDeadlineSeconds: 60 26 | testHookRestartPolicy: Never 27 | testHookDelaySeconds: 10 28 | testHookSucceed: "true" 29 | podinfo: 30 | service: 31 | enabled: true 32 | type: ClusterIP 33 | replicaCount: 1 34 | message: -Microservice Test 1 35 | -------------------------------------------------------------------------------- /pkg/repos/testdata/addons/bootstrap/microservice1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.fluxcd.io/v1 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-1 5 | namespace: bootstrap 6 | annotations: 7 | fluxcd.io/automated: "false" 8 | spec: 9 | releaseName: microservice-test-1 10 | test: 11 | enable: true 12 | ignoreFailures: false 13 | timeout: 300 14 | chart: 15 | git: https://github.com/fidelity/kraan 16 | path: testdata/charts/podinfo 17 | ref: master 18 | values: 19 | preHookBackoffLimit: 1 20 | preHookActiveDeadlineSeconds: 60 21 | preHookRestartPolicy: Never 22 | preHookDelaySeconds: 10 23 | preHookSucceed: "true" 24 | testHookBackoffLimit: 1 25 | testHookActiveDeadlineSeconds: 60 26 | testHookRestartPolicy: Never 27 | testHookDelaySeconds: 10 28 | testHookSucceed: "true" 29 | podinfo: 30 | service: 31 | enabled: true 32 | type: ClusterIP 33 | replicaCount: 1 34 | message: -Microservice Test 1 35 | -------------------------------------------------------------------------------- /pkg/repos/testdata/addons/bootstrap/microservice2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.fluxcd.io/v1 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-2 5 | namespace: bootstrap 6 | annotations: 7 | fluxcd.io/automated: "false" 8 | spec: 9 | releaseName: microservice-test-2 10 | test: 11 | enable: true 12 | ignoreFailures: false 13 | timeout: 300 14 | chart: 15 | git: https://github.com/fidelity/kraan 16 | path: testdata/charts/podinfo 17 | ref: master 18 | values: 19 | preHookBackoffLimit: 1 20 | preHookActiveDeadlineSeconds: 60 21 | preHookRestartPolicy: Never 22 | preHookDelaySeconds: 10 23 | preHookSucceed: "true" 24 | testHookBackoffLimit: 1 25 | testHookActiveDeadlineSeconds: 60 26 | testHookRestartPolicy: Never 27 | testHookDelaySeconds: 10 28 | testHookSucceed: "true" 29 | podinfo: 30 | service: 31 | enabled: true 32 | type: ClusterIP 33 | replicaCount: 1 34 | message: Microservice Test 2 35 | -------------------------------------------------------------------------------- /pkg/repos/testdata/source-repo.yaml: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "source.toolkit.fluxcd.io/v1", 3 | "kind": "GitRepository", 4 | "metadata": { 5 | "creationTimestamp": "2020-09-04T09:30:06Z", 6 | "finalizers": [ 7 | "finalizers.fluxcd.io" 8 | ], 9 | "generation": 15, 10 | "name": "addons-config", 11 | "namespace": "gotk-system", 12 | "resourceVersion": "2355954", 13 | "selfLink": "/apis/source.toolkit.fluxcd.io/v1/namespaces/gotk-system/gitrepositories/addons-config", 14 | "uid": "f261fb8e-f8bf-4f45-bc1a-45fc6e6b2e8d" 15 | }, 16 | "spec": { 17 | "interval": "1m0s", 18 | "ref": { 19 | "branch": "master" 20 | }, 21 | "secretRef": { 22 | "name": "kraan-http" 23 | }, 24 | "url": "https://github.com/fidelity/kraan.git" 25 | }, 26 | "status": { 27 | "artifact": { 28 | "lastUpdateTime": "2020-09-16T05:56:10Z", 29 | "path": "/data/gitrepository/gotk-system/addons-config/0415dab0313dfb23b7fff3fe809ac9b321067b7d.tar.gz", 30 | "revision": "master/0415dab0313dfb23b7fff3fe809ac9b321067b7d", 31 | "url": "http://source-controller.gotk-system/gitrepository/gotk-system/addons-config/0415dab0313dfb23b7fff3fe809ac9b321067b7d.tar.gz" 32 | }, 33 | "conditions": [ 34 | { 35 | "lastTransitionTime": "2020-09-16T09:48:43Z", 36 | "message": "Fetched revision: master/0415dab0313dfb23b7fff3fe809ac9b321067b7d", 37 | "reason": "GitOperationSucceed", 38 | "status": "True", 39 | "type": "Ready" 40 | } 41 | ], 42 | "url": "http://source-controller.gotk-system/gitrepository/gotk-system/addons-config/latest.tar.gz" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /project-name.mk: -------------------------------------------------------------------------------- 1 | PROJECT:=kraan-controller 2 | -------------------------------------------------------------------------------- /samples/datadog.yaml: -------------------------------------------------------------------------------- 1 | kraan: 2 | kraanController: 3 | extraPodAnnotations: 4 | ad.datadoghq.com/kraan-controller.logs: '[ { "source":"kraan-controller-source", "service":"kraan-controller-service"}]' 5 | ad.datadoghq.com/kraan-controller.check_names: '["openmetrics"]' 6 | ad.datadoghq.com/kraan-controller.init_configs: '[{}]' 7 | ad.datadoghq.com/kraan-controller.instances: '[{"prometheus_url": "http://%%host%%:%%port%%/actuator/prometheus", "namespace": "kraan-controller", "metrics": ["*"], "ssl_ca_cert": false}]' 8 | 9 | gotk: 10 | sourceController: 11 | extraPodAnnotations: 12 | ad.datadoghq.com/source-controller.logs: '[ { "source":"source-controller-source", "service":"source-controller-service"}]' 13 | ad.datadoghq.com/source-controller.check_names: '["openmetrics"]' 14 | ad.datadoghq.com/source-controller.init_configs: '[{}]' 15 | ad.datadoghq.com/source-controller.instances: '[{"prometheus_url": "http://%%host%%:%%port%%/actuator/prometheus", "namespace": "source-controller", "metrics": ["*"], "ssl_ca_cert": false}]' 16 | 17 | helmController: 18 | extraPodAnnotations: 19 | ad.datadoghq.com/helm-controller.logs: '[ { "source":"helm-controller-source", "service":"helm-controller-service"}]' 20 | ad.datadoghq.com/helm-controller.check_names: '["openmetrics"]' 21 | ad.datadoghq.com/helm-controller.init_configs: '[{}]' 22 | ad.datadoghq.com/helm-controller.instances: '[{"prometheus_url": "http://%%host%%:%%port%%/actuator/prometheus", "namespace": "helm-controller", "metrics": ["*"], "ssl_ca_cert": false}]' 23 | 24 | 25 | -------------------------------------------------------------------------------- /samples/local.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | extraPodAnnotations: 3 | created_by: "userid" 4 | org: "acme" 5 | app_id: "4321" 6 | env: "dev" 7 | csp: "aks" 8 | index_id: "1234" 9 | -------------------------------------------------------------------------------- /samples/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | app.kubernetes.io/instance: gotk-system 6 | app.kubernetes.io/version: latest 7 | name: gotk-system 8 | -------------------------------------------------------------------------------- /samples/proxy.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | env: 3 | httpsProxy: https://proxy.corp.com:8080 4 | -------------------------------------------------------------------------------- /scripts/kind.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit 3 | # desired cluster name; default is "kind" 4 | KIND_CLUSTER_NAME="${KIND_CLUSTER_NAME:-k8s}" 5 | KIND_PORT=${KIND_CLUSTER_PORT:-16443} 6 | cat <&1 | tee ${DATA_PATH}/kraan.log | grep "^{" | jq -r" 53 | read -p "press enter to continue" 54 | 55 | kubectl -n gotk-system port-forward svc/source-controller 8090:80 & 56 | export SC_HOST=localhost:8090 57 | export RUNTIME_NAMESPACE=gotk-system 58 | kraan-controller -zap-encoder=json ${log_level} 2>&1 | tee ${work_dir}/kraan.log | grep "^{" | jq -r 59 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export VERSION=test 3 | export REPO=kraan 4 | make clean-build 5 | make build 6 | kind create cluster 7 | kind load docker-image kraan/kraan-controller:test 8 | helm upgrade -i kraan chart --set kraan.kraanController.image.tag=test 9 | 10 | kubectl apply -f ../gitops-examples/helm-repos 11 | kubectl apply -f ../gitops-examples/git-repos 12 | kubectl apply -f ../gitops-examples/kraan 13 | -------------------------------------------------------------------------------- /scripts/validate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | BASE_REF=origin/${GITHUB_BASE_REF:-"master"} 4 | echo "Comparing against $BASE_REF" 5 | git diff --name-status "$BASE_REF" | tee 6 | if [[ ${CHART_APP_VERSION} != "${VERSION}" ]]; then 7 | echo "❌ chart/Chart.yaml appVersion '${CHART_APP_VERSION}' must match ./VERSION '${VERSION}'" 8 | false 9 | fi 10 | 11 | if [[ ${CHART_APP_VERSION} != "${CHART_VERSION}" ]]; then 12 | echo "❌ chart/Chart.yaml appVersion '${CHART_APP_VERSION}' must match chart/Chart.yaml Version '${CHART_VERSION}'" 13 | false 14 | fi 15 | 16 | if ! git diff "$BASE_REF" -- chart/Chart.yaml | grep '+version:'; then 17 | if git diff --name-status "$BASE_REF" | grep -E 'chart/values.yaml|chart/templates'; then 18 | echo "❌ Chart.yaml version must be changed whenever chart changes occur" 19 | false 20 | fi 21 | if git diff "$BASE_REF" -- chart/Chart.yaml | grep '+appVersion:'; then 22 | echo "❌ Chart.yaml appVersion was changed but version was not" 23 | false 24 | fi 25 | fi 26 | 27 | if ! git diff "$BASE_REF" -- VERSION | grep '+'; then 28 | if git diff --name-status "$BASE_REF" | grep -v "_test.go" | grep -E '\.go$|go\.mod|go\.sum|Dockerfile'; then 29 | echo "❌ VERSION was not changed even though relevant code changes occured" 30 | false 31 | fi 32 | fi 33 | 34 | echo "✅ Passed version validations" 35 | -------------------------------------------------------------------------------- /testdata/addons-orphan/addons-repo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1 2 | kind: HelmRepository 3 | metadata: 4 | name: podinfo 5 | namespace: gotk-system 6 | spec: 7 | interval: 1m0s 8 | url: https://stefanprodan.github.io/podinfo 9 | -------------------------------------------------------------------------------- /testdata/addons-orphan/addons-source.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1 2 | kind: GitRepository 3 | metadata: 4 | name: addons-config 5 | namespace: gotk-system 6 | spec: 7 | interval: 1m0s 8 | ref: 9 | branch: master 10 | url: https://github.com/fidelity/kraan.git 11 | -------------------------------------------------------------------------------- /testdata/addons-orphan/addons.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kraan.io/v1alpha1 2 | kind: AddonsLayer 3 | metadata: 4 | name: bootstrap 5 | spec: 6 | version: 0.1.01 7 | hold: false 8 | interval: 1m 9 | timeout: 30s 10 | source: 11 | name: addons-config 12 | namespace: gotk-system 13 | path: ./testdata/addons-orphan/bootstrap 14 | prereqs: 15 | k8sVersion: "v1.16" 16 | --- 17 | apiVersion: kraan.io/v1alpha1 18 | kind: AddonsLayer 19 | metadata: 20 | name: base 21 | finalizers: 22 | - finalizers.kraan.io 23 | spec: 24 | version: 0.1.01 25 | interval: 1m 26 | timeout: 30s 27 | source: 28 | kind: gitrepositories.source.toolkit.fluxcd.io 29 | name: addons-config 30 | namespace: gotk-system 31 | path: ./testdata/addons-orphan/base 32 | prereqs: 33 | k8sVersion: "v1.16" 34 | dependsOn: 35 | - bootstrap@0.1.01 36 | --- 37 | apiVersion: kraan.io/v1alpha1 38 | kind: AddonsLayer 39 | metadata: 40 | name: mgmt 41 | spec: 42 | interval: 1m 43 | timeout: 30s 44 | version: 0.1.01 45 | source: 46 | name: addons-config 47 | namespace: gotk-system 48 | path: ./testdata/addons-orphan/mgmt 49 | prereqs: 50 | k8sVersion: "v1.16" 51 | dependsOn: 52 | - base@0.1.01 53 | --- 54 | apiVersion: kraan.io/v1alpha1 55 | kind: AddonsLayer 56 | metadata: 57 | name: apps 58 | spec: 59 | version: 0.1.01 60 | interval: 1m 61 | timeout: 30s 62 | source: 63 | name: addons-config 64 | namespace: gotk-system 65 | path: ./testdata/addons-orphan/apps 66 | prereqs: 67 | k8sVersion: "v1.16" 68 | dependsOn: 69 | - base@0.1.01 70 | - mgmt@0.1.01 -------------------------------------------------------------------------------- /testdata/addons-orphan/apps/microservice1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-1 5 | namespace: apps 6 | spec: 7 | install: 8 | remediation: 9 | retries: -1 10 | upgrade: 11 | remediation: 12 | retries: -1 13 | chart: 14 | spec: 15 | chart: podinfo 16 | sourceRef: 17 | kind: HelmRepository 18 | name: podinfo 19 | namespace: gotk-system 20 | version: '>4.0.0' 21 | test: 22 | enable: false 23 | ignoreFailures: false 24 | timeout: "2m" 25 | values: 26 | preHookBackoffLimit: 1 27 | preHookActiveDeadlineSeconds: 60 28 | preHookRestartPolicy: Never 29 | preHookDelaySeconds: 10 30 | preHookSucceed: "true" 31 | testHookBackoffLimit: 1 32 | testHookActiveDeadlineSeconds: 60 33 | testHookRestartPolicy: Never 34 | testHookDelaySeconds: 10 35 | testHookSucceed: "true" 36 | podinfo: 37 | service: 38 | enabled: true 39 | type: ClusterIP 40 | replicaCount: 1 41 | message: -Microservice Test 1 42 | interval: 1m0s 43 | -------------------------------------------------------------------------------- /testdata/addons-orphan/apps/microservice2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-2 5 | namespace: bootstrap 6 | spec: 7 | install: 8 | remediation: 9 | retries: -1 10 | upgrade: 11 | remediation: 12 | retries: -1 13 | chart: 14 | spec: 15 | chart: podinfo 16 | sourceRef: 17 | kind: HelmRepository 18 | name: podinfo 19 | namespace: gotk-system 20 | version: '>4.0.0' 21 | test: 22 | enable: true 23 | ignoreFailures: true 24 | timeout: "1m" 25 | values: 26 | preHookBackoffLimit: 1 27 | preHookActiveDeadlineSeconds: 60 28 | preHookRestartPolicy: Never 29 | preHookDelaySeconds: 10 30 | preHookSucceed: "true" 31 | testHookBackoffLimit: 1 32 | testHookActiveDeadlineSeconds: 60 33 | testHookRestartPolicy: Never 34 | testHookDelaySeconds: 10 35 | testHookSucceed: "true" 36 | podinfo: 37 | service: 38 | enabled: true 39 | type: ClusterIP 40 | replicaCount: 1 41 | message: -Microservice Test 2 42 | interval: 1m0s 43 | -------------------------------------------------------------------------------- /testdata/addons-orphan/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: base 4 | resources: 5 | - microservice1.yaml 6 | -------------------------------------------------------------------------------- /testdata/addons-orphan/base/microservice1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-1 5 | spec: 6 | install: 7 | remediation: 8 | retries: -1 9 | upgrade: 10 | remediation: 11 | retries: -1 12 | chart: 13 | spec: 14 | chart: podinfo 15 | sourceRef: 16 | kind: HelmRepository 17 | name: podinfo 18 | namespace: gotk-system 19 | version: '>4.0.0' 20 | test: 21 | enable: false 22 | ignoreFailures: false 23 | timeout: "1m" 24 | values: 25 | preHookBackoffLimit: 1 26 | preHookActiveDeadlineSeconds: 60 27 | preHookRestartPolicy: Never 28 | preHookDelaySeconds: 10 29 | preHookSucceed: "true" 30 | testHookBackoffLimit: 1 31 | testHookActiveDeadlineSeconds: 60 32 | testHookRestartPolicy: Never 33 | testHookDelaySeconds: 10 34 | testHookSucceed: "true" 35 | podinfo: 36 | service: 37 | enabled: true 38 | type: ClusterIP 39 | replicaCount: 1 40 | message: -Microservice Test 1 41 | interval: 1m0s 42 | 43 | -------------------------------------------------------------------------------- /testdata/addons-orphan/bootstrap/integration-test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2 2 | kind: HelmRelease 3 | metadata: 4 | name: integration-test 5 | namespace: bootstrap 6 | annotations: 7 | kraan.updateVersion: "true" 8 | spec: 9 | install: 10 | remediation: 11 | retries: -1 12 | upgrade: 13 | remediation: 14 | retries: -1 15 | dependsOn: 16 | - name: microservice-1 17 | namespace: bootstrap 18 | - name: microservice-2 19 | namespace: bootstrap 20 | chart: 21 | spec: 22 | chart: podinfo 23 | sourceRef: 24 | kind: HelmRepository 25 | name: podinfo 26 | namespace: gotk-system 27 | version: '>4.0.0' 28 | test: 29 | enable: true 30 | ignoreFailures: true 31 | timeout: "1m" 32 | values: 33 | preHookBackoffLimit: 1 34 | preHookActiveDeadlineSeconds: 60 35 | preHookRestartPolicy: Never 36 | preHookDelaySeconds: 10 37 | preHookSucceed: "true" 38 | testHookBackoffLimit: 1 39 | testHookActiveDeadlineSeconds: 60 40 | testHookRestartPolicy: Never 41 | testHookDelaySeconds: 10 42 | testHookSucceed: "true" 43 | podinfo: 44 | service: 45 | enabled: true 46 | type: ClusterIP 47 | replicaCount: 1 48 | message: Integration Test 49 | interval: 1m0s 50 | -------------------------------------------------------------------------------- /testdata/addons-orphan/bootstrap/microservice1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-1 5 | namespace: bootstrap 6 | spec: 7 | install: 8 | remediation: 9 | retries: -1 10 | upgrade: 11 | remediation: 12 | retries: -1 13 | chart: 14 | spec: 15 | chart: podinfo 16 | sourceRef: 17 | kind: HelmRepository 18 | name: podinfo 19 | namespace: gotk-system 20 | version: '>4.0.0' 21 | test: 22 | enable: true 23 | ignoreFailures: true 24 | timeout: "2m" 25 | values: 26 | preHookBackoffLimit: 1 27 | preHookActiveDeadlineSeconds: 60 28 | preHookRestartPolicy: Never 29 | preHookDelaySeconds: 10 30 | preHookSucceed: "true" 31 | testHookBackoffLimit: 1 32 | testHookActiveDeadlineSeconds: 60 33 | testHookRestartPolicy: Never 34 | testHookDelaySeconds: 10 35 | testHookSucceed: "true" 36 | podinfo: 37 | service: 38 | enabled: true 39 | type: ClusterIP 40 | replicaCount: 1 41 | message: -Microservice Test 1 42 | interval: 1m0s 43 | -------------------------------------------------------------------------------- /testdata/addons-orphan/mgmt/microservice1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-1 5 | namespace: mgmt 6 | spec: 7 | install: 8 | remediation: 9 | retries: -1 10 | upgrade: 11 | remediation: 12 | retries: -1 13 | chart: 14 | spec: 15 | chart: podinfo 16 | sourceRef: 17 | kind: HelmRepository 18 | name: podinfo 19 | namespace: gotk-system 20 | version: '>4.0.0' 21 | test: 22 | enable: false 23 | ignoreFailures: false 24 | timeout: "1m" 25 | values: 26 | preHookBackoffLimit: 1 27 | preHookActiveDeadlineSeconds: 60 28 | preHookRestartPolicy: Never 29 | preHookDelaySeconds: 10 30 | preHookSucceed: "true" 31 | testHookBackoffLimit: 1 32 | testHookActiveDeadlineSeconds: 60 33 | testHookRestartPolicy: Never 34 | testHookDelaySeconds: 10 35 | testHookSucceed: "true" 36 | podinfo: 37 | service: 38 | enabled: true 39 | type: ClusterIP 40 | replicaCount: 1 41 | message: -Microservice Test 1 42 | interval: 1m0s 43 | 44 | -------------------------------------------------------------------------------- /testdata/addons/addons-repo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1 2 | kind: HelmRepository 3 | metadata: 4 | name: podinfo 5 | namespace: gotk-system 6 | spec: 7 | interval: 1m0s 8 | url: https://stefanprodan.github.io/podinfo 9 | -------------------------------------------------------------------------------- /testdata/addons/addons-source.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1 2 | kind: GitRepository 3 | metadata: 4 | name: addons-config 5 | namespace: gotk-system 6 | spec: 7 | interval: 1m0s 8 | ref: 9 | branch: master 10 | url: https://github.com/fidelity/kraan.git 11 | -------------------------------------------------------------------------------- /testdata/addons/addons.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kraan.io/v1alpha1 2 | kind: AddonsLayer 3 | metadata: 4 | name: bootstrap 5 | spec: 6 | version: 0.1.01 7 | hold: false 8 | interval: 1m 9 | timeout: 30s 10 | source: 11 | name: addons-config 12 | namespace: gotk-system 13 | path: ./testdata/addons/bootstrap 14 | prereqs: 15 | k8sVersion: "v1.16" 16 | --- 17 | apiVersion: kraan.io/v1alpha1 18 | kind: AddonsLayer 19 | metadata: 20 | name: base 21 | finalizers: 22 | - finalizers.kraan.io 23 | spec: 24 | version: 0.1.01 25 | interval: 1m 26 | timeout: 30s 27 | source: 28 | kind: gitrepositories.source.toolkit.fluxcd.io 29 | name: addons-config 30 | namespace: gotk-system 31 | path: ./testdata/addons/base 32 | prereqs: 33 | k8sVersion: "v1.16" 34 | dependsOn: 35 | - bootstrap@0.1.01 36 | --- 37 | apiVersion: kraan.io/v1alpha1 38 | kind: AddonsLayer 39 | metadata: 40 | name: mgmt 41 | spec: 42 | interval: 1m 43 | timeout: 30s 44 | version: 0.1.01 45 | source: 46 | name: addons-config 47 | namespace: gotk-system 48 | path: ./testdata/addons/mgmt 49 | prereqs: 50 | k8sVersion: "v1.16" 51 | dependsOn: 52 | - base@0.1.01 53 | --- 54 | apiVersion: kraan.io/v1alpha1 55 | kind: AddonsLayer 56 | metadata: 57 | name: apps 58 | spec: 59 | version: 0.1.01 60 | interval: 1m 61 | timeout: 30s 62 | source: 63 | name: addons-config 64 | namespace: gotk-system 65 | path: ./testdata/addons/apps 66 | prereqs: 67 | k8sVersion: "v1.16" 68 | dependsOn: 69 | - base@0.1.01 70 | - mgmt@0.1.01 -------------------------------------------------------------------------------- /testdata/addons/apps/microservice1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-1 5 | namespace: apps 6 | spec: 7 | install: 8 | remediation: 9 | retries: -1 10 | upgrade: 11 | remediation: 12 | retries: -1 13 | chart: 14 | spec: 15 | chart: podinfo 16 | sourceRef: 17 | kind: HelmRepository 18 | name: podinfo 19 | namespace: gotk-system 20 | version: '>4.0.0' 21 | test: 22 | enable: false 23 | ignoreFailures: false 24 | timeout: "2m" 25 | values: 26 | preHookBackoffLimit: 1 27 | preHookActiveDeadlineSeconds: 60 28 | preHookRestartPolicy: Never 29 | preHookDelaySeconds: 10 30 | preHookSucceed: "true" 31 | testHookBackoffLimit: 1 32 | testHookActiveDeadlineSeconds: 60 33 | testHookRestartPolicy: Never 34 | testHookDelaySeconds: 10 35 | testHookSucceed: "true" 36 | podinfo: 37 | service: 38 | enabled: true 39 | type: ClusterIP 40 | replicaCount: 1 41 | message: -Microservice Test 1 42 | interval: 1m0s 43 | -------------------------------------------------------------------------------- /testdata/addons/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: base 4 | resources: 5 | - microservice1.yaml 6 | -------------------------------------------------------------------------------- /testdata/addons/base/microservice1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-1 5 | spec: 6 | install: 7 | remediation: 8 | retries: -1 9 | upgrade: 10 | remediation: 11 | retries: -1 12 | chart: 13 | spec: 14 | chart: podinfo 15 | sourceRef: 16 | kind: HelmRepository 17 | name: podinfo 18 | namespace: gotk-system 19 | version: '>4.0.0' 20 | test: 21 | enable: false 22 | ignoreFailures: false 23 | timeout: "1m" 24 | values: 25 | preHookBackoffLimit: 1 26 | preHookActiveDeadlineSeconds: 60 27 | preHookRestartPolicy: Never 28 | preHookDelaySeconds: 10 29 | preHookSucceed: "true" 30 | testHookBackoffLimit: 1 31 | testHookActiveDeadlineSeconds: 60 32 | testHookRestartPolicy: Never 33 | testHookDelaySeconds: 10 34 | testHookSucceed: "true" 35 | podinfo: 36 | service: 37 | enabled: true 38 | type: ClusterIP 39 | replicaCount: 1 40 | message: -Microservice Test 1 41 | interval: 1m0s 42 | 43 | -------------------------------------------------------------------------------- /testdata/addons/bootstrap/integration-test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2 2 | kind: HelmRelease 3 | metadata: 4 | name: integration-test 5 | namespace: bootstrap 6 | annotations: 7 | kraan.updateVersion: "true" 8 | spec: 9 | install: 10 | remediation: 11 | retries: -1 12 | upgrade: 13 | remediation: 14 | retries: -1 15 | dependsOn: 16 | - name: microservice-1 17 | namespace: bootstrap 18 | - name: microservice-2 19 | namespace: bootstrap 20 | chart: 21 | spec: 22 | chart: podinfo 23 | sourceRef: 24 | kind: HelmRepository 25 | name: podinfo 26 | namespace: gotk-system 27 | version: '>4.0.0' 28 | test: 29 | enable: true 30 | ignoreFailures: true 31 | timeout: "1m" 32 | values: 33 | preHookBackoffLimit: 1 34 | preHookActiveDeadlineSeconds: 60 35 | preHookRestartPolicy: Never 36 | preHookDelaySeconds: 10 37 | preHookSucceed: "true" 38 | testHookBackoffLimit: 1 39 | testHookActiveDeadlineSeconds: 60 40 | testHookRestartPolicy: Never 41 | testHookDelaySeconds: 10 42 | testHookSucceed: "true" 43 | podinfo: 44 | service: 45 | enabled: true 46 | type: ClusterIP 47 | replicaCount: 1 48 | message: Integration Test 49 | interval: 1m0s 50 | -------------------------------------------------------------------------------- /testdata/addons/bootstrap/microservice1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-1 5 | namespace: bootstrap 6 | spec: 7 | install: 8 | remediation: 9 | retries: -1 10 | upgrade: 11 | remediation: 12 | retries: -1 13 | chart: 14 | spec: 15 | chart: podinfo 16 | sourceRef: 17 | kind: HelmRepository 18 | name: podinfo 19 | namespace: gotk-system 20 | version: '>4.0.0' 21 | test: 22 | enable: true 23 | ignoreFailures: true 24 | timeout: "2m" 25 | values: 26 | preHookBackoffLimit: 1 27 | preHookActiveDeadlineSeconds: 60 28 | preHookRestartPolicy: Never 29 | preHookDelaySeconds: 10 30 | preHookSucceed: "true" 31 | testHookBackoffLimit: 1 32 | testHookActiveDeadlineSeconds: 60 33 | testHookRestartPolicy: Never 34 | testHookDelaySeconds: 10 35 | testHookSucceed: "true" 36 | podinfo: 37 | service: 38 | enabled: true 39 | type: ClusterIP 40 | replicaCount: 1 41 | message: -Microservice Test 1 42 | interval: 1m0s 43 | -------------------------------------------------------------------------------- /testdata/addons/bootstrap/microservice2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-2 5 | namespace: bootstrap 6 | spec: 7 | install: 8 | remediation: 9 | retries: -1 10 | upgrade: 11 | remediation: 12 | retries: -1 13 | chart: 14 | spec: 15 | chart: podinfo 16 | sourceRef: 17 | kind: HelmRepository 18 | name: podinfo 19 | namespace: gotk-system 20 | version: '>4.0.0' 21 | test: 22 | enable: true 23 | ignoreFailures: true 24 | timeout: "1m" 25 | values: 26 | preHookBackoffLimit: 1 27 | preHookActiveDeadlineSeconds: 60 28 | preHookRestartPolicy: Never 29 | preHookDelaySeconds: 10 30 | preHookSucceed: "true" 31 | testHookBackoffLimit: 1 32 | testHookActiveDeadlineSeconds: 60 33 | testHookRestartPolicy: Never 34 | testHookDelaySeconds: 10 35 | testHookSucceed: "true" 36 | podinfo: 37 | service: 38 | enabled: true 39 | type: ClusterIP 40 | replicaCount: 1 41 | message: -Microservice Test 2 42 | interval: 1m0s 43 | -------------------------------------------------------------------------------- /testdata/addons/mgmt/microservice1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2 2 | kind: HelmRelease 3 | metadata: 4 | name: microservice-1 5 | namespace: mgmt 6 | spec: 7 | install: 8 | remediation: 9 | retries: -1 10 | upgrade: 11 | remediation: 12 | retries: -1 13 | chart: 14 | spec: 15 | chart: podinfo 16 | sourceRef: 17 | kind: HelmRepository 18 | name: podinfo 19 | namespace: gotk-system 20 | version: '>4.0.0' 21 | test: 22 | enable: false 23 | ignoreFailures: false 24 | timeout: "1m" 25 | values: 26 | preHookBackoffLimit: 1 27 | preHookActiveDeadlineSeconds: 60 28 | preHookRestartPolicy: Never 29 | preHookDelaySeconds: 10 30 | preHookSucceed: "true" 31 | testHookBackoffLimit: 1 32 | testHookActiveDeadlineSeconds: 60 33 | testHookRestartPolicy: Never 34 | testHookDelaySeconds: 10 35 | testHookSucceed: "true" 36 | podinfo: 37 | service: 38 | enabled: true 39 | type: ClusterIP 40 | replicaCount: 1 41 | message: -Microservice Test 1 42 | interval: 1m0s 43 | 44 | -------------------------------------------------------------------------------- /testdata/namespaces.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: bootstrap 5 | --- 6 | apiVersion: v1 7 | kind: Namespace 8 | metadata: 9 | name: base 10 | --- 11 | apiVersion: v1 12 | kind: Namespace 13 | metadata: 14 | name: mgmt 15 | --- 16 | apiVersion: v1 17 | kind: Namespace 18 | metadata: 19 | name: apps -------------------------------------------------------------------------------- /testdata/templates/template-http.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | username: GIT_USER 4 | password: GIT_CREDENTIALS 5 | kind: Secret 6 | metadata: 7 | name: kraan-http 8 | type: Opaque 9 | --------------------------------------------------------------------------------