├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── kapp │ └── kapp-controller-config.yaml ├── tls │ ├── config.json │ ├── intermediate-csr.json │ ├── root-csr.json │ └── server-csr.json └── workflows │ └── ci.yaml ├── .gitignore ├── .ko.yaml ├── CHANGELOG.md ├── CODE-OF-CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── api └── openapi-spec │ └── conventions-server.yaml ├── cmd ├── manager │ └── main.go └── yaml-expander │ └── main.go ├── codecov.yml ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ ├── conventions.carto.run_clusterpodconventions.yaml │ │ └── conventions.carto.run_podintents.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_clusterpodconventions.yaml │ │ ├── cainjection_in_podintents.yaml │ │ ├── ducks_in_clusterpodconventions.yaml │ │ ├── ducks_in_podintents.yaml │ │ ├── webhook_in_clusterpodconventions.yaml │ │ └── webhook_in_podintents.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_prometheus_metrics_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── rbac │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service-account.yaml ├── samples │ ├── v1alpha1_clusterpodconvention.yaml │ └── v1alpha1_podintent.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── manifests.yaml │ └── service.yaml ├── dist ├── aks-webhooks.yaml ├── bundle.values.yaml ├── bundle.yaml ├── ca-overlay.yaml ├── ca.pem ├── cartographer-conventions.yaml ├── package-install.values.yaml ├── package-install.yaml ├── package.values.yaml ├── package.yaml ├── resource-overlay.yaml ├── sa-arn-annotation-overlay.yaml ├── schema.yaml ├── strip-status.yaml └── third-party │ └── cert-manager.yaml ├── docs ├── design.md ├── images │ ├── conventions-flow.jpg │ └── supplychain-flow.jpg └── podconventioncontext-sample.yaml ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── boilerplate.ytt.txt ├── go.mod ├── go.sum ├── hello-sbom-layer.tar.gz ├── hello.Dockerfile ├── hello.tar.gz └── tools.go ├── pkg ├── apis │ ├── conventions │ │ └── v1alpha1 │ │ │ ├── clusterpodconvention_defaults.go │ │ │ ├── clusterpodconvention_test.go │ │ │ ├── clusterpodconvention_types.go │ │ │ ├── clusterpodconvention_validation.go │ │ │ ├── doc.go │ │ │ ├── groupversion_info.go │ │ │ ├── podintent_defaults.go │ │ │ ├── podintent_lifecycle.go │ │ │ ├── podintent_test.go │ │ │ ├── podintent_types.go │ │ │ ├── podintent_validation.go │ │ │ ├── podtemplatespec.go │ │ │ └── zz_generated.deepcopy.go │ ├── interfaces.go │ └── thirdparty │ │ └── cert-manager │ │ └── v1 │ │ ├── doc.go │ │ ├── groupversion_info.go │ │ ├── types.go │ │ ├── types_certificaterequest.go │ │ └── zz_generated.deepcopy.go ├── binding │ ├── convention.go │ ├── convention_test.go │ ├── conventions.go │ ├── conventions_test.go │ ├── fake │ │ └── conventionserver.go │ ├── image_config.go │ ├── image_config_test.go │ └── webhook_config.go ├── controllers │ ├── metrics_reconciler.go │ ├── metrics_reconciler_test.go │ ├── podintent_reconciler.go │ └── podintent_reconciler_test.go └── dies │ ├── cert-manager │ └── v1 │ │ ├── certificaterequest.go │ │ ├── zz_generated.die.go │ │ └── zz_generated.die_test.go │ └── conventions │ └── v1alpha1 │ ├── clusterpodconvention.go │ ├── podintent.go │ ├── zz_generated.die.go │ └── zz_generated.die_test.go ├── samples ├── convention-server │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── server.go │ ├── server.yaml │ └── workload.yaml ├── dumper-server │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── server.go │ └── server.yaml └── spring-convention-server │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── resources │ ├── conventions.go │ ├── dependencies.go │ ├── serviceintent.go │ ├── spring_properties.go │ └── springboot.go │ ├── server.go │ ├── server.yaml │ └── workload.yaml └── webhook ├── api └── v1alpha1 │ ├── groupverion_info.go │ ├── podconventioncontext_types.go │ └── zz_generated.deepcopy.go ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── boilerplate.ytt.txt ├── go.mod ├── go.sum ├── hello-sbom-layer.tar.gz ├── hello.Dockerfile ├── hello.tar.gz └── tools.go └── server.go /.gitattributes: -------------------------------------------------------------------------------- 1 | **/zz_generated.*.go linguist-generated=true 2 | hack/** linguist-generated=true 3 | vendor/** linguist-generated=true 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Use this template to report bugs or bad behavior. 4 | labels: bug, needs triage 5 | --- 6 | 8 | 9 | **Bug description** 10 | 11 | **Expected behavior** 12 | 13 | **Steps to reproduce the bug** 14 | 15 | **Version** (Tanzu Application Platform, K8s the controller is installed on) 16 | 17 | **Environment where the bug was observed (cloud, OS, etc)** 18 | 19 | **Relevant Debug Output (Logs, etc)** 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for a feature request or change. 4 | labels: needs triage, enhancement 5 | --- 6 | 7 | 9 | 10 | **Describe the feature request** 11 | 12 | **Is your feature request related to a problem? Please describe** 13 | 14 | **Describe alternatives you've considered** 15 | 16 | **Additional context** 17 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Pull request 2 | 4 | 5 | ### What this PR does / why we need it 6 | 7 | 8 | ### Which issue(s) this PR fixes 9 | 12 | 13 | Fixes # 14 | 15 | ### Describe testing done for PR 16 | 17 | 18 | 19 | 20 | ### Additional information or special notes for your reviewer 21 | 22 | 23 | 24 | 28 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: gomod 9 | directory: "/" 10 | schedule: 11 | interval: weekly 12 | open-pull-requests-limit: 10 13 | groups: 14 | k8s-dependencies: 15 | patterns: 16 | - "k8s.io*" 17 | - package-ecosystem: gomod 18 | directory: "/webhook" 19 | schedule: 20 | interval: weekly 21 | open-pull-requests-limit: 10 22 | groups: 23 | k8s-dependencies: 24 | patterns: 25 | - "k8s.io*" 26 | - package-ecosystem: gomod 27 | directory: "/webhook/hack" 28 | schedule: 29 | interval: weekly 30 | open-pull-requests-limit: 10 31 | groups: 32 | k8s-dependencies: 33 | patterns: 34 | - "k8s.io*" 35 | - package-ecosystem: gomod 36 | directory: "/samples/convention-server" 37 | schedule: 38 | interval: weekly 39 | open-pull-requests-limit: 10 40 | groups: 41 | k8s-dependencies: 42 | patterns: 43 | - "k8s.io*" 44 | - package-ecosystem: gomod 45 | directory: "/samples/dumper-server" 46 | schedule: 47 | interval: weekly 48 | open-pull-requests-limit: 10 49 | groups: 50 | k8s-dependencies: 51 | patterns: 52 | - "k8s.io*" 53 | - package-ecosystem: gomod 54 | directory: "/samples/spring-convention-server" 55 | schedule: 56 | interval: weekly 57 | open-pull-requests-limit: 10 58 | groups: 59 | k8s-dependencies: 60 | patterns: 61 | - "k8s.io*" 62 | - package-ecosystem: gomod 63 | directory: "/hack" 64 | schedule: 65 | interval: weekly 66 | open-pull-requests-limit: 10 67 | -------------------------------------------------------------------------------- /.github/kapp/kapp-controller-config.yaml: -------------------------------------------------------------------------------- 1 | 2 | #! Copyright 2022 VMware Inc. 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 | #@ load("@ytt:base64", "base64") 17 | #@ load("@ytt:data", "data") 18 | 19 | --- 20 | apiVersion: v1 21 | kind: Secret 22 | metadata: 23 | name: kapp-controller-config 24 | namespace: kapp-controller 25 | data: 26 | caCerts: #@ base64.encode(data.values.ca_cert_data) 27 | -------------------------------------------------------------------------------- /.github/tls/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "default": { 4 | "expiry": "24h" 5 | }, 6 | "profiles": { 7 | "intermediate": { 8 | "usages": [ 9 | "cert sign", 10 | "crl sign" 11 | ], 12 | "ca_constraint": { 13 | "is_ca": true, 14 | "max_path_len": 0, 15 | "max_path_len_zero": true 16 | }, 17 | "expiry": "24h" 18 | }, 19 | "server": { 20 | "usages": [ 21 | "signing", 22 | "key encipherment", 23 | "server auth" 24 | ], 25 | "expiry": "24h" 26 | }, 27 | "client": { 28 | "usages": [ 29 | "signing", 30 | "digital signature", 31 | "key encipherment", 32 | "client auth" 33 | ], 34 | "expiry": "24h" 35 | }, 36 | "peer": { 37 | "usages": [ 38 | "signing", 39 | "digital signature", 40 | "key encipherment", 41 | "client auth", 42 | "server auth" 43 | ], 44 | "expiry": "24h" 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /.github/tls/intermediate-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "CI Intermediate CA", 3 | "hosts": [ 4 | "" 5 | ], 6 | "key": { 7 | "algo": "rsa", 8 | "size": 2048 9 | }, 10 | "names": [ 11 | { 12 | "O": "VMware Inc", 13 | "OU": "Tanzu" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /.github/tls/root-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "CI Root CA", 3 | "hosts": [], 4 | "key": { 5 | "algo": "rsa", 6 | "size": 2048 7 | }, 8 | "names": [ 9 | { 10 | "O": "VMware Inc", 11 | "OU": "Tanzu" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /.github/tls/server-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": { 3 | "algo": "rsa", 4 | "size": 2048 5 | }, 6 | "names": [ 7 | { 8 | "O": "VMware Inc", 9 | "OU": "Tanzu" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /cover.out 2 | 3 | # IDEs 4 | .vscode/ 5 | .DS_Store 6 | .idea 7 | -------------------------------------------------------------------------------- /.ko.yaml: -------------------------------------------------------------------------------- 1 | defaultBaseImage: paketobuildpacks/run-jammy-tiny:latest 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Cartographer Conventions Changelog 2 | 3 | ## v0.2.0 4 | 5 | - [FEATURE] Add an optional selectorTarget field on the ClusterPodConvention resource to specify label source for ClusterPodConvention matchers (#158). 6 | - [DOCS] Add details about the newly added optional field `selectorTarget`(#172). 7 | 8 | ## v0.1.0 9 | 10 | Initial Release 11 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in Cartographer project and our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at oss-coc@vmware.com. 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series 85 | of actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or 92 | permanent ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within 112 | the community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.0, available at 118 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 119 | 120 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 121 | enforcement ladder](https://github.com/mozilla/diversity). 122 | 123 | [homepage]: https://www.contributor-covenant.org 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | https://www.contributor-covenant.org/faq. Translations are available at 127 | https://www.contributor-covenant.org/translations. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Cartographer Conventions 2 | 3 | The Cartographer project team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). 4 | 5 | ## Contribution Flow 6 | 7 | This is a rough outline of what a contributor's workflow looks like: 8 | 9 | - Create a topic branch from where you want to base your work 10 | - Make commits of logical units 11 | - Make sure your commit messages are in the proper format (see below) 12 | - Push your changes to a topic branch in your fork of the repository 13 | - Submit a pull request 14 | 15 | Example: 16 | 17 | ``` shell 18 | git remote add upstream https://github.com/vmware-tanzu/cartographer-conventions.git 19 | git checkout -b my-new-feature main 20 | git commit -a 21 | git push origin my-new-feature 22 | ``` 23 | 24 | ### Staying In Sync With Upstream 25 | 26 | When your branch gets out of sync with the vmware/main branch, use the following to update: 27 | 28 | ``` shell 29 | git checkout my-new-feature 30 | git fetch -a 31 | git pull --rebase upstream main 32 | git push --force-with-lease origin my-new-feature 33 | ``` 34 | 35 | ### Updating pull requests 36 | 37 | If your PR fails to pass CI or needs changes based on code review, you'll most likely want to squash these changes into 38 | existing commits. 39 | 40 | If your pull request contains a single commit or your changes are related to the most recent commit, you can simply 41 | amend the commit. 42 | 43 | ``` shell 44 | git add . 45 | git commit --amend 46 | git push --force-with-lease origin my-new-feature 47 | ``` 48 | 49 | If you need to squash changes into an earlier commit, you can use: 50 | 51 | ``` shell 52 | git add . 53 | git commit --fixup 54 | git rebase -i --autosquash main 55 | git push --force-with-lease origin my-new-feature 56 | ``` 57 | 58 | Be sure to add a comment to the PR indicating your new changes are ready to review, as GitHub does not generate a 59 | notification when you git push. 60 | 61 | ### Code Style 62 | 63 | ### Formatting Commit Messages 64 | 65 | We follow the conventions on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/). 66 | 67 | Be sure to include any related GitHub issue references in the commit message. See 68 | 69 | [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) for referencing issues 70 | and commits. 71 | 72 | ## Reporting Bugs and Creating Issues 73 | 74 | When opening a new issue, try to roughly follow the commit message format conventions above. 75 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 2 | ifeq (,$(shell go env GOBIN)) 3 | GOBIN=$(shell go env GOPATH)/bin 4 | else 5 | GOBIN=$(shell go env GOBIN) 6 | endif 7 | 8 | CONTROLLER_GEN ?= go run -modfile hack/go.mod sigs.k8s.io/controller-tools/cmd/controller-gen 9 | DIEGEN ?= go run -modfile hack/go.mod reconciler.io/dies/diegen 10 | GOIMPORTS ?= go run -modfile hack/go.mod golang.org/x/tools/cmd/goimports 11 | KUSTOMIZE ?= go run -modfile hack/go.mod sigs.k8s.io/kustomize/kustomize/v5 12 | YTT ?= go run -modfile hack/go.mod carvel.dev/ytt/cmd/ytt 13 | 14 | 15 | .PHONY: all 16 | all: test dist 17 | 18 | .PHONY: test 19 | test: generate fmt vet ## Run tests 20 | go test ./... -coverprofile cover.out 21 | 22 | 23 | 24 | # Generate manifests e.g. CRD, RBAC etc. 25 | .PHONY: manifests 26 | manifests: 27 | $(CONTROLLER_GEN) "crd:crdVersions=v1,maxDescLen=0" rbac:roleName=manager-role webhook \ 28 | paths="./pkg/apis/conventions/...;./pkg/controllers/..." \ 29 | output:crd:dir=./config/crd/bases \ 30 | output:rbac:dir=./config/rbac \ 31 | output:webhook:dir=./config/webhook 32 | # cleanup duplicate resource generation 33 | @rm -f config/*.yaml 34 | 35 | dist: dist/cartographer-conventions.yaml 36 | 37 | dist/cartographer-conventions.yaml: generate manifests 38 | $(KUSTOMIZE) build config/default \ 39 | | $(YTT) -f - -f dist/strip-status.yaml -f dist/aks-webhooks.yaml \ 40 | > dist/cartographer-conventions.yaml 41 | 42 | dist/third-party: dist/third-party/cert-manager.yaml 43 | 44 | dist/third-party/cert-manager.yaml: Makefile 45 | curl -Ls https://github.com/cert-manager/cert-manager/releases/download/v1.7.2/cert-manager.yaml > dist/third-party/cert-manager.yaml 46 | 47 | # Run go fmt against code 48 | .PHONY: fmt 49 | fmt: 50 | $(GOIMPORTS) --local github.com/vmware-tanzu/cartographer-conventions -w pkg/ webhook/ samples/ 51 | 52 | # Run go vet against code 53 | .PHONY: vet 54 | vet: 55 | go vet ./... 56 | 57 | .PHONY: generate 58 | generate: generate-internal fmt ## Generate code 59 | 60 | .PHONY: generate-internal 61 | generate-internal: 62 | $(CONTROLLER_GEN) object:headerFile=./hack/boilerplate.go.txt paths="./..." 63 | $(DIEGEN) die:headerFile=./hack/boilerplate.go.txt paths="./..." 64 | 65 | .PHONY: tidy 66 | tidy: ## Run go mod tidy 67 | go mod tidy -v 68 | cd hack && go mod tidy -v 69 | cd samples/convention-server && go mod tidy -v 70 | cd samples/dumper-server && go mod tidy -v 71 | cd samples/spring-convention-server && go mod tidy -v 72 | cd webhook && go mod tidy -v 73 | cd webhook/hack && go mod tidy -v 74 | 75 | # Absolutely awesome: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html 76 | help: ## Print help for each make target 77 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' 78 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2020-2022 VMware, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cartographer Conventions 2 | 3 | Conventions allow an operator to define cross cutting behavior that are directly relevant to the developer's intent. Conventions reduce the amount of manual configuration required to run applications on Kubernetes effectively. 4 | 5 | - [Pre-requisites](#pre-requisites) 6 | - [Install](#install) 7 | - [From Source](#from-source) 8 | - [Install on AWS](#running-cartographer-convention-on-an-aws) 9 | - [Samples](#samples) 10 | - [Contributing](#contributing) 11 | - [License](#license) 12 | 13 | ## Pre-requisites 14 | 15 | This project requires access to a [container registry](https://docs.docker.com/registry/introduction/) for fetching image metadata. It will not work for images that have bypassed a registry by loading directly into a local daemon. 16 | 17 | ## Install 18 | 19 | ### From Source 20 | 21 | We use [Golang 1.21+](https://golang.org) and [`ko`](https://github.com/google/ko) to build the controller, and recommend [`kapp`](https://get-kapp.io) to deploy. 22 | 23 | 1. Install cert-manager 24 | 25 | ```sh 26 | kapp deploy -n kube-system -a cert-manager -f dist/third-party/cert-manager.yaml 27 | ``` 28 | 29 | 2. Create a namespace to deploy components, if it doesn't already exist 30 | 31 | ```sh 32 | kubectl create ns conventions-system 33 | ``` 34 | 35 | 3. Optional: Trust additional certificate authorities certificate 36 | 37 | If a PodIntent references an image in a registry whose certificate was ***not*** signed by a Public Certificate Authority (CA), a certificate error `x509: certificate signed by unknown authority` will occur while applying conventions. To trust additional certificate authorities include the PEM encoded CA certificates in a file and set following environment variable to the location of that file. 38 | 39 | ```sh 40 | CA_DATA=path/to/certfile # a PEM-encoded CA certificate 41 | ``` 42 | 43 | 4. Build and install Cartographer Conventions 44 | 45 | ```sh 46 | kapp deploy -n conventions-system -a conventions \ 47 | -f <( \ 48 | ko resolve -f <( \ 49 | ytt \ 50 | -f dist/cartographer-conventions.yaml \ 51 | -f dist/ca-overlay.yaml \ 52 | --data-value-file ca_cert_data=${CA_DATA:-dist/ca.pem} \ 53 | ) \ 54 | ) 55 | ``` 56 | 57 | Note: you'll need to `export KO_DOCKER_REPO=` such that `ko` can push to the repository and your cluster can pull from it. Visit [the ko README](https://github.com/ko-build/ko) for more information. 58 | 59 | ## Running cartographer convention on an AWS cluster 60 | 61 | In order to [attach an IAM role](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) to the service account that the controller uses, provide the role [arn](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html) during installation phase. 62 | 63 | ```sh 64 | kapp deploy -n conventions-system -a conventions \ 65 | -f <( \ 66 | ko resolve -f <( \ 67 | ytt \ 68 | -f dist/cartographer-conventions.yaml \ 69 | -f dist/ca-overlay.yaml \ 70 | -f dist/sa-arn-annotation-overlay.yaml \ 71 | --data-value-file ca_cert_data=${CA_DATA:-dist/ca.pem} \ 72 | --data-value aws_iam_role_arn="eks.amazonaws.com/role-arn: arn:aws:iam::133523324:role/role_name" 73 | ) \ 74 | ) 75 | ``` 76 | 77 | The service account `cartographer-conventions-controller-manager` would have the role arn added as annotation 78 | 79 | ```sh 80 | apiVersion: v1 81 | kind: ServiceAccount 82 | metadata: 83 | labels: 84 | app.kubernetes.io/component: conventions 85 | name: cartographer-conventions-controller-manager 86 | namespace: conventions-system 87 | annotations: 88 | eks.amazonaws.com/role-arn: 'eks.amazonaws.com/role-arn: arn:aws:iam::133523324:role/role_name' 89 | ``` 90 | 91 | ## Samples 92 | 93 | - [Convention Server](./samples/convention-server/) 94 | 95 | Apply custom conventions to workloads with a ClusterPodConvention pointing at a webhook convention server. 96 | 97 | - [Spring Boot Conventions](./samples/spring-convention-server/) 98 | 99 | Apply custom conventions for Spring Boot workloads. This convention can detect if the workload is built from Spring Boot adding a label to the workload indicating the framework is `spring-boot`, and an annotation indicating the version of Spring Boot used. 100 | 101 | - [Dumper Server](./samples/dumper-server/) 102 | 103 | Log the content of the webhook request to stdout. Useful for capturing the image metadata available to conventions. 104 | 105 | ## Contributing 106 | 107 | The Cartographer project team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). For more detailed information, refer to [CONTRIBUTING.md](CONTRIBUTING.md). 108 | 109 | ## License 110 | 111 | Refer to [LICENSE](LICENSE) for details. 112 | -------------------------------------------------------------------------------- /api/openapi-spec/conventions-server.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: Pod convention webhook 4 | description: | 5 | This API defines the expected request and response format from the conventions server. 6 | version: 1.0.0-alpha.1 7 | license: 8 | name: Apache-2.0 9 | url: "https://www.apache.org/licenses/LICENSE-2.0.html" 10 | paths: 11 | /webhook: 12 | post: 13 | description: | 14 | The path defined above is arbitrary and can be overridden to any value by the ClusterPodConvention resource. 15 | The webhook path can be configured in the ClusterPodConvention on either .spec.webhook.clientConfig.url or 16 | .spec.webhook.clientConfig.service.path with the later preferred if the convention server is to run on the same cluster as workoads. 17 | The webhook request and response both use the PodConventionContext with the request defining 18 | the .spec and the response defining the status. 19 | status 20 | requestBody: 21 | content: 22 | "application/json": 23 | schema: 24 | $ref: "#/components/schemas/PodConventionContext" 25 | responses: 26 | 200: 27 | description: expected response once all conventions are applied successfully. 28 | content: 29 | "application/json": 30 | schema: 31 | $ref: "#/components/schemas/PodConventionContext" 32 | 400: 33 | description: | 34 | return code 400 if the request body is nil or if unable to to decode request body into a PodConventionContext. 35 | 500: 36 | description: | 37 | return code 500 if unable to apply conventions at all. 38 | 39 | components: 40 | schemas: 41 | PodConventionContext: 42 | description: | 43 | A wrapper for the PodConventionContextSpec and the PodConventionContextStatus which is the structure used for both requests 44 | and responses from the convention server. 45 | type: object 46 | properties: 47 | apiVersion: 48 | type: string 49 | kind: 50 | type: string 51 | metadata: 52 | type: object 53 | additionalProperties: true 54 | properties: 55 | name: 56 | type: string 57 | spec: 58 | $ref: "#/components/schemas/PodConventionContextSpec" 59 | status: 60 | $ref: "#/components/schemas/PodConventionContextStatus" 61 | PodConventionContextSpec: 62 | type: object 63 | description: a wrapper of the PodTemplateSpec and list of ImageConfigs provided in the request body of the server. 64 | properties: 65 | template: 66 | $ref: "#/components/schemas/PodTemplateSpec" 67 | imageConfig: 68 | description: | 69 | an array of imageConfig objects with each image configuration object holding the name of the image, the BOM, and the OCI image 70 | configuration with image metadata from the repository. Each of the image config array entries have a 1:1 mapping to 71 | images referenced in the PodTemplateSpec. 72 | type: array 73 | items: 74 | $ref: "#/components/schemas/ImageConfig" 75 | PodTemplateSpec: 76 | type: object 77 | properties: 78 | spec: 79 | type: object 80 | additionalProperties: true 81 | description: defines the PodTemplateSpec to be enriched by conventions. 82 | metadata: 83 | type: object 84 | additionalProperties: true 85 | properties: 86 | name: 87 | type: string 88 | additionalProperties: true 89 | ImageConfig: 90 | type: object 91 | properties: 92 | image: 93 | type: string 94 | description: a string reference to the image name and tag or associated digest. 95 | example: "example.com/repository/nginx:alpine" 96 | boms: 97 | type: array 98 | description: | 99 | an array of Bills of Materials (BOMs) describing the software components and their dependencies and may be zero or more per image. 100 | items: 101 | $ref: "#/components/schemas/BOM" 102 | config: 103 | type: object 104 | description: OCI image metadata 105 | additionalProperties: true 106 | BOM: 107 | type: object 108 | properties: 109 | name: 110 | description: bom-name 111 | type: string 112 | raw: 113 | description: base64 encoded bytes with the encoded content of the BOM. 114 | type: string 115 | example: | 116 | { 117 | "name": "bom-name", 118 | "raw": "c29tZSBieXRlIGFycmF5" 119 | } 120 | PodConventionContextStatus: 121 | description: status type used to represent the current status of the context retrieved by the request. 122 | type: object 123 | properties: 124 | template: 125 | $ref: "#/components/schemas/PodTemplateSpec" 126 | appliedConventions: 127 | description: a list of string with names of conventions to be applied 128 | type: array 129 | items: 130 | type: string 131 | example: | 132 | "appliedConventions": [ 133 | "convention-1", 134 | "convention-2", 135 | "convention-4" 136 | ] 137 | -------------------------------------------------------------------------------- /cmd/yaml-expander/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "io" 21 | "log" 22 | "os" 23 | 24 | "k8s.io/apimachinery/pkg/util/yaml" 25 | sigyaml "sigs.k8s.io/yaml" 26 | ) 27 | 28 | func main() { 29 | d := yaml.NewYAMLOrJSONDecoder(os.Stdin, 4096) 30 | for { 31 | doc := map[string]interface{}{} 32 | if err := d.Decode(&doc); err != nil { 33 | if err == io.EOF { 34 | return 35 | } 36 | log.Fatal(err) 37 | } 38 | if len(doc) == 0 { 39 | // skip empty documents 40 | continue 41 | } 42 | b, err := sigyaml.Marshal(doc) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | os.Stdout.Write(b) 47 | os.Stdout.Write([]byte("\n---\n")) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "**/zz_generated.*.go" 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | informational: true 8 | -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | apiVersion: cert-manager.io/v1 4 | kind: Issuer 5 | metadata: 6 | name: selfsigned-issuer 7 | namespace: system 8 | spec: 9 | selfSigned: {} 10 | --- 11 | apiVersion: cert-manager.io/v1 12 | kind: Certificate 13 | metadata: 14 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 15 | namespace: system 16 | spec: 17 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 18 | commonName: $(SERVICE_NAME) 19 | dnsNames: 20 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 21 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 22 | issuerRef: 23 | kind: Issuer 24 | name: selfsigned-issuer 25 | secretName: conventions-webhook-server-cert # this secret is manually prefixed, since it's not managed by kustomize 26 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: cert-manager.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: cert-manager.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /config/crd/bases/conventions.carto.run_clusterpodconventions.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.18.0 7 | name: clusterpodconventions.conventions.carto.run 8 | spec: 9 | group: conventions.carto.run 10 | names: 11 | categories: 12 | - conventions 13 | kind: ClusterPodConvention 14 | listKind: ClusterPodConventionList 15 | plural: clusterpodconventions 16 | singular: clusterpodconvention 17 | scope: Cluster 18 | versions: 19 | - additionalPrinterColumns: 20 | - jsonPath: .metadata.creationTimestamp 21 | name: Age 22 | type: date 23 | name: v1alpha1 24 | schema: 25 | openAPIV3Schema: 26 | properties: 27 | apiVersion: 28 | type: string 29 | kind: 30 | type: string 31 | metadata: 32 | type: object 33 | spec: 34 | properties: 35 | priority: 36 | type: string 37 | selectorTarget: 38 | type: string 39 | selectors: 40 | items: 41 | properties: 42 | matchExpressions: 43 | items: 44 | properties: 45 | key: 46 | type: string 47 | operator: 48 | type: string 49 | values: 50 | items: 51 | type: string 52 | type: array 53 | x-kubernetes-list-type: atomic 54 | required: 55 | - key 56 | - operator 57 | type: object 58 | type: array 59 | x-kubernetes-list-type: atomic 60 | matchLabels: 61 | additionalProperties: 62 | type: string 63 | type: object 64 | type: object 65 | x-kubernetes-map-type: atomic 66 | type: array 67 | webhook: 68 | properties: 69 | certificate: 70 | properties: 71 | name: 72 | type: string 73 | namespace: 74 | type: string 75 | required: 76 | - name 77 | - namespace 78 | type: object 79 | clientConfig: 80 | properties: 81 | caBundle: 82 | format: byte 83 | type: string 84 | service: 85 | properties: 86 | name: 87 | type: string 88 | namespace: 89 | type: string 90 | path: 91 | type: string 92 | port: 93 | format: int32 94 | type: integer 95 | required: 96 | - name 97 | - namespace 98 | type: object 99 | url: 100 | type: string 101 | type: object 102 | required: 103 | - clientConfig 104 | type: object 105 | type: object 106 | required: 107 | - spec 108 | type: object 109 | served: true 110 | storage: true 111 | subresources: {} 112 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/conventions.carto.run_clusterpodconventions.yaml 6 | - bases/conventions.carto.run_podintents.yaml 7 | # +kubebuilder:scaffold:crdkustomizeresource 8 | 9 | patchesStrategicMerge: 10 | # patch CRD bases to add labels for duck discovery 11 | - patches/ducks_in_clusterpodconventions.yaml 12 | - patches/ducks_in_podintents.yaml 13 | 14 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 15 | # patches here are for enabling the conversion webhook for each CRD 16 | #- patches/webhook_in_clusterpodconventions.yaml 17 | #- patches/webhook_in_podintents.yaml 18 | # +kubebuilder:scaffold:crdkustomizewebhookpatch 19 | 20 | # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. 21 | # patches here are for enabling the CA injection for each CRD 22 | #- patches/cainjection_in_clusterpodconventions.yaml 23 | #- patches/cainjection_in_podintents.yaml 24 | # +kubebuilder:scaffold:crdkustomizecainjectionpatch 25 | 26 | # the following config is for teaching kustomize how to do kustomization for CRDs. 27 | configurations: 28 | - kustomizeconfig.yaml 29 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | group: apiextensions.k8s.io 8 | path: spec/conversion/webhookClientConfig/service/name 9 | 10 | namespace: 11 | - kind: CustomResourceDefinition 12 | group: apiextensions.k8s.io 13 | path: spec/conversion/webhookClientConfig/service/namespace 14 | create: false 15 | 16 | varReference: 17 | - path: metadata/annotations 18 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_clusterpodconventions.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: clusterpodconventions.conventions.carto.run -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_podintents.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: podintents.conventions.carto.run 9 | -------------------------------------------------------------------------------- /config/crd/patches/ducks_in_clusterpodconventions.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds labels advertising that this resource implements known 2 | # duck types. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | labels: {} 7 | name: clusterpodconventions.conventions.carto.run 8 | -------------------------------------------------------------------------------- /config/crd/patches/ducks_in_podintents.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds labels advertising that this resource implements known 2 | # duck types. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | labels: 7 | duck.knative.dev/podspecable: "true" 8 | name: podintents.conventions.carto.run 9 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_clusterpodconventions.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: clusterpodconventions.conventions.carto.run 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_podintents.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: podintents.conventions.carto.run 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: conventions-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: cartographer-conventions- 10 | 11 | # Labels to add to all resources and selectors. 12 | commonLabels: 13 | app.kubernetes.io/component: conventions 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml 20 | - ../webhook 21 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 22 | - ../certmanager 23 | 24 | patchesStrategicMerge: 25 | # Protect the /metrics endpoint by putting it behind auth. 26 | # Only one of manager_auth_proxy_patch.yaml and 27 | # manager_prometheus_metrics_patch.yaml should be enabled. 28 | # - manager_auth_proxy_patch.yaml 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, uncomment the following line and 31 | # comment manager_auth_proxy_patch.yaml. 32 | # Only one of manager_auth_proxy_patch.yaml and 33 | # manager_prometheus_metrics_patch.yaml should be enabled. 34 | #- manager_prometheus_metrics_patch.yaml 35 | 36 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml 37 | - manager_webhook_patch.yaml 38 | 39 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 40 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 41 | # 'CERTMANAGER' needs to be enabled to use ca injection 42 | - webhookcainjection_patch.yaml 43 | 44 | # the following config is for teaching kustomize how to do var substitution 45 | vars: 46 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 47 | - name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 48 | objref: 49 | kind: Certificate 50 | group: cert-manager.io 51 | version: v1 52 | name: serving-cert # this name should match the one in certificate.yaml 53 | fieldref: 54 | fieldpath: metadata.namespace 55 | - name: CERTIFICATE_NAME 56 | objref: 57 | kind: Certificate 58 | group: cert-manager.io 59 | version: v1 60 | name: serving-cert # this name should match the one in certificate.yaml 61 | - name: SERVICE_NAMESPACE # namespace of the service 62 | objref: 63 | kind: Service 64 | version: v1 65 | name: webhook-service 66 | fieldref: 67 | fieldpath: metadata.namespace 68 | - name: SERVICE_NAME 69 | objref: 70 | kind: Service 71 | version: v1 72 | name: webhook-service 73 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the controller manager, 2 | # it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=10" 19 | ports: 20 | - containerPort: 8443 21 | name: https 22 | - name: manager 23 | args: 24 | - "--metrics-addr=127.0.0.1:8080" 25 | - "--enable-leader-election" 26 | -------------------------------------------------------------------------------- /config/default/manager_prometheus_metrics_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch enables Prometheus scraping for the manager pod. 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: controller-manager 6 | namespace: system 7 | spec: 8 | template: 9 | metadata: 10 | annotations: 11 | prometheus.io/scrape: 'true' 12 | spec: 13 | containers: 14 | # Expose the prometheus metrics on default port 15 | - name: manager 16 | ports: 17 | - containerPort: 8080 18 | name: metrics 19 | protocol: TCP 20 | -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 443 13 | name: webhook-server 14 | protocol: TCP 15 | readinessProbe: 16 | httpGet: 17 | port: 8081 18 | path: /readyz 19 | livenessProbe: 20 | httpGet: 21 | port: 8081 22 | path: /healthz 23 | volumeMounts: 24 | - mountPath: /tmp/k8s-webhook-server/serving-certs 25 | name: cert 26 | readOnly: true 27 | volumes: 28 | - name: cert 29 | secret: 30 | defaultMode: 420 31 | secretName: conventions-webhook-server-cert 32 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. 3 | apiVersion: admissionregistration.k8s.io/v1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | name: mutating-webhook-configuration 7 | annotations: 8 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 9 | --- 10 | apiVersion: admissionregistration.k8s.io/v1 11 | kind: ValidatingWebhookConfiguration 12 | metadata: 13 | name: validating-webhook-configuration 14 | annotations: 15 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 16 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | template: 21 | metadata: 22 | labels: 23 | control-plane: controller-manager 24 | spec: 25 | containers: 26 | - args: 27 | - --enable-leader-election 28 | env: 29 | - name: SYSTEM_NAMESPACE 30 | valueFrom: 31 | fieldRef: 32 | fieldPath: metadata.namespace 33 | image: ko://github.com/vmware-tanzu/cartographer-conventions/cmd/manager 34 | name: manager 35 | resources: 36 | limits: 37 | cpu: 100m 38 | memory: 512Mi 39 | requests: 40 | cpu: 100m 41 | memory: 20Mi 42 | volumeMounts: 43 | - mountPath: /var/cache/ggcr 44 | name: cache-volume 45 | - mountPath: /var/conventions/tls 46 | name: ca-certs 47 | securityContext: 48 | allowPrivilegeEscalation: false 49 | readOnlyRootFilesystem: true 50 | runAsNonRoot: true 51 | seccompProfile: 52 | type: RuntimeDefault 53 | capabilities: 54 | drop: 55 | - ALL 56 | volumes: 57 | - name: cache-volume 58 | emptyDir: {} 59 | - name: ca-certs 60 | secret: 61 | secretName: ca-certificates 62 | serviceAccountName: controller-manager 63 | terminationGracePeriodSeconds: 10 64 | --- 65 | apiVersion: v1 66 | data: 67 | ca-certificates.crt: '' 68 | kind: Secret 69 | metadata: 70 | labels: 71 | control-plane: controller-manager 72 | name: ca-certificates 73 | namespace: system 74 | type: Opaque -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: ["authentication.k8s.io"] 7 | resources: 8 | - tokenreviews 9 | verbs: ["create"] 10 | - apiGroups: ["authorization.k8s.io"] 11 | resources: 12 | - subjectaccessreviews 13 | verbs: ["create"] 14 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | prometheus.io/port: "8443" 6 | prometheus.io/scheme: https 7 | prometheus.io/scrape: "true" 8 | labels: 9 | control-plane: controller-manager 10 | name: controller-manager-metrics-service 11 | namespace: system 12 | spec: 13 | ports: 14 | - name: https 15 | port: 8443 16 | targetPort: https 17 | selector: 18 | control-plane: controller-manager 19 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - role.yaml 3 | - role_binding.yaml 4 | - leader_election_role.yaml 5 | - leader_election_role_binding.yaml 6 | # Comment the following 3 lines if you want to disable 7 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 8 | # which protects your /metrics endpoint. 9 | - auth_proxy_service.yaml 10 | - auth_proxy_role.yaml 11 | - auth_proxy_role_binding.yaml 12 | - service-account.yaml 13 | -------------------------------------------------------------------------------- /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 | - coordination.k8s.io 9 | resources: 10 | - leases 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases/status 23 | verbs: 24 | - get 25 | - update 26 | - patch 27 | - apiGroups: 28 | - "" 29 | resources: 30 | - events 31 | verbs: 32 | - create 33 | - apiGroups: 34 | - "" 35 | resources: 36 | - configmaps 37 | verbs: 38 | - get 39 | - list 40 | - watch 41 | - create 42 | - update 43 | - patch 44 | - delete 45 | - apiGroups: 46 | - "" 47 | resources: 48 | - configmaps/status 49 | verbs: 50 | - get 51 | - update 52 | - patch 53 | - apiGroups: 54 | - "" 55 | resources: 56 | - events 57 | verbs: 58 | - create 59 | -------------------------------------------------------------------------------- /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: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | - events 12 | verbs: 13 | - create 14 | - delete 15 | - get 16 | - list 17 | - patch 18 | - update 19 | - watch 20 | - apiGroups: 21 | - "" 22 | resources: 23 | - secrets 24 | - serviceaccounts 25 | verbs: 26 | - get 27 | - list 28 | - watch 29 | - apiGroups: 30 | - cert-manager.io 31 | resources: 32 | - certificaterequests 33 | verbs: 34 | - get 35 | - list 36 | - watch 37 | - apiGroups: 38 | - conventions.carto.run 39 | resources: 40 | - clusterpodconventions 41 | verbs: 42 | - get 43 | - list 44 | - watch 45 | - apiGroups: 46 | - conventions.carto.run 47 | resources: 48 | - podintents 49 | verbs: 50 | - create 51 | - delete 52 | - get 53 | - list 54 | - patch 55 | - update 56 | - watch 57 | - apiGroups: 58 | - conventions.carto.run 59 | resources: 60 | - podintents/status 61 | verbs: 62 | - get 63 | - patch 64 | - update 65 | -------------------------------------------------------------------------------- /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: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/service-account.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: controller-manager 6 | namespace: system 7 | -------------------------------------------------------------------------------- /config/samples/v1alpha1_clusterpodconvention.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: conventions.carto.run/v1alpha1 2 | kind: ClusterPodConvention 3 | metadata: 4 | name: clusterpodconvention-sample 5 | spec: 6 | clientConfig: 7 | service: 8 | name: "stub-svc" 9 | namespace: "stub-ns" 10 | -------------------------------------------------------------------------------- /config/samples/v1alpha1_podintent.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: conventions.carto.run/v1alpha1 2 | kind: PodIntent 3 | metadata: 4 | name: podintent-sample 5 | spec: 6 | serviceAccountName: default 7 | template: 8 | spec: 9 | restartPolicy: Never 10 | containers: 11 | - name: workload 12 | image: ubuntu:bionic 13 | command: 14 | - env 15 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | 24 | varReference: 25 | - path: metadata/annotations 26 | -------------------------------------------------------------------------------- /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: MutatingWebhookConfiguration 4 | metadata: 5 | name: mutating-webhook-configuration 6 | webhooks: 7 | - admissionReviewVersions: 8 | - v1beta1 9 | clientConfig: 10 | service: 11 | name: webhook-service 12 | namespace: system 13 | path: /mutate-conventions-carto-run-v1alpha1-clusterpodconvention 14 | failurePolicy: Fail 15 | name: clusterpodconventions.conventions.carto.run 16 | rules: 17 | - apiGroups: 18 | - conventions.carto.run 19 | apiVersions: 20 | - v1alpha1 21 | operations: 22 | - CREATE 23 | - UPDATE 24 | resources: 25 | - clusterpodconventions 26 | sideEffects: None 27 | - admissionReviewVersions: 28 | - v1beta1 29 | clientConfig: 30 | service: 31 | name: webhook-service 32 | namespace: system 33 | path: /mutate-conventions-carto-run-v1alpha1-podintent 34 | failurePolicy: Fail 35 | name: podintents.conventions.carto.run 36 | rules: 37 | - apiGroups: 38 | - conventions.carto.run 39 | apiVersions: 40 | - v1alpha1 41 | operations: 42 | - CREATE 43 | - UPDATE 44 | resources: 45 | - podintents 46 | sideEffects: None 47 | --- 48 | apiVersion: admissionregistration.k8s.io/v1 49 | kind: ValidatingWebhookConfiguration 50 | metadata: 51 | name: validating-webhook-configuration 52 | webhooks: 53 | - admissionReviewVersions: 54 | - v1beta1 55 | clientConfig: 56 | service: 57 | name: webhook-service 58 | namespace: system 59 | path: /validate-conventions-carto-run-v1alpha1-clusterpodconvention 60 | failurePolicy: Fail 61 | name: clusterpodconventions.conventions.carto.run 62 | rules: 63 | - apiGroups: 64 | - conventions.carto.run 65 | apiVersions: 66 | - v1alpha1 67 | operations: 68 | - CREATE 69 | - UPDATE 70 | resources: 71 | - clusterpodconventions 72 | sideEffects: None 73 | - admissionReviewVersions: 74 | - v1beta1 75 | clientConfig: 76 | service: 77 | name: webhook-service 78 | namespace: system 79 | path: /validate-conventions-carto-run-v1alpha1-podintent 80 | failurePolicy: Fail 81 | name: podintents.conventions.carto.run 82 | rules: 83 | - apiGroups: 84 | - conventions.carto.run 85 | apiVersions: 86 | - v1alpha1 87 | operations: 88 | - CREATE 89 | - UPDATE 90 | resources: 91 | - podintents 92 | sideEffects: None 93 | -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: webhook-service 6 | namespace: system 7 | spec: 8 | ports: 9 | - port: 443 10 | targetPort: 9443 11 | selector: 12 | control-plane: controller-manager 13 | -------------------------------------------------------------------------------- /dist/aks-webhooks.yaml: -------------------------------------------------------------------------------- 1 | #! Copyright 2022 VMware Inc. 2 | #! 3 | #! Licensed under the Apache License, Version 2.0 (the "License"); 4 | #! you may not use this file except in compliance with the License. 5 | #! You may obtain a copy of the License at 6 | #! 7 | #! http://www.apache.org/licenses/LICENSE-2.0 8 | #! 9 | #! Unless required by applicable law or agreed to in writing, software 10 | #! distributed under the License is distributed on an "AS IS" BASIS, 11 | #! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | #! See the License for the specific language governing permissions and 13 | #! limitations under the License. 14 | 15 | #@ load("@ytt:overlay", "overlay") 16 | 17 | #@ mutating_webhook = overlay.subset({"apiVersion": "admissionregistration.k8s.io/v1", "kind": "MutatingWebhookConfiguration"}) 18 | #@ validating_webhook = overlay.subset({"apiVersion": "admissionregistration.k8s.io/v1", "kind": "ValidatingWebhookConfiguration"}) 19 | 20 | #@overlay/match by=overlay.or_op(mutating_webhook,validating_webhook), expects="0+" 21 | --- 22 | metadata: 23 | #@overlay/match missing_ok=True 24 | annotations: 25 | #@overlay/match missing_ok=True 26 | admissions.enforcer/disabled: "true" 27 | -------------------------------------------------------------------------------- /dist/bundle.values.yaml: -------------------------------------------------------------------------------- 1 | #! Copyright 2021-2022 VMware Inc. 2 | #! 3 | #! Licensed under the Apache License, Version 2.0 (the "License"); 4 | #! you may not use this file except in compliance with the License. 5 | #! You may obtain a copy of the License at 6 | #! 7 | #! http://www.apache.org/licenses/LICENSE-2.0 8 | #! 9 | #! Unless required by applicable law or agreed to in writing, software 10 | #! distributed under the License is distributed on an "AS IS" BASIS, 11 | #! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | #! See the License for the specific language governing permissions and 13 | #! limitations under the License. 14 | 15 | #@ load("@ytt:data", "data") 16 | 17 | #@data/values 18 | --- 19 | ca_cert_data: "" 20 | aws_iam_role_arn: "" 21 | -------------------------------------------------------------------------------- /dist/bundle.yaml: -------------------------------------------------------------------------------- 1 | #! Copyright 2021-2022 VMware Inc. 2 | #! 3 | #! Licensed under the Apache License, Version 2.0 (the "License"); 4 | #! you may not use this file except in compliance with the License. 5 | #! You may obtain a copy of the License at 6 | #! 7 | #! http://www.apache.org/licenses/LICENSE-2.0 8 | #! 9 | #! Unless required by applicable law or agreed to in writing, software 10 | #! distributed under the License is distributed on an "AS IS" BASIS, 11 | #! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | #! See the License for the specific language governing permissions and 13 | #! limitations under the License. 14 | 15 | #@ load("@ytt:overlay", "overlay") 16 | #@ load("@ytt:data", "data") 17 | #@ load("@ytt:base64", "base64") 18 | 19 | --- 20 | apiVersion: v1 21 | kind: Secret 22 | metadata: 23 | name: cartographer-conventions-reg-creds 24 | namespace: conventions-system 25 | annotations: 26 | secretgen.carvel.dev/image-pull-secret: "" 27 | type: kubernetes.io/dockerconfigjson 28 | data: 29 | .dockerconfigjson: e30K 30 | 31 | #@overlay/match by=overlay.subset({"apiVersion":"apps/v1","kind":"Deployment","metadata":{"labels":{"app.kubernetes.io/component":"conventions"}}}) 32 | --- 33 | spec: 34 | template: 35 | spec: 36 | #@overlay/match when=0 37 | imagePullSecrets: 38 | - name: cartographer-conventions-reg-creds 39 | -------------------------------------------------------------------------------- /dist/ca-overlay.yaml: -------------------------------------------------------------------------------- 1 | #! Copyright 2021-2022 VMware Inc. All rights reserved 2 | 3 | #@ load("@ytt:overlay", "overlay") 4 | #@ load("@ytt:data", "data") 5 | #@ load("@ytt:base64", "base64") 6 | 7 | 8 | #@overlay/match by=overlay.subset({"apiVersion": "v1", "kind": "Secret", "metadata":{"name": "cartographer-conventions-ca-certificates"}}) 9 | --- 10 | data: 11 | ca-certificates.crt: #@ base64.encode(data.values.ca_cert_data) 12 | -------------------------------------------------------------------------------- /dist/ca.pem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-tanzu/cartographer-conventions/a18491a0abe039656766dcca5f14d0df090a4ae4/dist/ca.pem -------------------------------------------------------------------------------- /dist/package-install.values.yaml: -------------------------------------------------------------------------------- 1 | #! Copyright 2021-2022 VMware Inc. 2 | #! 3 | #! Licensed under the Apache License, Version 2.0 (the "License"); 4 | #! you may not use this file except in compliance with the License. 5 | #! You may obtain a copy of the License at 6 | #! 7 | #! http://www.apache.org/licenses/LICENSE-2.0 8 | #! 9 | #! Unless required by applicable law or agreed to in writing, software 10 | #! distributed under the License is distributed on an "AS IS" BASIS, 11 | #! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | #! See the License for the specific language governing permissions and 13 | #! limitations under the License. 14 | 15 | #@ load("@ytt:data", "data") 16 | 17 | #@data/values 18 | --- 19 | namespace: default 20 | name: cartographer-conventions-controller 21 | package_name: controller.conventions.carto.run 22 | package_prerelease: null 23 | service_account_name: cartographer-conventions-controller-kc 24 | cluster_role_name: cartographer-conventions-controller-kc 25 | cluster_role_binding_name: cartographer-conventions-controller-kc 26 | sync_period: 10m 27 | 28 | has_values: false 29 | ca_cert_data: "" 30 | aws_iam_role_arn: "" 31 | -------------------------------------------------------------------------------- /dist/package-install.yaml: -------------------------------------------------------------------------------- 1 | #! Copyright 2021-2022 VMware Inc. 2 | #! 3 | #! Licensed under the Apache License, Version 2.0 (the "License"); 4 | #! you may not use this file except in compliance with the License. 5 | #! You may obtain a copy of the License at 6 | #! 7 | #! http://www.apache.org/licenses/LICENSE-2.0 8 | #! 9 | #! Unless required by applicable law or agreed to in writing, software 10 | #! distributed under the License is distributed on an "AS IS" BASIS, 11 | #! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | #! See the License for the specific language governing permissions and 13 | #! limitations under the License. 14 | 15 | #@ load("@ytt:data", "data") 16 | #@ load("@ytt:base64", "base64") 17 | #@ load("@ytt:yaml", "yaml") 18 | 19 | --- 20 | apiVersion: packaging.carvel.dev/v1alpha1 21 | kind: PackageInstall 22 | metadata: 23 | namespace: #@ data.values.namespace 24 | name: #@ data.values.name 25 | annotations: 26 | kapp.k14s.io/change-group: conventions.carto.run/install 27 | kapp.k14s.io/change-rule: "upsert after upserting conventions.carto.run/install-rbac" 28 | spec: 29 | serviceAccountName: #@ data.values.service_account_name 30 | syncPeriod: #@ data.values.sync_period 31 | packageRef: 32 | refName: #@ data.values.package_name 33 | versionSelection: 34 | constraints: #@ data.values.package_constraints 35 | #@ if data.values.package_prerelease != None: 36 | prereleases: #@ data.values.package_prerelease 37 | #@ end 38 | #@ if/end data.values.has_values: 39 | values: 40 | - secretRef: 41 | name: cartographer-conventions-controller-values 42 | 43 | 44 | --- 45 | apiVersion: kapp.k14s.io/v1alpha1 46 | kind: Config 47 | minimumRequiredVersion: 0.29.0 48 | waitRules: 49 | - supportsObservedGeneration: true 50 | conditionMatchers: 51 | - type: ReconcileFailed 52 | status: "True" 53 | failure: true 54 | - type: ReconcileSucceeded 55 | status: "True" 56 | success: true 57 | resourceMatchers: 58 | - apiVersionKindMatcher: 59 | apiVersion: packaging.carvel.dev/v1alpha1 60 | kind: PackageInstall 61 | 62 | --- 63 | apiVersion: v1 64 | kind: ServiceAccount 65 | metadata: 66 | namespace: #@ data.values.namespace 67 | name: #@ data.values.service_account_name 68 | annotations: 69 | kapp.k14s.io/change-group: conventions.carto.run/install-rbac 70 | kapp.k14s.io/change-rule: "delete after deleting conventions.carto.run/install" 71 | 72 | --- 73 | kind: ClusterRole 74 | apiVersion: rbac.authorization.k8s.io/v1 75 | metadata: 76 | name: #@ data.values.cluster_role_name 77 | annotations: 78 | kapp.k14s.io/change-group: conventions.carto.run/install-rbac 79 | kapp.k14s.io/change-rule: "delete after deleting conventions.carto.run/install" 80 | rules: 81 | - apiGroups: 82 | - "" 83 | resources: 84 | - configmaps 85 | - namespaces 86 | - secrets 87 | - serviceaccounts 88 | - services 89 | verbs: 90 | - "*" 91 | - apiGroups: 92 | - apps 93 | resources: 94 | - deployments 95 | verbs: 96 | - "*" 97 | - apiGroups: 98 | - admissionregistration.k8s.io 99 | resources: 100 | - mutatingwebhookconfigurations 101 | - validatingwebhookconfigurations 102 | verbs: 103 | - "*" 104 | - apiGroups: 105 | - apiextensions.k8s.io 106 | resources: 107 | - customresourcedefinitions 108 | verbs: 109 | - "*" 110 | - apiGroups: 111 | - rbac.authorization.k8s.io 112 | resources: 113 | - clusterrolebindings 114 | - clusterroles 115 | - rolebindings 116 | - roles 117 | verbs: 118 | - "*" 119 | - apiGroups: 120 | - cert-manager.io 121 | resources: 122 | - certificates 123 | - issuers 124 | verbs: 125 | - "*" 126 | 127 | --- 128 | apiVersion: rbac.authorization.k8s.io/v1 129 | kind: ClusterRoleBinding 130 | metadata: 131 | name: #@ data.values.cluster_role_binding_name 132 | annotations: 133 | kapp.k14s.io/change-group: conventions.carto.run/install-rbac 134 | kapp.k14s.io/change-rule: "delete after deleting conventions.carto.run/install" 135 | subjects: 136 | - kind: ServiceAccount 137 | name: #@ data.values.service_account_name 138 | namespace: #@ data.values.namespace 139 | roleRef: 140 | kind: ClusterRole 141 | name: #@ data.values.cluster_role_name 142 | apiGroup: rbac.authorization.k8s.io 143 | 144 | #@ if/end data.values.has_values: 145 | --- 146 | apiVersion: v1 147 | kind: Secret 148 | metadata: 149 | name: cartographer-conventions-controller-values 150 | annotations: 151 | kapp.k14s.io/change-group: conventions.carto.run/install-rbac 152 | kapp.k14s.io/change-rule: "delete after deleting conventions.carto.run/install" 153 | data: 154 | values.yaml: #@ base64.encode(yaml.encode(data.values)) 155 | -------------------------------------------------------------------------------- /dist/package.values.yaml: -------------------------------------------------------------------------------- 1 | #! Copyright 2021-2022 VMware Inc. 2 | #! 3 | #! Licensed under the Apache License, Version 2.0 (the "License"); 4 | #! you may not use this file except in compliance with the License. 5 | #! You may obtain a copy of the License at 6 | #! 7 | #! http://www.apache.org/licenses/LICENSE-2.0 8 | #! 9 | #! Unless required by applicable law or agreed to in writing, software 10 | #! distributed under the License is distributed on an "AS IS" BASIS, 11 | #! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | #! See the License for the specific language governing permissions and 13 | #! limitations under the License. 14 | 15 | #@ load("@ytt:data", "data") 16 | 17 | #@data/values 18 | --- 19 | name: controller.conventions.carto.run -------------------------------------------------------------------------------- /dist/package.yaml: -------------------------------------------------------------------------------- 1 | #! Copyright 2021-2022 VMware Inc. 2 | #! 3 | #! Licensed under the Apache License, Version 2.0 (the "License"); 4 | #! you may not use this file except in compliance with the License. 5 | #! You may obtain a copy of the License at 6 | #! 7 | #! http://www.apache.org/licenses/LICENSE-2.0 8 | #! 9 | #! Unless required by applicable law or agreed to in writing, software 10 | #! distributed under the License is distributed on an "AS IS" BASIS, 11 | #! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | #! See the License for the specific language governing permissions and 13 | #! limitations under the License. 14 | 15 | #@ load("@ytt:data", "data") 16 | 17 | --- 18 | apiVersion: data.packaging.carvel.dev/v1alpha1 19 | kind: Package 20 | metadata: 21 | name: #@ data.values.name + '.' + data.values.version 22 | spec: 23 | refName: #@ data.values.name 24 | version: #@ data.values.version 25 | valuesSchema: 26 | openAPIv3: 27 | title: #@ data.values.name + '.' + data.values.version + ' values schema' 28 | properties: 29 | ca_cert_data: 30 | type: string 31 | description: "Optional: PEM Encoded certificate data for image registries with private CA." 32 | default: "" 33 | aws_iam_role_arn: 34 | type: string 35 | description: 'Optional: Arn role that has access to pull images from ECR container registry' 36 | default: "" 37 | template: 38 | spec: 39 | fetch: 40 | - imgpkgBundle: 41 | image: #@ data.values.image 42 | template: 43 | - kbld: 44 | paths: 45 | - .imgpkg/images.yml 46 | - config/cartographer-conventions.yaml 47 | - ytt: 48 | paths: 49 | - "-" 50 | - bundle.yaml 51 | - bundle.values.yaml 52 | - ca-overlay.yaml 53 | deploy: 54 | - kapp: {} 55 | -------------------------------------------------------------------------------- /dist/resource-overlay.yaml: -------------------------------------------------------------------------------- 1 | #! Copyright 2024 VMware Inc. 2 | #! 3 | #! Licensed under the Apache License, Version 2.0 (the "License"); 4 | #! you may not use this file except in compliance with the License. 5 | #! You may obtain a copy of the License at 6 | #! 7 | #! http://www.apache.org/licenses/LICENSE-2.0 8 | #! 9 | #! Unless required by applicable law or agreed to in writing, software 10 | #! distributed under the License is distributed on an "AS IS" BASIS, 11 | #! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | #! See the License for the specific language governing permissions and 13 | #! limitations under the License. 14 | 15 | #@ load("@ytt:overlay", "overlay") 16 | #@ load("@ytt:data", "data") 17 | 18 | #@ def conventions_deployment(): 19 | kind: Deployment 20 | metadata: 21 | name: cartographer-conventions-controller-manager 22 | namespace: conventions-system 23 | #@ end 24 | 25 | #@overlay/match by=overlay.subset(conventions_deployment()), expects="0+" 26 | --- 27 | spec: 28 | template: 29 | spec: 30 | containers: 31 | #@overlay/match by="name" 32 | - name: manager 33 | #@overlay/match missing_ok=True 34 | resources: #@ data.values.resources -------------------------------------------------------------------------------- /dist/sa-arn-annotation-overlay.yaml: -------------------------------------------------------------------------------- 1 | #! Copyright 2022 VMware Inc. All rights reserved 2 | 3 | 4 | #@ load("@ytt:overlay", "overlay") 5 | #@ load("@ytt:data", "data") 6 | 7 | #@ if/end hasattr(data.values, "aws_iam_role_arn") and data.values.aws_iam_role_arn != "": 8 | #@overlay/match by=overlay.subset({"apiVersion":"v1","kind":"ServiceAccount","metadata":{"namespace":"conventions-system", "name":"cartographer-conventions-controller-manager"}}) 9 | --- 10 | metadata: 11 | #@overlay/match missing_ok=True 12 | annotations: 13 | eks.amazonaws.com/role-arn: #@ data.values.aws_iam_role_arn 14 | -------------------------------------------------------------------------------- /dist/schema.yaml: -------------------------------------------------------------------------------- 1 | #@data/values-schema 2 | #@schema/desc "OpenAPIv3 Schema for Cartographer Conventions" 3 | 4 | --- 5 | #@schema/desc "Optional: PEM Encoded certificate data for image registries with private CA." 6 | ca_cert_data: "" 7 | 8 | #@schema/desc "Optional: Arn role that has access to pull images from ECR container registry" 9 | aws_iam_role_arn: "" 10 | 11 | #@schema/desc "Optional: Cartographer Conventions controller resource limit configuration" 12 | #@schema/type any=True 13 | resources: {} -------------------------------------------------------------------------------- /dist/strip-status.yaml: -------------------------------------------------------------------------------- 1 | #! Copyright 2022 VMware Inc. All rights reserved 2 | 3 | #@ load("@ytt:overlay", "overlay") 4 | 5 | #@overlay/match by=overlay.all, expects="0+" 6 | --- 7 | #@overlay/match missing_ok=True 8 | status: #@overlay/remove 9 | -------------------------------------------------------------------------------- /docs/images/conventions-flow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-tanzu/cartographer-conventions/a18491a0abe039656766dcca5f14d0df090a4ae4/docs/images/conventions-flow.jpg -------------------------------------------------------------------------------- /docs/images/supplychain-flow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-tanzu/cartographer-conventions/a18491a0abe039656766dcca5f14d0df090a4ae4/docs/images/supplychain-flow.jpg -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vmware-tanzu/cartographer-conventions 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | replace github.com/vmware-tanzu/cartographer-conventions/webhook => ./webhook 8 | 9 | require ( 10 | github.com/go-logr/logr v1.4.3 11 | github.com/google/go-cmp v0.7.0 12 | github.com/google/go-containerregistry v0.20.5 13 | github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20220105220605-d9bfbcb99e52 14 | github.com/vmware-tanzu/cartographer-conventions/webhook v0.5.1 15 | go.uber.org/zap v1.27.0 16 | k8s.io/api v0.33.1 17 | k8s.io/apimachinery v0.33.1 18 | k8s.io/apiserver v0.33.1 19 | k8s.io/client-go v0.33.1 20 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 21 | reconciler.io/dies v0.16.0 22 | reconciler.io/runtime v0.23.0 23 | sigs.k8s.io/controller-runtime v0.21.0 24 | sigs.k8s.io/yaml v1.4.0 25 | ) 26 | 27 | require ( 28 | github.com/Azure/azure-sdk-for-go v55.0.0+incompatible // indirect 29 | github.com/Azure/go-autorest v14.2.0+incompatible // indirect 30 | github.com/Azure/go-autorest/autorest v0.11.27 // indirect 31 | github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect 32 | github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect 33 | github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect 34 | github.com/Azure/go-autorest/autorest/validation v0.1.0 // indirect 35 | github.com/Azure/go-autorest/logger v0.2.1 // indirect 36 | github.com/Azure/go-autorest/tracing v0.6.0 // indirect 37 | github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect 38 | github.com/aws/aws-sdk-go v1.39.4 // indirect 39 | github.com/beorn7/perks v1.0.1 // indirect 40 | github.com/blang/semver/v4 v4.0.0 // indirect 41 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 42 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 43 | github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect 44 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 45 | github.com/docker/cli v28.1.1+incompatible // indirect 46 | github.com/docker/distribution v2.8.3+incompatible // indirect 47 | github.com/docker/docker-credential-helpers v0.9.3 // indirect 48 | github.com/emicklei/go-restful/v3 v3.11.2 // indirect 49 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 50 | github.com/fatih/color v1.18.0 // indirect 51 | github.com/felixge/httpsnoop v1.0.4 // indirect 52 | github.com/fsnotify/fsnotify v1.7.0 // indirect 53 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 54 | github.com/go-logr/stdr v1.2.2 // indirect 55 | github.com/go-logr/zapr v1.3.0 // indirect 56 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 57 | github.com/go-openapi/jsonreference v0.20.4 // indirect 58 | github.com/go-openapi/swag v0.23.0 // indirect 59 | github.com/gogo/protobuf v1.3.2 // indirect 60 | github.com/golang-jwt/jwt/v4 v4.5.2 // indirect 61 | github.com/google/btree v1.1.3 // indirect 62 | github.com/google/gnostic-models v0.6.9 // indirect 63 | github.com/google/uuid v1.6.0 // indirect 64 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect 65 | github.com/jmespath/go-jmespath v0.4.0 // indirect 66 | github.com/josharian/intern v1.0.0 // indirect 67 | github.com/json-iterator/go v1.1.12 // indirect 68 | github.com/klauspost/compress v1.18.0 // indirect 69 | github.com/mailru/easyjson v0.7.7 // indirect 70 | github.com/mattn/go-colorable v0.1.13 // indirect 71 | github.com/mattn/go-isatty v0.0.20 // indirect 72 | github.com/mitchellh/go-homedir v1.1.0 // indirect 73 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 74 | github.com/modern-go/reflect2 v1.0.2 // indirect 75 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 76 | github.com/opencontainers/go-digest v1.0.0 // indirect 77 | github.com/opencontainers/image-spec v1.1.1 // indirect 78 | github.com/pkg/errors v0.9.1 // indirect 79 | github.com/prometheus/client_golang v1.22.0 // indirect 80 | github.com/prometheus/client_model v0.6.1 // indirect 81 | github.com/prometheus/common v0.62.0 // indirect 82 | github.com/prometheus/procfs v0.15.1 // indirect 83 | github.com/sirupsen/logrus v1.9.3 // indirect 84 | github.com/spf13/pflag v1.0.6 // indirect 85 | github.com/vbatts/tar-split v0.12.1 // indirect 86 | github.com/vdemeester/k8s-pkg-credentialprovider v1.22.4 // indirect 87 | github.com/x448/float16 v0.8.4 // indirect 88 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 89 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect 90 | go.opentelemetry.io/otel v1.35.0 // indirect 91 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect 92 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 // indirect 93 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 94 | go.opentelemetry.io/otel/sdk v1.35.0 // indirect 95 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 96 | go.opentelemetry.io/proto/otlp v1.4.0 // indirect 97 | go.uber.org/multierr v1.11.0 // indirect 98 | golang.org/x/crypto v0.36.0 // indirect 99 | golang.org/x/net v0.38.0 // indirect 100 | golang.org/x/oauth2 v0.30.0 // indirect 101 | golang.org/x/sync v0.14.0 // indirect 102 | golang.org/x/sys v0.33.0 // indirect 103 | golang.org/x/term v0.30.0 // indirect 104 | golang.org/x/text v0.23.0 // indirect 105 | golang.org/x/time v0.9.0 // indirect 106 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 107 | gomodules.xyz/jsonpatch/v3 v3.0.1 // indirect 108 | gomodules.xyz/orderedmap v0.1.0 // indirect 109 | google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect 110 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect 111 | google.golang.org/grpc v1.68.1 // indirect 112 | google.golang.org/protobuf v1.36.5 // indirect 113 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 114 | gopkg.in/inf.v0 v0.9.1 // indirect 115 | gopkg.in/yaml.v3 v3.0.1 // indirect 116 | k8s.io/apiextensions-apiserver v0.33.0 // indirect 117 | k8s.io/cloud-provider v0.28.0 // indirect 118 | k8s.io/component-base v0.33.1 // indirect 119 | k8s.io/klog/v2 v2.130.1 // indirect 120 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 121 | k8s.io/legacy-cloud-providers v0.22.4 // indirect 122 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect 123 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 124 | sigs.k8s.io/randfill v1.0.0 // indirect 125 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 126 | ) 127 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020-2022 VMware Inc. 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 | */ -------------------------------------------------------------------------------- /hack/boilerplate.ytt.txt: -------------------------------------------------------------------------------- 1 | #! Copyright 2020-2022 VMware Inc. 2 | #! 3 | #! Licensed under the Apache License, Version 2.0 (the "License"); 4 | #! you may not use this file except in compliance with the License. 5 | #! You may obtain a copy of the License at 6 | #! 7 | #! http://www.apache.org/licenses/LICENSE-2.0 8 | #! 9 | #! Unless required by applicable law or agreed to in writing, software 10 | #! distributed under the License is distributed on an "AS IS" BASIS, 11 | #! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | #! See the License for the specific language governing permissions and 13 | #! limitations under the License. 14 | -------------------------------------------------------------------------------- /hack/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vmware-tanzu/cartographer-conventions/tools 2 | 3 | go 1.24.2 4 | 5 | require ( 6 | carvel.dev/ytt v0.52.0 7 | golang.org/x/tools v0.33.0 8 | reconciler.io/dies/diegen v0.16.0 9 | sigs.k8s.io/controller-tools v0.18.0 10 | sigs.k8s.io/kustomize/kustomize/v5 v5.6.0 11 | ) 12 | 13 | require ( 14 | github.com/BurntSushi/toml v1.2.1 // indirect 15 | github.com/blang/semver/v4 v4.0.0 // indirect 16 | github.com/cppforlife/cobrautil v0.0.0-20200514214827-bb86e6965d72 // indirect 17 | github.com/cppforlife/go-cli-ui v0.0.0-20200505234325-512793797f05 // indirect 18 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 19 | github.com/fatih/color v1.18.0 // indirect 20 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 21 | github.com/go-errors/errors v1.4.2 // indirect 22 | github.com/go-logr/logr v1.4.2 // indirect 23 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 24 | github.com/go-openapi/jsonreference v0.20.2 // indirect 25 | github.com/go-openapi/swag v0.23.0 // indirect 26 | github.com/gobuffalo/flect v1.0.3 // indirect 27 | github.com/gogo/protobuf v1.3.2 // indirect 28 | github.com/google/gnostic-models v0.6.9 // indirect 29 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 30 | github.com/hashicorp/go-version v1.6.0 // indirect 31 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 32 | github.com/josharian/intern v1.0.0 // indirect 33 | github.com/json-iterator/go v1.1.12 // indirect 34 | github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 // indirect 35 | github.com/mailru/easyjson v0.7.7 // indirect 36 | github.com/mattn/go-colorable v0.1.13 // indirect 37 | github.com/mattn/go-isatty v0.0.20 // indirect 38 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 39 | github.com/modern-go/reflect2 v1.0.2 // indirect 40 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 41 | github.com/pkg/errors v0.9.1 // indirect 42 | github.com/sergi/go-diff v1.2.0 // indirect 43 | github.com/spf13/cobra v1.9.1 // indirect 44 | github.com/spf13/pflag v1.0.6 // indirect 45 | github.com/x448/float16 v0.8.4 // indirect 46 | github.com/xlab/treeprint v1.2.0 // indirect 47 | golang.org/x/mod v0.24.0 // indirect 48 | golang.org/x/net v0.40.0 // indirect 49 | golang.org/x/sync v0.14.0 // indirect 50 | golang.org/x/sys v0.33.0 // indirect 51 | golang.org/x/text v0.25.0 // indirect 52 | google.golang.org/protobuf v1.36.5 // indirect 53 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 54 | gopkg.in/inf.v0 v0.9.1 // indirect 55 | gopkg.in/yaml.v2 v2.4.0 // indirect 56 | gopkg.in/yaml.v3 v3.0.1 // indirect 57 | k8s.io/api v0.33.0 // indirect 58 | k8s.io/apiextensions-apiserver v0.33.0 // indirect 59 | k8s.io/apimachinery v0.33.0 // indirect 60 | k8s.io/code-generator v0.33.0 // indirect 61 | k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 // indirect 62 | k8s.io/klog/v2 v2.130.1 // indirect 63 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 64 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 65 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 66 | sigs.k8s.io/kustomize/api v0.19.0 // indirect 67 | sigs.k8s.io/kustomize/cmd/config v0.19.0 // indirect 68 | sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect 69 | sigs.k8s.io/randfill v1.0.0 // indirect 70 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 71 | sigs.k8s.io/yaml v1.4.0 // indirect 72 | ) 73 | -------------------------------------------------------------------------------- /hack/hello-sbom-layer.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-tanzu/cartographer-conventions/a18491a0abe039656766dcca5f14d0df090a4ae4/hack/hello-sbom-layer.tar.gz -------------------------------------------------------------------------------- /hack/hello.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | LABEL hello=world 3 | COPY boilerplate.go.txt /LICENSE 4 | -------------------------------------------------------------------------------- /hack/hello.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-tanzu/cartographer-conventions/a18491a0abe039656766dcca5f14d0df090a4ae4/hack/hello.tar.gz -------------------------------------------------------------------------------- /hack/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | // This package imports things required by build scripts, to force `go mod` to see them as dependencies 5 | package tools 6 | 7 | import ( 8 | _ "carvel.dev/ytt/cmd/ytt" 9 | _ "golang.org/x/tools/cmd/goimports" 10 | _ "reconciler.io/dies/diegen" 11 | _ "sigs.k8s.io/controller-tools/cmd/controller-gen" 12 | _ "sigs.k8s.io/kustomize/kustomize/v5" 13 | ) 14 | -------------------------------------------------------------------------------- /pkg/apis/conventions/v1alpha1/clusterpodconvention_defaults.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "k8s.io/apimachinery/pkg/runtime" 24 | utilpointer "k8s.io/utils/pointer" 25 | "sigs.k8s.io/controller-runtime/pkg/webhook" 26 | ) 27 | 28 | // +kubebuilder:webhook:path=/mutate-conventions-carto-run-v1alpha1-clusterpodconvention,mutating=true,failurePolicy=fail,sideEffects=none,admissionReviewVersions=v1beta1,groups=conventions.carto.run,resources=clusterpodconventions,verbs=create;update,versions=v1alpha1,name=clusterpodconventions.conventions.carto.run 29 | 30 | type ClusterPodConventionDefaults struct{} 31 | 32 | var _ webhook.CustomDefaulter = &ClusterPodConventionDefaults{} 33 | 34 | // Default implements webhook.Defaulter so a webhook will be registered for the type 35 | func (r *ClusterPodConventionDefaults) Default(ctx context.Context, obj runtime.Object) error { 36 | clusterPodConvention, ok := obj.(*ClusterPodConvention) 37 | if !ok { 38 | return fmt.Errorf("expected a ClusterPodConvention object, but got git %T", obj) 39 | } 40 | return clusterPodConvention.Spec.Default() 41 | } 42 | 43 | func (s *ClusterPodConventionSpec) Default() error { 44 | if s.Priority == "" { 45 | s.Priority = NormalPriority 46 | } 47 | if s.Webhook != nil { 48 | s.Webhook.Default() 49 | } 50 | if s.SelectorTarget == "" { 51 | s.SelectorTarget = PodTemplateSpecLabels 52 | } 53 | return nil 54 | } 55 | 56 | func (s *ClusterPodConventionWebhook) Default() { 57 | if s.ClientConfig.Service != nil { 58 | if s.ClientConfig.Service.Port == nil { 59 | s.ClientConfig.Service.Port = utilpointer.Int32Ptr(443) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pkg/apis/conventions/v1alpha1/clusterpodconvention_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | type PriorityLevel string 25 | type SelectorTargetSource string 26 | 27 | const ( 28 | // EarlyPriority defines Early priority level for ClusterPodConvention 29 | EarlyPriority PriorityLevel = "Early" 30 | // NormalPriority defines Normal priority level for ClusterPodConvention 31 | NormalPriority PriorityLevel = "Normal" 32 | // LatePriority defines Late priority level for ClusterPodConvention 33 | LatePriority PriorityLevel = "Late" 34 | ) 35 | 36 | const ( 37 | PodTemplateSpecLabels SelectorTargetSource = "PodTemplateSpec" 38 | PodIntentLabels SelectorTargetSource = "PodIntent" 39 | ) 40 | 41 | type ClusterPodConventionSpec struct { 42 | // Label selector for workloads. 43 | // It must match the workload's pod template's labels. 44 | Selectors []metav1.LabelSelector `json:"selectors,omitempty"` 45 | // +optional 46 | SelectorTarget SelectorTargetSource `json:"selectorTarget"` 47 | Priority PriorityLevel `json:"priority,omitempty"` 48 | Webhook *ClusterPodConventionWebhook `json:"webhook,omitempty"` 49 | } 50 | 51 | type ClusterPodConventionWebhook struct { 52 | // ClientConfig defines how to communicate with the convention. 53 | ClientConfig admissionregistrationv1.WebhookClientConfig `json:"clientConfig"` 54 | // Certificate references a cert-manager Certificate resource whose CA should be trusted. 55 | Certificate *ClusterPodConventionWebhookCertificate `json:"certificate,omitempty"` 56 | } 57 | 58 | type ClusterPodConventionWebhookCertificate struct { 59 | Namespace string `json:"namespace"` 60 | Name string `json:"name"` 61 | } 62 | 63 | // +kubebuilder:object:root=true 64 | // +kubebuilder:resource:categories="conventions",scope=Cluster 65 | // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` 66 | 67 | type ClusterPodConvention struct { 68 | metav1.TypeMeta `json:",inline"` 69 | metav1.ObjectMeta `json:"metadata,omitempty"` 70 | 71 | Spec ClusterPodConventionSpec `json:"spec"` 72 | } 73 | 74 | // +kubebuilder:object:root=true 75 | 76 | type ClusterPodConventionList struct { 77 | metav1.TypeMeta `json:",inline"` 78 | metav1.ListMeta `json:"metadata,omitempty"` 79 | Items []ClusterPodConvention `json:"items"` 80 | } 81 | 82 | func init() { 83 | SchemeBuilder.Register(&ClusterPodConvention{}, &ClusterPodConventionList{}) 84 | } 85 | -------------------------------------------------------------------------------- /pkg/apis/conventions/v1alpha1/clusterpodconvention_validation.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020-2023 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | "k8s.io/apimachinery/pkg/util/validation/field" 27 | apiserverwebhook "k8s.io/apiserver/pkg/util/webhook" 28 | "sigs.k8s.io/controller-runtime/pkg/webhook" 29 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 30 | ) 31 | 32 | // +kubebuilder:webhook:path=/validate-conventions-carto-run-v1alpha1-clusterpodconvention,mutating=false,failurePolicy=fail,sideEffects=none,admissionReviewVersions=v1beta1,groups=conventions.carto.run,resources=clusterpodconventions,verbs=create;update,versions=v1alpha1,name=clusterpodconventions.conventions.carto.run 33 | 34 | type ClusterPodConventionValidator struct{} 35 | 36 | var ( 37 | _ webhook.CustomValidator = &ClusterPodConventionValidator{} 38 | ) 39 | 40 | // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type 41 | func (r *ClusterPodConventionValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { 42 | clusterPodConvention, ok := obj.(*ClusterPodConvention) 43 | if !ok { 44 | return nil, fmt.Errorf("expected a ClusterPodConvention object, but got git %T", obj) 45 | } 46 | return nil, clusterPodConvention.validate().ToAggregate() 47 | } 48 | 49 | // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type 50 | func (c *ClusterPodConventionValidator) ValidateUpdate(ctx context.Context, old runtime.Object, new runtime.Object) (admission.Warnings, error) { 51 | clusterPodConvention, ok := new.(*ClusterPodConvention) 52 | if !ok { 53 | return nil, fmt.Errorf("expected a ClusterPodConvention object, but got git %T", new) 54 | } 55 | // TODO check for immutable fields 56 | return nil, clusterPodConvention.validate().ToAggregate() 57 | } 58 | 59 | // ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type 60 | func (c *ClusterPodConventionValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { 61 | return nil, nil 62 | } 63 | 64 | func (r *ClusterPodConvention) validate() field.ErrorList { 65 | errs := field.ErrorList{} 66 | errs = append(errs, r.Spec.validate(field.NewPath("spec"))...) 67 | return errs 68 | } 69 | 70 | func (s *ClusterPodConventionSpec) validate(fldPath *field.Path) field.ErrorList { 71 | errs := field.ErrorList{} 72 | 73 | for i := range s.Selectors { 74 | if _, err := metav1.LabelSelectorAsSelector(&s.Selectors[i]); err != nil { 75 | errs = append(errs, field.Invalid(fldPath.Child("selectors").Index(i), s.Selectors[i], "")) 76 | } 77 | } 78 | 79 | if s.Priority != EarlyPriority && s.Priority != LatePriority && s.Priority != NormalPriority { 80 | errs = append(errs, field.Invalid(fldPath.Child("priority"), s.Priority, `The priority value provided is invalid. Accepted priority values include \"Early\" or \"Normal\" or \"Late\". The default value is set to \"Normal\"`)) 81 | } 82 | 83 | // Webhook will be required mutually exclusive of other options that don't exist yet 84 | if s.Webhook == nil { 85 | errs = append(errs, field.Required(fldPath.Child("webhook"), "")) 86 | } else { 87 | errs = append(errs, s.Webhook.validate(fldPath.Child("webhook"))...) 88 | } 89 | 90 | if s.SelectorTarget != PodTemplateSpecLabels && s.SelectorTarget != PodIntentLabels { 91 | errs = append(errs, 92 | field.Invalid(fldPath.Child("selectorTarget"), s.SelectorTarget, 93 | `The value provided for the selectorTarget field is invalid. Accepted selectorTarget values include \"PodIntent\" and \"PodTemplateSpec\". The default value is set to \"PodTemplateSpec\"`), 94 | ) 95 | } 96 | 97 | return errs 98 | } 99 | 100 | func (s *ClusterPodConventionWebhook) validate(fldPath *field.Path) field.ErrorList { 101 | errs := field.ErrorList{} 102 | 103 | errs = append(errs, validateClientConfig(fldPath.Child("clientConfig"), s.ClientConfig)...) 104 | errs = append(errs, s.Certificate.validate(fldPath.Child("certificate"))...) 105 | 106 | return errs 107 | } 108 | 109 | func (s *ClusterPodConventionWebhookCertificate) validate(fldPath *field.Path) field.ErrorList { 110 | errs := field.ErrorList{} 111 | 112 | if s == nil { 113 | return errs 114 | } 115 | if s.Namespace == "" { 116 | errs = append(errs, field.Required(fldPath.Child("namespace"), "")) 117 | } 118 | if s.Name == "" { 119 | errs = append(errs, field.Required(fldPath.Child("name"), "")) 120 | } 121 | 122 | return errs 123 | } 124 | 125 | func validateClientConfig(fldPath *field.Path, clientConfig admissionregistrationv1.WebhookClientConfig) field.ErrorList { 126 | errs := field.ErrorList{} 127 | 128 | switch { 129 | case (clientConfig.URL != nil) && (clientConfig.Service != nil): 130 | errs = append(errs, field.Required(fldPath.Child("[url, service]"), "expected exactly one, got both")) 131 | case (clientConfig.URL == nil) == (clientConfig.Service == nil): 132 | errs = append(errs, field.Required(fldPath.Child("[url, service]"), "expected exactly one, got neither")) 133 | case clientConfig.URL != nil: 134 | errs = append(errs, apiserverwebhook.ValidateWebhookURL(fldPath.Child("url"), *clientConfig.URL, true)...) 135 | case clientConfig.Service != nil: 136 | errs = append(errs, apiserverwebhook.ValidateWebhookService(fldPath.Child("service"), clientConfig.Service.Name, clientConfig.Service.Namespace, 137 | clientConfig.Service.Path, *clientConfig.Service.Port)...) 138 | } 139 | 140 | return errs 141 | } 142 | -------------------------------------------------------------------------------- /pkg/apis/conventions/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 VMware Inc. 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 conventions v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | package v1alpha1 20 | -------------------------------------------------------------------------------- /pkg/apis/conventions/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 VMware Inc. 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 conventions v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=conventions.carto.run 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: "conventions.carto.run", 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 | -------------------------------------------------------------------------------- /pkg/apis/conventions/v1alpha1/podintent_defaults.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "k8s.io/apimachinery/pkg/runtime" 24 | "sigs.k8s.io/controller-runtime/pkg/webhook" 25 | ) 26 | 27 | // +kubebuilder:webhook:path=/mutate-conventions-carto-run-v1alpha1-podintent,mutating=true,failurePolicy=fail,sideEffects=none,admissionReviewVersions=v1beta1,groups=conventions.carto.run,resources=podintents,verbs=create;update,versions=v1alpha1,name=podintents.conventions.carto.run 28 | 29 | type PodIntentDefaulter struct{} 30 | 31 | var _ webhook.CustomDefaulter = &PodIntentDefaulter{} 32 | 33 | // Default implements webhook.Defaulter so a webhook will be registered for the type 34 | func (r *PodIntentDefaulter) Default(ctx context.Context, obj runtime.Object) error { 35 | podIntent, ok := obj.(*PodIntent) 36 | if !ok { 37 | return fmt.Errorf("expected a PodIntent object, but got %T", obj) 38 | } 39 | return podIntent.Spec.Default() 40 | } 41 | 42 | func (s *PodIntentSpec) Default() error { 43 | if s.ServiceAccountName == "" { 44 | s.ServiceAccountName = "default" 45 | } 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /pkg/apis/conventions/v1alpha1/podintent_lifecycle.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "context" 21 | 22 | "reconciler.io/runtime/apis" 23 | ) 24 | 25 | const ( 26 | AppliedConventionsAnnotationKey = "conventions.carto.run/applied-conventions" 27 | ) 28 | 29 | const ( 30 | PodIntentConditionReady = apis.ConditionReady 31 | PodIntentConditionConventionsApplied = "ConventionsApplied" 32 | ) 33 | 34 | var podintentCondSet = apis.NewLivingConditionSetWithHappyReason( 35 | "ConventionsApplied", 36 | PodIntentConditionConventionsApplied, 37 | ) 38 | 39 | func (s *PodIntent) GetConditionsAccessor() apis.ConditionsAccessor { 40 | return &s.Status 41 | } 42 | 43 | func (s *PodIntent) GetConditionSet() apis.ConditionSet { 44 | return podintentCondSet 45 | } 46 | 47 | func (s *PodIntentStatus) InitializeConditions(ctx context.Context) { 48 | conditionManager := podintentCondSet.ManageWithContext(ctx, s) 49 | conditionManager.InitializeConditions() 50 | // reset existing managed conditions 51 | conditionManager.MarkUnknown(PodIntentConditionConventionsApplied, "Initializing", "") 52 | } 53 | -------------------------------------------------------------------------------- /pkg/apis/conventions/v1alpha1/podintent_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020-2023 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "github.com/google/go-cmp/cmp" 24 | corev1 "k8s.io/api/core/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/util/validation/field" 27 | "reconciler.io/runtime/apis" 28 | rtesting "reconciler.io/runtime/testing" 29 | ) 30 | 31 | func TestPodIntentDefault(t *testing.T) { 32 | tests := []struct { 33 | name string 34 | in *PodIntent 35 | want *PodIntent 36 | }{{ 37 | name: "empty", 38 | in: &PodIntent{}, 39 | want: &PodIntent{ 40 | Spec: PodIntentSpec{ 41 | ServiceAccountName: "default", 42 | }, 43 | }, 44 | }} 45 | 46 | for _, test := range tests { 47 | t.Run(test.name, func(t *testing.T) { 48 | got := test.in 49 | _ = got.Spec.Default() 50 | if diff := cmp.Diff(test.want, got); diff != "" { 51 | t.Errorf("Default() (-want, +got) = %v", diff) 52 | } 53 | }) 54 | } 55 | } 56 | 57 | func TestPodIntentValidate(t *testing.T) { 58 | for _, c := range []struct { 59 | name string 60 | target *PodIntent 61 | validator PodIntentValidator 62 | expected field.ErrorList 63 | }{{ 64 | name: "empty", 65 | target: &PodIntent{}, 66 | expected: field.ErrorList{}, 67 | }, { 68 | name: "empty image pull secret", 69 | target: &PodIntent{ 70 | Spec: PodIntentSpec{ 71 | ImagePullSecrets: []corev1.LocalObjectReference{{}}, 72 | }, 73 | }, 74 | expected: field.ErrorList{ 75 | field.Required(field.NewPath("spec", "imagePullSecrets").Index(0).Child("name"), ""), 76 | }, 77 | }} { 78 | t.Run(c.name, func(t *testing.T) { 79 | actual := c.target.validate() 80 | if diff := cmp.Diff(c.expected, actual); diff != "" { 81 | t.Errorf("Validate() (-expected, +actual) = %v", diff) 82 | } 83 | _, create := c.validator.ValidateCreate(context.TODO(), c.target) 84 | if diff := cmp.Diff(c.expected.ToAggregate(), create); diff != "" { 85 | t.Errorf("ValidateCreate() (-expected, +actual) = %v", diff) 86 | } 87 | _, update := c.validator.ValidateUpdate(context.TODO(), nil, c.target) 88 | if diff := cmp.Diff(c.expected.ToAggregate(), update); diff != "" { 89 | t.Errorf("ValidateUpdate() (-expected, +actual) = %v", diff) 90 | } 91 | _, deleteValidation := c.validator.ValidateDelete(context.TODO(), c.target) 92 | if diff := cmp.Diff(nil, deleteValidation); diff != "" { 93 | t.Errorf("ValidateUpdate() (-expected, +actual) = %v", diff) 94 | } 95 | }) 96 | } 97 | } 98 | 99 | func TestPodIntentConditions(t *testing.T) { 100 | for _, c := range []struct { 101 | name string 102 | work func(*PodIntent) 103 | expected *PodIntentStatus 104 | }{{ 105 | name: "initialize", 106 | work: func(s *PodIntent) { 107 | s.Status.InitializeConditions(context.TODO()) 108 | }, 109 | expected: &PodIntentStatus{ 110 | Status: apis.Status{ 111 | Conditions: []metav1.Condition{ 112 | { 113 | Type: PodIntentConditionConventionsApplied, 114 | Status: metav1.ConditionUnknown, 115 | Reason: "Initializing", 116 | }, 117 | { 118 | Type: PodIntentConditionReady, 119 | Status: metav1.ConditionUnknown, 120 | Reason: "Initializing", 121 | }, 122 | }, 123 | }, 124 | }, 125 | }, { 126 | name: "reset", 127 | work: func(s *PodIntent) { 128 | s.GetConditionSet().Manage(s.GetConditionsAccessor()).MarkTrue(PodIntentConditionConventionsApplied, "Applied", "") 129 | s.Status.InitializeConditions(context.Background()) 130 | }, 131 | expected: &PodIntentStatus{ 132 | Status: apis.Status{ 133 | Conditions: []metav1.Condition{ 134 | { 135 | Type: PodIntentConditionConventionsApplied, 136 | Status: metav1.ConditionUnknown, 137 | Reason: "Initializing", 138 | }, 139 | { 140 | Type: PodIntentConditionReady, 141 | Status: metav1.ConditionUnknown, 142 | Reason: "Initializing", 143 | }, 144 | }, 145 | }, 146 | }, 147 | }, { 148 | name: "ready", 149 | work: func(s *PodIntent) { 150 | s.Status.InitializeConditions(context.TODO()) 151 | s.GetConditionSet().Manage(s.GetConditionsAccessor()).MarkTrue(PodIntentConditionConventionsApplied, "Applied", "") 152 | }, 153 | expected: &PodIntentStatus{ 154 | Status: apis.Status{ 155 | Conditions: []metav1.Condition{ 156 | { 157 | Type: PodIntentConditionConventionsApplied, 158 | Status: metav1.ConditionTrue, 159 | Reason: "Applied", 160 | }, 161 | { 162 | Type: PodIntentConditionReady, 163 | Status: metav1.ConditionTrue, 164 | Reason: "ConventionsApplied", 165 | }, 166 | }, 167 | }, 168 | }, 169 | }} { 170 | t.Run(c.name, func(t *testing.T) { 171 | actual := &PodIntent{} 172 | c.work(actual) 173 | if diff := cmp.Diff(c.expected, &actual.Status, rtesting.IgnoreLastTransitionTime); diff != "" { 174 | t.Errorf("(-expected, +actual) = %v", diff) 175 | } 176 | }) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /pkg/apis/conventions/v1alpha1/podintent_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | corev1 "k8s.io/api/core/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | "reconciler.io/runtime/apis" 23 | ) 24 | 25 | type PodIntentSpec struct { 26 | // ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate 27 | // the image pull if the service account has attached pull secrets. For more information: 28 | // https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account 29 | // +optional 30 | ServiceAccountName string `json:"serviceAccountName,omitempty"` 31 | // ImagePullSecrets contains the names of the Kubernetes Secrets containing registry login 32 | // information to resolve image metadata. 33 | // +optional 34 | ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` 35 | // Template defines the workload pod temple 36 | Template PodTemplateSpec `json:"template"` 37 | } 38 | 39 | type PodIntentStatus struct { 40 | apis.Status `json:",inline"` 41 | Template *PodTemplateSpec `json:"template,omitempty"` 42 | } 43 | 44 | // +kubebuilder:object:root=true 45 | // +kubebuilder:subresource:status 46 | // +kubebuilder:resource:categories="conventions" 47 | // +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status` 48 | // +kubebuilder:printcolumn:name="Reason",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason` 49 | // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` 50 | 51 | type PodIntent struct { 52 | metav1.TypeMeta `json:",inline"` 53 | metav1.ObjectMeta `json:"metadata,omitempty"` 54 | 55 | Spec PodIntentSpec `json:"spec"` 56 | // +optional 57 | Status PodIntentStatus `json:"status"` 58 | } 59 | 60 | // +kubebuilder:object:root=true 61 | 62 | type PodIntentList struct { 63 | metav1.TypeMeta `json:",inline"` 64 | metav1.ListMeta `json:"metadata,omitempty"` 65 | Items []PodIntent `json:"items"` 66 | } 67 | 68 | func init() { 69 | SchemeBuilder.Register(&PodIntent{}, &PodIntentList{}) 70 | } 71 | -------------------------------------------------------------------------------- /pkg/apis/conventions/v1alpha1/podintent_validation.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020-2023 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | runtime "k8s.io/apimachinery/pkg/runtime" 24 | "k8s.io/apimachinery/pkg/util/validation/field" 25 | "sigs.k8s.io/controller-runtime/pkg/webhook" 26 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 27 | ) 28 | 29 | // +kubebuilder:webhook:path=/validate-conventions-carto-run-v1alpha1-podintent,mutating=false,failurePolicy=fail,sideEffects=none,admissionReviewVersions=v1beta1,groups=conventions.carto.run,resources=podintents,verbs=create;update,versions=v1alpha1,name=podintents.conventions.carto.run 30 | 31 | type PodIntentValidator struct{} 32 | 33 | var ( 34 | _ webhook.CustomValidator = &PodIntentValidator{} 35 | ) 36 | 37 | // ValidateCreate implements webhook.Validator so a webhook will be registered for the type 38 | func (r *PodIntentValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { 39 | podIntent, ok := obj.(*PodIntent) 40 | if !ok { 41 | return admission.Warnings{}, fmt.Errorf("expected a PodIntent, but got %T", obj) 42 | } 43 | 44 | return nil, podIntent.validate().ToAggregate() 45 | } 46 | 47 | // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type 48 | func (r *PodIntentValidator) ValidateUpdate(ctx context.Context, old runtime.Object, new runtime.Object) (admission.Warnings, error) { 49 | podIntent, ok := new.(*PodIntent) 50 | if !ok { 51 | return admission.Warnings{}, fmt.Errorf("expected a PodIntent, but got %T", new) 52 | } 53 | // TODO check for immutable fields 54 | return nil, podIntent.validate().ToAggregate() 55 | } 56 | 57 | // ValidateDelete implements webhook.Validator so a webhook will be registered for the type 58 | func (r *PodIntentValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { 59 | return nil, nil 60 | } 61 | 62 | func (r *PodIntent) validate() field.ErrorList { 63 | errs := field.ErrorList{} 64 | 65 | errs = append(errs, r.Spec.validate(field.NewPath("spec"))...) 66 | 67 | return errs 68 | } 69 | 70 | func (s *PodIntentSpec) validate(fldPath *field.Path) field.ErrorList { 71 | errs := field.ErrorList{} 72 | 73 | for index, ips := range s.ImagePullSecrets { 74 | if ips.Name == "" { 75 | errs = append(errs, field.Required(fldPath.Child("imagePullSecrets").Index(index).Child("name"), "")) 76 | } 77 | } 78 | // TODO 79 | 80 | return errs 81 | } 82 | -------------------------------------------------------------------------------- /pkg/apis/conventions/v1alpha1/podtemplatespec.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "fmt" 21 | 22 | corev1 "k8s.io/api/core/v1" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/apimachinery/pkg/types" 25 | ) 26 | 27 | var ( 28 | _ metav1.ObjectMetaAccessor = (*PodTemplateSpec)(nil) 29 | _ metav1.Object = (*ObjectMeta)(nil) 30 | ) 31 | 32 | // PodTemplateSpec mirrors corev1.PodTemplateSpec with a simplified ObjectMeta. 33 | // This hacks around an issue with controller-gen where it doesn't generate the 34 | // correct structural scheme that results in all ObjectMeta fields being lost. 35 | // See https://github.com/kubernetes-sigs/controller-tools/issues/448 36 | type PodTemplateSpec struct { 37 | ObjectMeta `json:"metadata,omitempty"` 38 | Spec corev1.PodSpec `json:"spec,omitempty"` 39 | } 40 | 41 | func NewPodTemplateSpec(pts *corev1.PodTemplateSpec) *PodTemplateSpec { 42 | return &PodTemplateSpec{ 43 | ObjectMeta: ObjectMeta{ 44 | Name: pts.Name, 45 | GenerateName: pts.GenerateName, 46 | Namespace: pts.Namespace, 47 | Labels: pts.Labels, 48 | Annotations: pts.Annotations, 49 | }, 50 | Spec: pts.Spec, 51 | } 52 | } 53 | 54 | func (p *PodTemplateSpec) AsPodTemplateSpec() *corev1.PodTemplateSpec { 55 | if p == nil { 56 | return nil 57 | } 58 | return &corev1.PodTemplateSpec{ 59 | ObjectMeta: p.AsObjectMeta(), 60 | Spec: p.Spec, 61 | } 62 | } 63 | 64 | func (p *PodTemplateSpec) GetObjectMeta() metav1.Object { 65 | if p == nil { 66 | return nil 67 | } 68 | return &p.ObjectMeta 69 | } 70 | 71 | type ObjectMeta struct { 72 | Name string `json:"name,omitempty"` 73 | GenerateName string `json:"generateName,omitempty"` 74 | Namespace string `json:"namespace,omitempty"` 75 | Labels map[string]string `json:"labels,omitempty"` 76 | Annotations map[string]string `json:"annotations,omitempty"` 77 | } 78 | 79 | func (m *ObjectMeta) AsObjectMeta() metav1.ObjectMeta { 80 | return metav1.ObjectMeta{ 81 | Name: m.Name, 82 | GenerateName: m.GenerateName, 83 | Namespace: m.Namespace, 84 | Labels: m.Labels, 85 | Annotations: m.Annotations, 86 | } 87 | } 88 | 89 | func (m *ObjectMeta) GetNamespace() string { return m.Namespace } 90 | func (m *ObjectMeta) SetNamespace(namespace string) { m.Namespace = namespace } 91 | func (m *ObjectMeta) GetName() string { return m.Name } 92 | func (m *ObjectMeta) SetName(name string) { m.Name = name } 93 | func (m *ObjectMeta) GetGenerateName() string { return m.GenerateName } 94 | func (m *ObjectMeta) SetGenerateName(name string) { m.GenerateName = name } 95 | func (m *ObjectMeta) GetLabels() map[string]string { return m.Labels } 96 | func (m *ObjectMeta) SetLabels(labels map[string]string) { m.Labels = labels } 97 | func (m *ObjectMeta) GetAnnotations() map[string]string { return m.Annotations } 98 | func (m *ObjectMeta) SetAnnotations(annotations map[string]string) { m.Annotations = annotations } 99 | 100 | func (m *ObjectMeta) GetUID() types.UID { panic(fmt.Errorf("GetUID is not implemented")) } 101 | func (m *ObjectMeta) SetUID(uid types.UID) { panic(fmt.Errorf("SetUID is not implemented")) } 102 | func (m *ObjectMeta) GetResourceVersion() string { 103 | panic(fmt.Errorf("GetResourceVersion is not implemented")) 104 | } 105 | func (m *ObjectMeta) SetResourceVersion(version string) { 106 | panic(fmt.Errorf("SetResourceVersion is not implemented")) 107 | } 108 | func (m *ObjectMeta) GetGeneration() int64 { panic(fmt.Errorf("GetGeneration is not implemented")) } 109 | func (m *ObjectMeta) SetGeneration(generation int64) { 110 | panic(fmt.Errorf("SetGeneration is not implemented")) 111 | } 112 | func (m *ObjectMeta) GetSelfLink() string { 113 | panic(fmt.Errorf("GetSelfLink is not implemented")) 114 | } 115 | func (m *ObjectMeta) SetSelfLink(selfLink string) { 116 | panic(fmt.Errorf("SetSelfLink is not implemented")) 117 | } 118 | func (m *ObjectMeta) GetCreationTimestamp() metav1.Time { 119 | panic(fmt.Errorf("GetCreationTimestamp is not implemented")) 120 | } 121 | func (m *ObjectMeta) SetCreationTimestamp(timestamp metav1.Time) { 122 | panic(fmt.Errorf("SetCreationTimestamp is not implemented")) 123 | } 124 | func (m *ObjectMeta) GetDeletionTimestamp() *metav1.Time { 125 | panic(fmt.Errorf("GetDeletionTimestamp is not implemented")) 126 | } 127 | func (m *ObjectMeta) SetDeletionTimestamp(timestamp *metav1.Time) { 128 | panic(fmt.Errorf("SetDeletionTimestamp is not implemented")) 129 | } 130 | func (m *ObjectMeta) GetDeletionGracePeriodSeconds() *int64 { 131 | panic(fmt.Errorf("GetDeletionGracePeriodSeconds is not implemented")) 132 | } 133 | func (m *ObjectMeta) SetDeletionGracePeriodSeconds(*int64) { 134 | panic(fmt.Errorf("SetDeletionGracePeriodSeconds is not implemented")) 135 | } 136 | func (m *ObjectMeta) GetFinalizers() []string { 137 | panic(fmt.Errorf("GetFinalizers is not implemented")) 138 | } 139 | func (m *ObjectMeta) SetFinalizers(finalizers []string) { 140 | panic(fmt.Errorf("SetFinalizers is not implemented")) 141 | } 142 | func (m *ObjectMeta) GetOwnerReferences() []metav1.OwnerReference { 143 | panic(fmt.Errorf("GetOwnerReferences is not implemented")) 144 | } 145 | func (m *ObjectMeta) SetOwnerReferences([]metav1.OwnerReference) { 146 | panic(fmt.Errorf("SetOwnerReferences is not implemented")) 147 | } 148 | func (m *ObjectMeta) GetManagedFields() []metav1.ManagedFieldsEntry { 149 | panic(fmt.Errorf("GetManagedFields is not implemented")) 150 | } 151 | func (m *ObjectMeta) SetManagedFields(managedFields []metav1.ManagedFieldsEntry) { 152 | panic(fmt.Errorf("SetManagedFields is not implemented")) 153 | } 154 | -------------------------------------------------------------------------------- /pkg/apis/interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 VMware Inc. 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 apis 18 | 19 | import ( 20 | "reconciler.io/runtime/apis" 21 | ) 22 | 23 | // ConditionSetAccessor is the interface for a Resource that accesses the 24 | // condition set managing conditions for the resource. 25 | type ConditionSetAccessor interface { 26 | GetConditionSet() apis.ConditionSet 27 | } 28 | -------------------------------------------------------------------------------- /pkg/apis/thirdparty/cert-manager/v1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 contains API Schema definition for the cert-manager.io/v1 API group 18 | 19 | // This API group is a forked subset of https://github.com/jetstack/cert-manager/tree/v1.4.1/pkg/apis/certmanager/v1 20 | // It is indended to enable interaction with the Cert Manager API without including unnecessary dependencies. 21 | 22 | // Types have been lightly edited to avoid dependencies and support kubebuilder. 23 | 24 | // +kubebuilder:object:generate=true 25 | package v1 26 | -------------------------------------------------------------------------------- /pkg/apis/thirdparty/cert-manager/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The cert-manager Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package v1 17 | 18 | import ( 19 | "k8s.io/apimachinery/pkg/runtime/schema" 20 | "sigs.k8s.io/controller-runtime/pkg/scheme" 21 | ) 22 | 23 | var ( 24 | // SchemeGroupVersion is group version used to register these objects 25 | SchemeGroupVersion = schema.GroupVersion{Group: "cert-manager.io", Version: "v1"} 26 | 27 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 28 | SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} 29 | 30 | // AddToScheme adds the types in this group-version to the given scheme. 31 | AddToScheme = SchemeBuilder.AddToScheme 32 | ) 33 | -------------------------------------------------------------------------------- /pkg/apis/thirdparty/cert-manager/v1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | /* 4 | Copyright 2020-2022 VMware Inc. 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 v1 22 | 23 | import ( 24 | metav1 "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 *CertificateRequest) DeepCopyInto(out *CertificateRequest) { 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 CertificateRequest. 38 | func (in *CertificateRequest) DeepCopy() *CertificateRequest { 39 | if in == nil { 40 | return nil 41 | } 42 | out := new(CertificateRequest) 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 *CertificateRequest) 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 *CertificateRequestCondition) DeepCopyInto(out *CertificateRequestCondition) { 57 | *out = *in 58 | if in.LastTransitionTime != nil { 59 | in, out := &in.LastTransitionTime, &out.LastTransitionTime 60 | *out = (*in).DeepCopy() 61 | } 62 | } 63 | 64 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestCondition. 65 | func (in *CertificateRequestCondition) DeepCopy() *CertificateRequestCondition { 66 | if in == nil { 67 | return nil 68 | } 69 | out := new(CertificateRequestCondition) 70 | in.DeepCopyInto(out) 71 | return out 72 | } 73 | 74 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 75 | func (in *CertificateRequestList) DeepCopyInto(out *CertificateRequestList) { 76 | *out = *in 77 | out.TypeMeta = in.TypeMeta 78 | in.ListMeta.DeepCopyInto(&out.ListMeta) 79 | if in.Items != nil { 80 | in, out := &in.Items, &out.Items 81 | *out = make([]CertificateRequest, len(*in)) 82 | for i := range *in { 83 | (*in)[i].DeepCopyInto(&(*out)[i]) 84 | } 85 | } 86 | } 87 | 88 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestList. 89 | func (in *CertificateRequestList) DeepCopy() *CertificateRequestList { 90 | if in == nil { 91 | return nil 92 | } 93 | out := new(CertificateRequestList) 94 | in.DeepCopyInto(out) 95 | return out 96 | } 97 | 98 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 99 | func (in *CertificateRequestList) DeepCopyObject() runtime.Object { 100 | if c := in.DeepCopy(); c != nil { 101 | return c 102 | } 103 | return nil 104 | } 105 | 106 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 107 | func (in *CertificateRequestSpec) DeepCopyInto(out *CertificateRequestSpec) { 108 | *out = *in 109 | if in.Duration != nil { 110 | in, out := &in.Duration, &out.Duration 111 | *out = new(metav1.Duration) 112 | **out = **in 113 | } 114 | out.IssuerRef = in.IssuerRef 115 | if in.Request != nil { 116 | in, out := &in.Request, &out.Request 117 | *out = make([]byte, len(*in)) 118 | copy(*out, *in) 119 | } 120 | if in.Usages != nil { 121 | in, out := &in.Usages, &out.Usages 122 | *out = make([]KeyUsage, len(*in)) 123 | copy(*out, *in) 124 | } 125 | if in.Groups != nil { 126 | in, out := &in.Groups, &out.Groups 127 | *out = make([]string, len(*in)) 128 | copy(*out, *in) 129 | } 130 | if in.Extra != nil { 131 | in, out := &in.Extra, &out.Extra 132 | *out = make(map[string][]string, len(*in)) 133 | for key, val := range *in { 134 | var outVal []string 135 | if val == nil { 136 | (*out)[key] = nil 137 | } else { 138 | inVal := (*in)[key] 139 | in, out := &inVal, &outVal 140 | *out = make([]string, len(*in)) 141 | copy(*out, *in) 142 | } 143 | (*out)[key] = outVal 144 | } 145 | } 146 | } 147 | 148 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestSpec. 149 | func (in *CertificateRequestSpec) DeepCopy() *CertificateRequestSpec { 150 | if in == nil { 151 | return nil 152 | } 153 | out := new(CertificateRequestSpec) 154 | in.DeepCopyInto(out) 155 | return out 156 | } 157 | 158 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 159 | func (in *CertificateRequestStatus) DeepCopyInto(out *CertificateRequestStatus) { 160 | *out = *in 161 | if in.Conditions != nil { 162 | in, out := &in.Conditions, &out.Conditions 163 | *out = make([]CertificateRequestCondition, len(*in)) 164 | for i := range *in { 165 | (*in)[i].DeepCopyInto(&(*out)[i]) 166 | } 167 | } 168 | if in.Certificate != nil { 169 | in, out := &in.Certificate, &out.Certificate 170 | *out = make([]byte, len(*in)) 171 | copy(*out, *in) 172 | } 173 | if in.CA != nil { 174 | in, out := &in.CA, &out.CA 175 | *out = make([]byte, len(*in)) 176 | copy(*out, *in) 177 | } 178 | if in.FailureTime != nil { 179 | in, out := &in.FailureTime, &out.FailureTime 180 | *out = (*in).DeepCopy() 181 | } 182 | } 183 | 184 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestStatus. 185 | func (in *CertificateRequestStatus) DeepCopy() *CertificateRequestStatus { 186 | if in == nil { 187 | return nil 188 | } 189 | out := new(CertificateRequestStatus) 190 | in.DeepCopyInto(out) 191 | return out 192 | } 193 | -------------------------------------------------------------------------------- /pkg/binding/convention.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020-2023 VMware Inc. 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 binding 18 | 19 | import ( 20 | "context" 21 | 22 | admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/apiserver/pkg/util/webhook" 25 | webhookutil "k8s.io/apiserver/pkg/util/webhook" 26 | 27 | conventionsv1alpha1 "github.com/vmware-tanzu/cartographer-conventions/pkg/apis/conventions/v1alpha1" 28 | webhookv1alpha1 "github.com/vmware-tanzu/cartographer-conventions/webhook/api/v1alpha1" 29 | ) 30 | 31 | type Convention struct { 32 | Name string 33 | SelectorTarget conventionsv1alpha1.SelectorTargetSource 34 | Selectors []metav1.LabelSelector 35 | Priority conventionsv1alpha1.PriorityLevel 36 | ClientConfig admissionregistrationv1.WebhookClientConfig 37 | } 38 | 39 | func (o *Convention) Apply(ctx context.Context, conventionRequest *webhookv1alpha1.PodConventionContext, wc WebhookConfig) (*webhookv1alpha1.PodConventionContext, error) { 40 | cc := o.WebhookClientConfig() 41 | cm, err := NewClientManager(wc, webhookv1alpha1.GroupVersion, webhookv1alpha1.AddToScheme) 42 | if err != nil { 43 | return nil, err 44 | } 45 | webClient, err := cm.HookClient(cc) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | r := webClient.Post().Body(conventionRequest) 51 | enrichedIntent := &webhookv1alpha1.PodConventionContext{} 52 | res := r.Do(ctx) 53 | if res.Error() != nil { 54 | return nil, res.Error() 55 | } 56 | if err := res.Into(enrichedIntent); err != nil { 57 | return nil, err 58 | } 59 | return enrichedIntent, nil 60 | } 61 | 62 | func (o *Convention) WebhookClientConfig() webhook.ClientConfig { 63 | cc := webhook.ClientConfig{ 64 | Name: o.Name, 65 | CABundle: o.ClientConfig.CABundle, 66 | } 67 | if o.ClientConfig.URL != nil { 68 | cc.URL = *o.ClientConfig.URL 69 | } 70 | if o.ClientConfig.Service != nil { 71 | cc.Service = &webhookutil.ClientConfigService{ 72 | Name: o.ClientConfig.Service.Name, 73 | Namespace: o.ClientConfig.Service.Namespace, 74 | } 75 | if o.ClientConfig.Service.Path != nil { 76 | cc.Service.Path = *o.ClientConfig.Service.Path 77 | } 78 | if o.ClientConfig.Service.Port != nil { 79 | cc.Service.Port = *o.ClientConfig.Service.Port 80 | } 81 | } 82 | return cc 83 | } 84 | -------------------------------------------------------------------------------- /pkg/binding/conventions.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 VMware Inc. 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 binding 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "sort" 23 | "strings" 24 | 25 | "github.com/go-logr/logr" 26 | "github.com/google/go-cmp/cmp" 27 | "github.com/google/go-cmp/cmp/cmpopts" 28 | corev1 "k8s.io/api/core/v1" 29 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 | "k8s.io/apimachinery/pkg/labels" 31 | 32 | conventionsv1alpha1 "github.com/vmware-tanzu/cartographer-conventions/pkg/apis/conventions/v1alpha1" 33 | webhookv1alpha1 "github.com/vmware-tanzu/cartographer-conventions/webhook/api/v1alpha1" 34 | ) 35 | 36 | type Conventions []Convention 37 | 38 | func (c *Conventions) FilterAndSort(collectedLabels map[string]labels.Set) (Conventions, error) { 39 | filteredConventions, err := c.Filter(collectedLabels) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return filteredConventions.Sort(), nil 44 | } 45 | 46 | func (c *Conventions) Filter(collectedLabels map[string]labels.Set) (Conventions, error) { 47 | originalOrder := *c 48 | 49 | var filteredSources Conventions 50 | for _, source := range originalOrder { 51 | selectors := source.Selectors 52 | if len(selectors) == 0 { 53 | selectors = []metav1.LabelSelector{ 54 | // an empty selector matches everything 55 | {}, 56 | } 57 | } 58 | for _, selector := range selectors { 59 | sourceLabels, err := metav1.LabelSelectorAsSelector(&selector) 60 | if err != nil { 61 | return nil, fmt.Errorf("unable to convert label selector for ClusterPodConvention %q: %v", source.Name, err) 62 | } 63 | 64 | if sourceLabels.Matches(collectedLabels[string(source.SelectorTarget)]) { 65 | filteredSources = append(filteredSources, source) 66 | break 67 | } 68 | } 69 | } 70 | 71 | return filteredSources, nil 72 | } 73 | 74 | func (c *Conventions) Sort() Conventions { 75 | originalConventions := *c 76 | sort.Slice(originalConventions, func(i, j int) bool { 77 | a := originalConventions[i] 78 | b := originalConventions[j] 79 | if a.Priority == b.Priority { 80 | return a.Name < b.Name 81 | } 82 | return a.Priority == conventionsv1alpha1.EarlyPriority || b.Priority == conventionsv1alpha1.LatePriority 83 | }) 84 | return originalConventions 85 | } 86 | 87 | func (c *Conventions) Apply(ctx context.Context, 88 | parent *conventionsv1alpha1.PodIntent, 89 | wc WebhookConfig, 90 | rc RegistryConfig, 91 | ) (*corev1.PodTemplateSpec, error) { 92 | log := logr.FromContextOrDiscard(ctx) 93 | if parent == nil { 94 | return nil, fmt.Errorf("PodIntent value cannot be nil") 95 | } 96 | workload := parent.Spec.Template.AsPodTemplateSpec() 97 | appliedConventions := []string{} 98 | if str := workload.Annotations[conventionsv1alpha1.AppliedConventionsAnnotationKey]; str != "" { 99 | appliedConventions = strings.Split(str, "\n") 100 | } 101 | for _, convention := range *c { 102 | // fetch metadata for workload 103 | imageConfigList, err := rc.ResolveImageMetadata(ctx, workload) 104 | if err != nil { 105 | log.Error(err, "fetching metadata for Images failed") 106 | return nil, fmt.Errorf("failed to fetch metadata for Images: %v", err) 107 | } 108 | conventionRequestObj := &webhookv1alpha1.PodConventionContext{ 109 | ObjectMeta: metav1.ObjectMeta{ 110 | Name: fmt.Sprintf("%s-%s", parent.GetName(), convention.Name), 111 | }, 112 | Spec: webhookv1alpha1.PodConventionContextSpec{ 113 | ImageConfig: imageConfigList, 114 | Template: *workload, 115 | }, 116 | } 117 | conventionResp, err := convention.Apply(ctx, conventionRequestObj, wc) 118 | if err != nil { 119 | log.Error(err, "failed to apply convention", "Convention", convention) 120 | return nil, fmt.Errorf("failed to apply convention with name %s: %s", convention.Name, err.Error()) 121 | } 122 | workloadDiff := cmp.Diff(workload, conventionResp.Status.Template, cmpopts.EquateEmpty()) 123 | log.Info("applied convention", "diff", workloadDiff, "convention", convention.Name) 124 | 125 | workload = &conventionResp.Status.Template // update pod spec before calling another webhook 126 | 127 | for _, appliedConvention := range conventionResp.Status.AppliedConventions { 128 | labelWithPrefix := fmt.Sprintf("%s/%s", convention.Name, appliedConvention) 129 | // append to the original list so that an convention cannot remove the history 130 | appliedConventions = append(appliedConventions, labelWithPrefix) 131 | } 132 | if workload.Annotations == nil { 133 | workload.Annotations = map[string]string{} 134 | } 135 | workload.Annotations[conventionsv1alpha1.AppliedConventionsAnnotationKey] = strings.Join(appliedConventions, "\n") 136 | } 137 | return workload, nil 138 | } 139 | -------------------------------------------------------------------------------- /pkg/binding/webhook_config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 VMware Inc. 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 binding 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/runtime" 21 | "k8s.io/apimachinery/pkg/runtime/schema" 22 | "k8s.io/apiserver/pkg/util/webhook" 23 | webhookutil "k8s.io/apiserver/pkg/util/webhook" 24 | ) 25 | 26 | type WebhookConfig struct { 27 | AuthInfoResolver webhookutil.AuthenticationInfoResolver 28 | ServiceResolver webhookutil.ServiceResolver 29 | } 30 | 31 | func NewClientManager(wc WebhookConfig, grp schema.GroupVersion, addToSchemaFunc func(s *runtime.Scheme) error) (cm webhook.ClientManager, err error) { 32 | cm, err = webhookutil.NewClientManager([]schema.GroupVersion{grp}, addToSchemaFunc) 33 | if err != nil { 34 | return 35 | } 36 | cm.SetAuthenticationInfoResolver(wc.AuthInfoResolver) 37 | cm.SetServiceResolver(wc.ServiceResolver) 38 | return 39 | } 40 | -------------------------------------------------------------------------------- /pkg/dies/cert-manager/v1/certificaterequest.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | diecorev1 "reconciler.io/dies/apis/core/v1" 21 | diemetav1 "reconciler.io/dies/apis/meta/v1" 22 | 23 | certmanagerv1 "github.com/vmware-tanzu/cartographer-conventions/pkg/apis/thirdparty/cert-manager/v1" 24 | ) 25 | 26 | // +die:object=true 27 | type _ = certmanagerv1.CertificateRequest 28 | 29 | // +die 30 | type _ = certmanagerv1.CertificateRequestSpec 31 | 32 | func (d *CertificateRequestSpecDie) IssuerRefDie(fn func(d *diecorev1.ObjectReferenceDie)) *CertificateRequestSpecDie { 33 | return d.DieStamp(func(r *certmanagerv1.CertificateRequestSpec) { 34 | d := diecorev1.ObjectReferenceBlank. 35 | DieImmutable(false). 36 | DieFeed(r.IssuerRef) 37 | fn(d) 38 | r.IssuerRef = d.DieRelease() 39 | }) 40 | } 41 | 42 | func (d *CertificateRequestSpecDie) AddExtra(key string, values ...string) *CertificateRequestSpecDie { 43 | return d.DieStamp(func(r *certmanagerv1.CertificateRequestSpec) { 44 | if r.Extra == nil { 45 | r.Extra = make(map[string][]string) 46 | } 47 | r.Extra[key] = values 48 | }) 49 | } 50 | 51 | // +die 52 | type _ = certmanagerv1.CertificateRequestStatus 53 | 54 | func (d *CertificateRequestStatusDie) ConditionsDie(conditions ...*diemetav1.ConditionDie) *CertificateRequestStatusDie { 55 | return d.DieStamp(func(r *certmanagerv1.CertificateRequestStatus) { 56 | r.Conditions = make([]certmanagerv1.CertificateRequestCondition, len(conditions)) 57 | for i := range conditions { 58 | c := conditions[i].DieRelease() 59 | // coerce metav1.Condition to certmanagerv1.CertificateRequestCondition 60 | r.Conditions[i] = certmanagerv1.CertificateRequestCondition{ 61 | Type: certmanagerv1.CertificateRequestConditionType(c.Type), 62 | Status: c.Status, 63 | Reason: c.Reason, 64 | Message: c.Message, 65 | LastTransitionTime: &c.LastTransitionTime, 66 | } 67 | } 68 | }) 69 | } 70 | 71 | var ( 72 | CertificateRequestConditionReadyBlank = diemetav1.ConditionBlank.Type(string(certmanagerv1.CertificateRequestConditionReady)) 73 | CertificateRequestConditionInvalidRequestBlank = diemetav1.ConditionBlank.Type(string(certmanagerv1.CertificateRequestConditionInvalidRequest)) 74 | CertificateRequestConditionApprovedBlank = diemetav1.ConditionBlank.Type(string(certmanagerv1.CertificateRequestConditionApproved)) 75 | CertificateRequestConditionDeniedBlank = diemetav1.ConditionBlank.Type(string(certmanagerv1.CertificateRequestConditionDenied)) 76 | ) 77 | -------------------------------------------------------------------------------- /pkg/dies/cert-manager/v1/zz_generated.die_test.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright 2020-2022 VMware Inc. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by diegen. DO NOT EDIT. 21 | 22 | package v1alpha1 23 | 24 | import ( 25 | testingx "testing" 26 | 27 | testing "reconciler.io/dies/testing" 28 | ) 29 | 30 | func TestCertificateRequestDie_MissingMethods(t *testingx.T) { 31 | die := CertificateRequestBlank 32 | ignore := []string{"TypeMeta", "ObjectMeta"} 33 | diff := testing.DieFieldDiff(die).Delete(ignore...) 34 | if diff.Len() != 0 { 35 | t.Errorf("found missing fields for CertificateRequestDie: %s", diff.List()) 36 | } 37 | } 38 | 39 | func TestCertificateRequestSpecDie_MissingMethods(t *testingx.T) { 40 | die := CertificateRequestSpecBlank 41 | ignore := []string{} 42 | diff := testing.DieFieldDiff(die).Delete(ignore...) 43 | if diff.Len() != 0 { 44 | t.Errorf("found missing fields for CertificateRequestSpecDie: %s", diff.List()) 45 | } 46 | } 47 | 48 | func TestCertificateRequestStatusDie_MissingMethods(t *testingx.T) { 49 | die := CertificateRequestStatusBlank 50 | ignore := []string{} 51 | diff := testing.DieFieldDiff(die).Delete(ignore...) 52 | if diff.Len() != 0 { 53 | t.Errorf("found missing fields for CertificateRequestStatusDie: %s", diff.List()) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /pkg/dies/conventions/v1alpha1/clusterpodconvention.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | dieadmissionregistrationv1 "reconciler.io/dies/apis/admissionregistration/v1" 22 | diemetav1 "reconciler.io/dies/apis/meta/v1" 23 | 24 | conventionsv1alpha1 "github.com/vmware-tanzu/cartographer-conventions/pkg/apis/conventions/v1alpha1" 25 | ) 26 | 27 | // +die:object=true 28 | type _ = conventionsv1alpha1.ClusterPodConvention 29 | 30 | // +die 31 | type _ = conventionsv1alpha1.ClusterPodConventionSpec 32 | 33 | func (d *ClusterPodConventionSpecDie) SelectorsDie(selectors ...*diemetav1.LabelSelectorDie) *ClusterPodConventionSpecDie { 34 | return d.DieStamp(func(r *conventionsv1alpha1.ClusterPodConventionSpec) { 35 | r.Selectors = make([]metav1.LabelSelector, len(selectors)) 36 | for i := range selectors { 37 | r.Selectors[i] = selectors[i].DieRelease() 38 | } 39 | }) 40 | } 41 | 42 | func (d *ClusterPodConventionSpecDie) WebookDie(fn func(d *ClusterPodConventionWebhookDie)) *ClusterPodConventionSpecDie { 43 | return d.DieStamp(func(r *conventionsv1alpha1.ClusterPodConventionSpec) { 44 | d := ClusterPodConventionWebhookBlank. 45 | DieImmutable(false). 46 | DieFeedPtr(r.Webhook) 47 | fn(d) 48 | r.Webhook = d.DieReleasePtr() 49 | }) 50 | } 51 | 52 | // +die 53 | type _ = conventionsv1alpha1.ClusterPodConventionWebhook 54 | 55 | func (d *ClusterPodConventionWebhookDie) ClientConfigDie(fn func(d *dieadmissionregistrationv1.WebhookClientConfigDie)) *ClusterPodConventionWebhookDie { 56 | return d.DieStamp(func(r *conventionsv1alpha1.ClusterPodConventionWebhook) { 57 | d := dieadmissionregistrationv1.WebhookClientConfigBlank. 58 | DieImmutable(false). 59 | DieFeed(r.ClientConfig) 60 | fn(d) 61 | r.ClientConfig = d.DieRelease() 62 | }) 63 | } 64 | 65 | func (d *ClusterPodConventionWebhookDie) CertificateDie(fn func(d *ClusterPodConventionWebhookCertificateDie)) *ClusterPodConventionWebhookDie { 66 | return d.DieStamp(func(r *conventionsv1alpha1.ClusterPodConventionWebhook) { 67 | d := ClusterPodConventionWebhookCertificateBlank. 68 | DieImmutable(false). 69 | DieFeedPtr(r.Certificate) 70 | fn(d) 71 | r.Certificate = d.DieReleasePtr() 72 | }) 73 | } 74 | 75 | // +die 76 | type _ = conventionsv1alpha1.ClusterPodConventionWebhookCertificate 77 | -------------------------------------------------------------------------------- /pkg/dies/conventions/v1alpha1/podintent.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | corev1 "k8s.io/api/core/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | diecorev1 "reconciler.io/dies/apis/core/v1" 23 | diemetav1 "reconciler.io/dies/apis/meta/v1" 24 | 25 | conventionsv1alpha1 "github.com/vmware-tanzu/cartographer-conventions/pkg/apis/conventions/v1alpha1" 26 | ) 27 | 28 | // +die:object=true 29 | type _ = conventionsv1alpha1.PodIntent 30 | 31 | // +die 32 | type _ = conventionsv1alpha1.PodIntentSpec 33 | 34 | func (d *PodIntentSpecDie) ImagePullSecretsDie(pullSecrets ...*diecorev1.LocalObjectReferenceDie) *PodIntentSpecDie { 35 | return d.DieStamp(func(r *conventionsv1alpha1.PodIntentSpec) { 36 | r.ImagePullSecrets = make([]corev1.LocalObjectReference, len(pullSecrets)) 37 | for i := range pullSecrets { 38 | r.ImagePullSecrets[i] = pullSecrets[i].DieRelease() 39 | } 40 | }) 41 | } 42 | 43 | func (d *PodIntentSpecDie) TemplateDie(fn func(d *diecorev1.PodTemplateSpecDie)) *PodIntentSpecDie { 44 | return d.DieStamp(func(r *conventionsv1alpha1.PodIntentSpec) { 45 | d := diecorev1.PodTemplateSpecBlank. 46 | DieImmutable(false). 47 | DieFeedPtr(r.Template.AsPodTemplateSpec()) 48 | fn(d) 49 | r.Template = *conventionsv1alpha1.NewPodTemplateSpec(d.DieReleasePtr()) 50 | }) 51 | } 52 | 53 | // +die 54 | type _ = conventionsv1alpha1.PodIntentStatus 55 | 56 | func (d *PodIntentStatusDie) ConditionsDie(conditions ...*diemetav1.ConditionDie) *PodIntentStatusDie { 57 | return d.DieStamp(func(r *conventionsv1alpha1.PodIntentStatus) { 58 | r.Conditions = make([]metav1.Condition, len(conditions)) 59 | for i := range conditions { 60 | r.Conditions[i] = conditions[i].DieRelease() 61 | } 62 | }) 63 | } 64 | 65 | func (d *PodIntentStatusDie) ObservedGeneration(generation int64) *PodIntentStatusDie { 66 | return d.DieStamp(func(r *conventionsv1alpha1.PodIntentStatus) { 67 | r.ObservedGeneration = generation 68 | }) 69 | } 70 | 71 | func (d *PodIntentStatusDie) TemplateDie(fn func(d *diecorev1.PodTemplateSpecDie)) *PodIntentStatusDie { 72 | return d.DieStamp(func(r *conventionsv1alpha1.PodIntentStatus) { 73 | d := diecorev1.PodTemplateSpecBlank. 74 | DieImmutable(false). 75 | DieFeedPtr(r.Template.AsPodTemplateSpec()) 76 | fn(d) 77 | r.Template = conventionsv1alpha1.NewPodTemplateSpec(d.DieReleasePtr()) 78 | }) 79 | } 80 | 81 | var ( 82 | PodIntentConditionReadyBlank = diemetav1.ConditionBlank.Type(conventionsv1alpha1.PodIntentConditionReady) 83 | PodIntentConditionConventionsAppliedBlank = diemetav1.ConditionBlank.Type(conventionsv1alpha1.PodIntentConditionConventionsApplied) 84 | ) 85 | -------------------------------------------------------------------------------- /pkg/dies/conventions/v1alpha1/zz_generated.die_test.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright 2020-2022 VMware Inc. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by diegen. DO NOT EDIT. 21 | 22 | package v1alpha1 23 | 24 | import ( 25 | testingx "testing" 26 | 27 | testing "reconciler.io/dies/testing" 28 | ) 29 | 30 | func TestClusterPodConventionDie_MissingMethods(t *testingx.T) { 31 | die := ClusterPodConventionBlank 32 | ignore := []string{"TypeMeta", "ObjectMeta"} 33 | diff := testing.DieFieldDiff(die).Delete(ignore...) 34 | if diff.Len() != 0 { 35 | t.Errorf("found missing fields for ClusterPodConventionDie: %s", diff.List()) 36 | } 37 | } 38 | 39 | func TestClusterPodConventionSpecDie_MissingMethods(t *testingx.T) { 40 | die := ClusterPodConventionSpecBlank 41 | ignore := []string{} 42 | diff := testing.DieFieldDiff(die).Delete(ignore...) 43 | if diff.Len() != 0 { 44 | t.Errorf("found missing fields for ClusterPodConventionSpecDie: %s", diff.List()) 45 | } 46 | } 47 | 48 | func TestClusterPodConventionWebhookDie_MissingMethods(t *testingx.T) { 49 | die := ClusterPodConventionWebhookBlank 50 | ignore := []string{} 51 | diff := testing.DieFieldDiff(die).Delete(ignore...) 52 | if diff.Len() != 0 { 53 | t.Errorf("found missing fields for ClusterPodConventionWebhookDie: %s", diff.List()) 54 | } 55 | } 56 | 57 | func TestClusterPodConventionWebhookCertificateDie_MissingMethods(t *testingx.T) { 58 | die := ClusterPodConventionWebhookCertificateBlank 59 | ignore := []string{} 60 | diff := testing.DieFieldDiff(die).Delete(ignore...) 61 | if diff.Len() != 0 { 62 | t.Errorf("found missing fields for ClusterPodConventionWebhookCertificateDie: %s", diff.List()) 63 | } 64 | } 65 | 66 | func TestPodIntentDie_MissingMethods(t *testingx.T) { 67 | die := PodIntentBlank 68 | ignore := []string{"TypeMeta", "ObjectMeta"} 69 | diff := testing.DieFieldDiff(die).Delete(ignore...) 70 | if diff.Len() != 0 { 71 | t.Errorf("found missing fields for PodIntentDie: %s", diff.List()) 72 | } 73 | } 74 | 75 | func TestPodIntentSpecDie_MissingMethods(t *testingx.T) { 76 | die := PodIntentSpecBlank 77 | ignore := []string{} 78 | diff := testing.DieFieldDiff(die).Delete(ignore...) 79 | if diff.Len() != 0 { 80 | t.Errorf("found missing fields for PodIntentSpecDie: %s", diff.List()) 81 | } 82 | } 83 | 84 | func TestPodIntentStatusDie_MissingMethods(t *testingx.T) { 85 | die := PodIntentStatusBlank 86 | ignore := []string{} 87 | diff := testing.DieFieldDiff(die).Delete(ignore...) 88 | if diff.Len() != 0 { 89 | t.Errorf("found missing fields for PodIntentStatusDie: %s", diff.List()) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /samples/convention-server/README.md: -------------------------------------------------------------------------------- 1 | # Sample convention webserver 2 | 3 | The image contains a simple Go webserver `server.go`, that will by 4 | default, listens on port `9000` and expose a service at `/`. 5 | 6 | The webserver can only be called with request body of type `PodConventionContext` of APIGroup `webhooks.conventions.carto.run`. If not, server responds with 400 status code. 7 | When called with correct request body type, the server emits a response of object type `PodConventionContext` of APIGroup `webhooks.conventions.carto.run` and for this sample, appends an environment variable. 8 | 9 | ## Trying out 10 | 11 | Build and run the convention server: 12 | 13 | ```sh 14 | # either, from source: 15 | ko apply -f server.yaml 16 | 17 | # or, from a release distribution: 18 | kubectl create -f <(kbld -f server.yaml -f ../.imgpkg/images.yml) 19 | ``` 20 | 21 | To verify the setup, add an Workload and check the status 22 | 23 | ```sh 24 | kubectl create -f workload.yaml 25 | ``` 26 | 27 | ```sh 28 | kubectl get podintents.conventions.carto.run sample -oyaml 29 | ``` 30 | 31 | If everything works correctly, the status will contain a transformed template that includes an environment variable added by the convention server. 32 | 33 | ```yaml 34 | apiVersion: conventions.carto.run/v1alpha1 35 | kind: PodIntent 36 | metadata: 37 | creationTimestamp: "2021-01-20T02:06:33Z" 38 | generation: 1 39 | name: sample 40 | namespace: default 41 | resourceVersion: "6978954" 42 | selfLink: /apis/conventions.carto.run/v1alpha1/namespaces/default/podintents/sample 43 | uid: d8ade195-7f9d-4694-99b1-47b01052461b 44 | spec: 45 | template: 46 | metadata: {} 47 | spec: 48 | containers: 49 | - image: ubuntu 50 | name: workload 51 | resources: {} 52 | status: 53 | conditions: 54 | - lastTransitionTime: "2021-01-20T02:06:34Z" 55 | status: "True" 56 | type: ConventionsApplied 57 | - lastTransitionTime: "2021-01-20T02:06:34Z" 58 | status: "True" 59 | type: Ready 60 | observedGeneration: 1 61 | template: 62 | metadata: 63 | annotations: 64 | conventions.carto.run/applied-conventions: sample/add-env-var 65 | spec: 66 | containers: 67 | - image: ubuntu 68 | name: workload 69 | env: 70 | - name: CONVENTION_SERVER 71 | value: HELLO FROM CONVENTION 72 | resources: {} 73 | ``` 74 | -------------------------------------------------------------------------------- /samples/convention-server/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vmware-tanzu/cartographer-conventions/samples/convention-server 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | replace github.com/vmware-tanzu/cartographer-conventions/webhook => ../../webhook 8 | 9 | require ( 10 | github.com/go-logr/logr v1.4.3 11 | github.com/go-logr/zapr v1.3.0 12 | github.com/vmware-tanzu/cartographer-conventions/webhook v0.5.1 13 | go.uber.org/zap v1.27.0 14 | k8s.io/api v0.33.1 15 | ) 16 | 17 | require ( 18 | github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect 19 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 20 | github.com/gogo/protobuf v1.3.2 // indirect 21 | github.com/google/go-containerregistry v0.20.5 // indirect 22 | github.com/json-iterator/go v1.1.12 // indirect 23 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 24 | github.com/modern-go/reflect2 v1.0.2 // indirect 25 | github.com/x448/float16 v0.8.4 // indirect 26 | go.uber.org/multierr v1.11.0 // indirect 27 | golang.org/x/net v0.38.0 // indirect 28 | golang.org/x/text v0.23.0 // indirect 29 | gopkg.in/inf.v0 v0.9.1 // indirect 30 | k8s.io/apimachinery v0.33.1 // indirect 31 | k8s.io/klog/v2 v2.130.1 // indirect 32 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 33 | sigs.k8s.io/controller-runtime v0.21.0 // indirect 34 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 35 | sigs.k8s.io/randfill v1.0.0 // indirect 36 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 37 | sigs.k8s.io/yaml v1.4.0 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /samples/convention-server/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "log" 23 | "net/http" 24 | "os" 25 | 26 | "github.com/go-logr/logr" 27 | "github.com/go-logr/zapr" 28 | "go.uber.org/zap" 29 | corev1 "k8s.io/api/core/v1" 30 | 31 | "github.com/vmware-tanzu/cartographer-conventions/webhook" 32 | ) 33 | 34 | func conventionHandler(template *corev1.PodTemplateSpec, images []webhook.ImageConfig) ([]string, error) { 35 | applied := false 36 | for i := range template.Spec.Containers { 37 | if addEnvVar(&template.Spec.Containers[i], corev1.EnvVar{ 38 | Name: "CONVENTION_SERVER", 39 | Value: "HELLO FROM CONVENTION", 40 | }) { 41 | applied = true 42 | } 43 | } 44 | if applied { 45 | return []string{"add-env-var"}, nil 46 | } 47 | return []string{}, nil 48 | } 49 | 50 | func addEnvVar(container *corev1.Container, envvar corev1.EnvVar) bool { 51 | for _, e := range container.Env { 52 | if e.Name == envvar.Name { 53 | return false 54 | } 55 | } 56 | container.Env = append(container.Env, envvar) 57 | return true 58 | } 59 | 60 | func main() { 61 | ctx := context.Background() 62 | port := os.Getenv("PORT") 63 | if port == "" { 64 | port = "9000" 65 | } 66 | 67 | zapLog, err := zap.NewProductionConfig().Build() 68 | if err != nil { 69 | log.Fatalf("failed to create logger: %v", err) 70 | } 71 | logger := zapr.NewLogger(zapLog) 72 | ctx = logr.NewContext(ctx, logger) 73 | 74 | http.HandleFunc("/", webhook.ConventionHandler(ctx, conventionHandler)) 75 | log.Fatal(webhook.NewConventionServer(ctx, fmt.Sprintf(":%s", port))) 76 | } 77 | -------------------------------------------------------------------------------- /samples/convention-server/server.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: sample-conventions 6 | 7 | --- 8 | # The following manifests contain a self-signed issuer CR and a certificate CR. 9 | # More document can be found at https://docs.cert-manager.io 10 | apiVersion: cert-manager.io/v1 11 | kind: Issuer 12 | metadata: 13 | name: selfsigned-issuer 14 | namespace: sample-conventions 15 | spec: 16 | selfSigned: {} 17 | 18 | --- 19 | apiVersion: cert-manager.io/v1 20 | kind: Certificate 21 | metadata: 22 | name: webhook-cert 23 | namespace: sample-conventions 24 | spec: 25 | subject: 26 | organizations: 27 | - vmware 28 | organizationalUnits: 29 | - tanzu 30 | commonName: webhook.sample-conventions.svc 31 | dnsNames: 32 | - webhook.sample-conventions.svc 33 | - webhook.sample-conventions.svc.cluster.local 34 | issuerRef: 35 | kind: Issuer 36 | name: selfsigned-issuer 37 | secretName: webhook-cert 38 | revisionHistoryLimit: 10 39 | 40 | --- 41 | apiVersion: apps/v1 42 | kind: Deployment 43 | metadata: 44 | name: webhook 45 | namespace: sample-conventions 46 | spec: 47 | replicas: 1 48 | selector: 49 | matchLabels: 50 | app: webhook 51 | template: 52 | metadata: 53 | labels: 54 | app: webhook 55 | spec: 56 | containers: 57 | - name: webhook 58 | image: ko://github.com/vmware-tanzu/cartographer-conventions/samples/convention-server 59 | env: 60 | - name: PORT 61 | value: "8443" 62 | ports: 63 | - containerPort: 8443 64 | name: webhook 65 | livenessProbe: 66 | httpGet: 67 | scheme: HTTPS 68 | port: webhook 69 | path: /healthz 70 | readinessProbe: 71 | httpGet: 72 | scheme: HTTPS 73 | port: webhook 74 | path: /healthz 75 | volumeMounts: 76 | - name: certs 77 | mountPath: /config/certs 78 | readOnly: true 79 | volumes: 80 | - name: certs 81 | secret: 82 | defaultMode: 420 83 | secretName: webhook-cert 84 | 85 | --- 86 | apiVersion: v1 87 | kind: Service 88 | metadata: 89 | name: webhook 90 | namespace: sample-conventions 91 | spec: 92 | selector: 93 | app: webhook 94 | ports: 95 | - protocol: TCP 96 | port: 443 97 | targetPort: webhook 98 | 99 | --- 100 | apiVersion: conventions.carto.run/v1alpha1 101 | kind: ClusterPodConvention 102 | metadata: 103 | name: sample 104 | spec: 105 | webhook: 106 | certificate: 107 | namespace: sample-conventions 108 | name: webhook-cert 109 | clientConfig: 110 | service: 111 | name: webhook 112 | namespace: sample-conventions -------------------------------------------------------------------------------- /samples/convention-server/workload.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: conventions.carto.run/v1alpha1 3 | kind: PodIntent 4 | metadata: 5 | name: sample 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: workload 11 | image: ubuntu 12 | -------------------------------------------------------------------------------- /samples/dumper-server/README.md: -------------------------------------------------------------------------------- 1 | # Dumper convention webserver 2 | 3 | The dumper is a convention server that "dumps" the `PodConventionContext` request made by the convention controller to stdout. It can be useful for gather OCI image metadata and SBOMs as a convention will receive the request. 4 | 5 | ## Trying out 6 | 7 | Build and run the convention server: 8 | 9 | ```sh 10 | # either, from source: 11 | ko apply -f server.yaml 12 | 13 | # or, from a release distribution: 14 | kubectl create -f <(kbld -f server.yaml -f ../.imgpkg/images.yml) 15 | ``` 16 | 17 | To verify the dumped values in the logs 18 | 19 | ```sh 20 | kubectl logs -n dumper-conventions -l app=webhook --tail 1000 21 | ``` 22 | 23 | Depending on the size of the `PodConventionContext` resources, this command may collect multiple requests, or only a portion of a single request. Adjust the number of lines collected as appropriate. 24 | -------------------------------------------------------------------------------- /samples/dumper-server/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vmware-tanzu/cartographer-conventions/samples/dumper-server 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | replace github.com/vmware-tanzu/cartographer-conventions/webhook => ../../webhook 8 | 9 | require ( 10 | github.com/go-logr/logr v1.4.3 11 | github.com/go-logr/zapr v1.3.0 12 | github.com/vmware-tanzu/cartographer-conventions/webhook v0.5.1 13 | go.uber.org/zap v1.27.0 14 | sigs.k8s.io/yaml v1.4.0 15 | ) 16 | 17 | require ( 18 | github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect 19 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 20 | github.com/gogo/protobuf v1.3.2 // indirect 21 | github.com/google/go-containerregistry v0.20.5 // indirect 22 | github.com/json-iterator/go v1.1.12 // indirect 23 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 24 | github.com/modern-go/reflect2 v1.0.2 // indirect 25 | github.com/x448/float16 v0.8.4 // indirect 26 | go.uber.org/multierr v1.11.0 // indirect 27 | golang.org/x/net v0.38.0 // indirect 28 | golang.org/x/text v0.23.0 // indirect 29 | gopkg.in/inf.v0 v0.9.1 // indirect 30 | k8s.io/api v0.33.1 // indirect 31 | k8s.io/apimachinery v0.33.1 // indirect 32 | k8s.io/klog/v2 v2.130.1 // indirect 33 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 34 | sigs.k8s.io/controller-runtime v0.21.0 // indirect 35 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 36 | sigs.k8s.io/randfill v1.0.0 // indirect 37 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /samples/dumper-server/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "encoding/json" 23 | "fmt" 24 | "io" 25 | "log" 26 | "net/http" 27 | "os" 28 | 29 | "github.com/go-logr/logr" 30 | "github.com/go-logr/zapr" 31 | "go.uber.org/zap" 32 | "sigs.k8s.io/yaml" 33 | 34 | "github.com/vmware-tanzu/cartographer-conventions/webhook" 35 | webhookv1alpha1 "github.com/vmware-tanzu/cartographer-conventions/webhook/api/v1alpha1" 36 | ) 37 | 38 | func conventionHandler(w http.ResponseWriter, r *http.Request) { 39 | wc := &webhookv1alpha1.PodConventionContext{} 40 | if r.Body != nil { 41 | reqBody, err := io.ReadAll(r.Body) 42 | if err != nil { 43 | w.WriteHeader(http.StatusBadRequest) 44 | return 45 | } 46 | decoder := json.NewDecoder(bytes.NewBuffer(reqBody)) 47 | if derr := decoder.Decode(wc); derr != nil { 48 | w.WriteHeader(http.StatusBadRequest) 49 | return 50 | } 51 | 52 | // dump request body as yaml 53 | d, err := yaml.Marshal(wc) 54 | if err == nil { 55 | fmt.Println("---") 56 | fmt.Println(string(d)) 57 | } 58 | } 59 | w.Header().Set("Content-Type", "application/json") 60 | wc.Status.AppliedConventions = []string{"dumper"} 61 | wc.Status.Template = wc.Spec.Template 62 | if err := json.NewEncoder(w).Encode(wc); err != nil { 63 | return 64 | } 65 | } 66 | 67 | func main() { 68 | ctx := context.Background() 69 | port := os.Getenv("PORT") 70 | if port == "" { 71 | port = "9000" 72 | } 73 | 74 | zapLog, err := zap.NewProductionConfig().Build() 75 | if err != nil { 76 | log.Fatalf("failed to create logger: %v", err) 77 | } 78 | logger := zapr.NewLogger(zapLog) 79 | ctx = logr.NewContext(ctx, logger) 80 | 81 | http.HandleFunc("/", conventionHandler) 82 | log.Fatal(webhook.NewConventionServer(ctx, fmt.Sprintf(":%s", port))) 83 | } 84 | -------------------------------------------------------------------------------- /samples/dumper-server/server.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: dumper-conventions 6 | 7 | --- 8 | # The following manifests contain a self-signed issuer CR and a certificate CR. 9 | # More document can be found at https://docs.cert-manager.io 10 | apiVersion: cert-manager.io/v1 11 | kind: Issuer 12 | metadata: 13 | name: selfsigned-issuer 14 | namespace: dumper-conventions 15 | spec: 16 | selfSigned: {} 17 | 18 | --- 19 | apiVersion: cert-manager.io/v1 20 | kind: Certificate 21 | metadata: 22 | name: webhook-cert 23 | namespace: dumper-conventions 24 | spec: 25 | subject: 26 | organizations: 27 | - vmware 28 | organizationalUnits: 29 | - tanzu 30 | commonName: webhook.dumper-conventions.svc 31 | dnsNames: 32 | - webhook.dumper-conventions.svc 33 | - webhook.dumper-conventions.svc.cluster.local 34 | issuerRef: 35 | kind: Issuer 36 | name: selfsigned-issuer 37 | secretName: webhook-cert 38 | revisionHistoryLimit: 10 39 | 40 | --- 41 | apiVersion: apps/v1 42 | kind: Deployment 43 | metadata: 44 | name: webhook 45 | namespace: dumper-conventions 46 | spec: 47 | replicas: 1 48 | selector: 49 | matchLabels: 50 | app: webhook 51 | template: 52 | metadata: 53 | labels: 54 | app: webhook 55 | spec: 56 | containers: 57 | - name: webhook 58 | image: ko://github.com/vmware-tanzu/cartographer-conventions/samples/dumper-server 59 | env: 60 | - name: PORT 61 | value: "8443" 62 | ports: 63 | - containerPort: 8443 64 | name: webhook 65 | livenessProbe: 66 | httpGet: 67 | scheme: HTTPS 68 | port: webhook 69 | path: /healthz 70 | readinessProbe: 71 | httpGet: 72 | scheme: HTTPS 73 | port: webhook 74 | path: /healthz 75 | volumeMounts: 76 | - name: certs 77 | mountPath: /config/certs 78 | readOnly: true 79 | volumes: 80 | - name: certs 81 | secret: 82 | defaultMode: 420 83 | secretName: webhook-cert 84 | 85 | --- 86 | apiVersion: v1 87 | kind: Service 88 | metadata: 89 | name: webhook 90 | namespace: dumper-conventions 91 | spec: 92 | selector: 93 | app: webhook 94 | ports: 95 | - protocol: TCP 96 | port: 443 97 | targetPort: webhook 98 | 99 | --- 100 | apiVersion: conventions.carto.run/v1alpha1 101 | kind: ClusterPodConvention 102 | metadata: 103 | name: dumper 104 | spec: 105 | priority: Early 106 | webhook: 107 | certificate: 108 | namespace: dumper-conventions 109 | name: webhook-cert 110 | clientConfig: 111 | service: 112 | name: webhook 113 | namespace: dumper-conventions 114 | -------------------------------------------------------------------------------- /samples/spring-convention-server/README.md: -------------------------------------------------------------------------------- 1 | # Sample springboot convention webserver 2 | 3 | The image contains a simple Go webserver `server.go`, that will by default, listens on port `9000` and expose a service at `/`. 4 | 5 | The webserver can only be called with request body of type `PodConventionContext` of APIGroup `webhooks.conventions.carto.run`. If not, server responds with 400 status code. 6 | When called with correct request body type, the server emits a response of object type `PodConventionContext` of APIGroup `webhooks.conventions.carto.run`. This spring sample webserver adds annotation `boot.spring.io/version` with value {spring-boot-version} and add label `conventions.carto.run/framework` with value `spring-boot` if the container image has `spring-boot` dependency. 7 | 8 | ## Trying out 9 | 10 | Build and run the convention server: 11 | 12 | ``` 13 | # either, from source: 14 | ko apply -f server.yaml 15 | 16 | # or, from a release distribution: 17 | kubectl create -f <(kbld -f server.yaml -f ../.imgpkg/images.yml) 18 | ``` 19 | 20 | To verify the setup, add an Workload and check the status 21 | 22 | ```sh 23 | kubectl create -f workload.yaml 24 | ``` 25 | 26 | ``` 27 | kubectl get podintents.conventions.carto.run spring-sample -oyaml 28 | ``` 29 | 30 | If everything works correctly, the status will contain a transformed template that includes Spring Boot variables added by the convention server. 31 | 32 | ```yaml 33 | apiVersion: conventions.carto.run/v1alpha1 34 | kind: PodIntent 35 | metadata: 36 | annotations: 37 | kubectl.kubernetes.io/last-applied-configuration: | 38 | {"apiVersion":"conventions.carto.run/v1alpha1","kind":"PodIntent","metadata":{"annotations":{},"name":"spring-sample","namespace":"test"},"spec":{"template":{"spec":{"containers":[{"image":"krashed843/tanzu-java-app@sha256:9f90358e4c4eff2255bab81e3fa6316418ef435465dbae3bba74a2262f7c227d","name":"workload"}]}}}} 39 | creationTimestamp: "2023-12-21T20:40:59Z" 40 | generation: 1 41 | name: spring-sample 42 | namespace: test 43 | resourceVersion: "7568605" 44 | uid: 3c263b9c-a586-4933-97a8-604a1badeccb 45 | spec: 46 | serviceAccountName: default 47 | template: 48 | metadata: {} 49 | spec: 50 | containers: 51 | - image: krashed843/tanzu-java-app@sha256:9f90358e4c4eff2255bab81e3fa6316418ef435465dbae3bba74a2262f7c227d 52 | name: workload 53 | resources: {} 54 | status: 55 | conditions: 56 | - lastTransitionTime: "2023-12-21T20:40:59Z" 57 | message: "" 58 | reason: Applied 59 | status: "True" 60 | type: ConventionsApplied 61 | - lastTransitionTime: "2023-12-21T20:40:59Z" 62 | message: "" 63 | reason: ConventionsApplied 64 | status: "True" 65 | type: Ready 66 | observedGeneration: 1 67 | template: 68 | metadata: 69 | annotations: 70 | boot.spring.io/actuator: http://:8080/actuator 71 | boot.spring.io/version: 2.7.15 72 | conventions.carto.run/applied-conventions: |- 73 | spring-sample/spring-boot 74 | spring-sample/spring-boot-web 75 | spring-sample/spring-boot-actuator 76 | spring-sample/spring-boot-actuator-probes 77 | labels: 78 | conventions.carto.run/framework: spring-boot 79 | spec: 80 | containers: 81 | - env: 82 | - name: JAVA_TOOL_OPTIONS 83 | value: -Dmanagement.endpoints.web.base-path=/actuator -Dmanagement.health.probes.enabled=true 84 | -Dmanagement.server.port=8080 -Dserver.port=8080 85 | image: index.docker.io/krashed843/tanzu-java-app@sha256:9f90358e4c4eff2255bab81e3fa6316418ef435465dbae3bba74a2262f7c227d 86 | livenessProbe: 87 | httpGet: 88 | path: /actuator/health/liveness 89 | port: 8080 90 | scheme: HTTP 91 | name: workload 92 | ports: 93 | - containerPort: 8080 94 | protocol: TCP 95 | readinessProbe: 96 | httpGet: 97 | path: /actuator/health/readiness 98 | port: 8080 99 | scheme: HTTP 100 | resources: {} 101 | startupProbe: 102 | failureThreshold: 120 103 | httpGet: 104 | path: /actuator/health/liveness 105 | port: 8080 106 | scheme: HTTP 107 | initialDelaySeconds: 1 108 | periodSeconds: 1 109 | ``` 110 | -------------------------------------------------------------------------------- /samples/spring-convention-server/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vmware-tanzu/cartographer-conventions/samples/spring-convention-server 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | replace github.com/vmware-tanzu/cartographer-conventions/webhook => ../../webhook 8 | 9 | require ( 10 | github.com/CycloneDX/cyclonedx-go v0.9.2 11 | github.com/Masterminds/semver v1.5.0 12 | github.com/go-logr/logr v1.4.3 13 | github.com/go-logr/zapr v1.3.0 14 | github.com/vmware-tanzu/cartographer-conventions/webhook v0.5.1 15 | go.uber.org/zap v1.27.0 16 | k8s.io/api v0.33.1 17 | k8s.io/apimachinery v0.33.1 18 | ) 19 | 20 | require ( 21 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 22 | github.com/gogo/protobuf v1.3.2 // indirect 23 | github.com/google/go-containerregistry v0.20.5 // indirect 24 | github.com/json-iterator/go v1.1.12 // indirect 25 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 26 | github.com/modern-go/reflect2 v1.0.2 // indirect 27 | github.com/x448/float16 v0.8.4 // indirect 28 | go.uber.org/multierr v1.11.0 // indirect 29 | golang.org/x/net v0.38.0 // indirect 30 | golang.org/x/text v0.23.0 // indirect 31 | gopkg.in/inf.v0 v0.9.1 // indirect 32 | k8s.io/klog/v2 v2.130.1 // indirect 33 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 34 | sigs.k8s.io/controller-runtime v0.21.0 // indirect 35 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 36 | sigs.k8s.io/randfill v1.0.0 // indirect 37 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 38 | sigs.k8s.io/yaml v1.4.0 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /samples/spring-convention-server/resources/conventions.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 VMware Inc. 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 resources 18 | 19 | import ( 20 | "context" 21 | 22 | corev1 "k8s.io/api/core/v1" 23 | 24 | webhookv1alpha1 "github.com/vmware-tanzu/cartographer-conventions/webhook/api/v1alpha1" 25 | ) 26 | 27 | type ImageMetadata = map[string]webhookv1alpha1.ImageConfig 28 | 29 | type Convention interface { 30 | GetId() string 31 | IsApplicable(ctx context.Context, metadata ImageMetadata) bool 32 | ApplyConvention(ctx context.Context, target *corev1.PodTemplateSpec, containerIdx int, metadata ImageMetadata) error 33 | } 34 | 35 | var _ Convention = (*BasicConvention)(nil) 36 | 37 | type BasicConvention struct { 38 | Id string 39 | Applicable func(ctx context.Context, metadata ImageMetadata) bool 40 | Apply func(ctx context.Context, target *corev1.PodTemplateSpec, containerIdx int, metadata ImageMetadata) error 41 | } 42 | 43 | func (o *BasicConvention) GetId() string { 44 | return o.Id 45 | } 46 | 47 | func (o *BasicConvention) IsApplicable(ctx context.Context, metadata ImageMetadata) bool { 48 | if o.Applicable == nil { 49 | return true 50 | } 51 | return o.Applicable(ctx, metadata) 52 | } 53 | 54 | func (o *BasicConvention) ApplyConvention(ctx context.Context, target *corev1.PodTemplateSpec, containerIdx int, metadata ImageMetadata) error { 55 | return o.Apply(ctx, target, containerIdx, metadata) 56 | } 57 | 58 | // setAnnotation sets the annotation on PodTemplateSpec 59 | func setAnnotation(pts *corev1.PodTemplateSpec, key, value string) { 60 | if pts.Annotations == nil { 61 | pts.Annotations = map[string]string{} 62 | } 63 | pts.Annotations[key] = value 64 | } 65 | 66 | // setLabel sets the label on PodTemplateSpec 67 | func setLabel(pts *corev1.PodTemplateSpec, key, value string) { 68 | if pts.Labels == nil { 69 | pts.Labels = map[string]string{} 70 | } 71 | pts.Labels[key] = value 72 | } 73 | 74 | func findEnvVar(container corev1.Container, name string) *corev1.EnvVar { 75 | for i := range container.Env { 76 | e := &container.Env[i] 77 | if e.Name == name { 78 | return e 79 | } 80 | } 81 | return nil 82 | } 83 | 84 | func findContainerPort(ps corev1.PodSpec, port int32) (string, *corev1.ContainerPort) { 85 | for _, c := range ps.Containers { 86 | for _, p := range c.Ports { 87 | if p.ContainerPort == port { 88 | return c.Name, &p 89 | } 90 | } 91 | } 92 | return "", nil 93 | } 94 | -------------------------------------------------------------------------------- /samples/spring-convention-server/resources/dependencies.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 VMware Inc. 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 resources 18 | 19 | import ( 20 | "context" 21 | "regexp" 22 | 23 | "github.com/CycloneDX/cyclonedx-go" 24 | // wokeignore:rule=master 25 | "github.com/Masterminds/semver" 26 | "k8s.io/apimachinery/pkg/util/sets" 27 | 28 | webhookv1alpha1 "github.com/vmware-tanzu/cartographer-conventions/webhook/api/v1alpha1" 29 | ) 30 | 31 | func NewDependenciesBOM(boms []webhookv1alpha1.BOM) DependenciesBOM { 32 | d := DependenciesBOM{} 33 | for _, b := range boms { 34 | // ignore errors, other boms may be in a different structure or not json 35 | if cdx, _ := b.AsCycloneDX(); cdx != nil { 36 | d.boms = append(d.boms, *cdx) 37 | } 38 | } 39 | return d 40 | } 41 | 42 | type DependenciesBOM struct { 43 | boms []cyclonedx.BOM 44 | } 45 | 46 | func (m *DependenciesBOM) Dependency(name string) *cyclonedx.Component { 47 | for _, b := range m.boms { 48 | if b.Components == nil { 49 | continue 50 | } 51 | for _, c := range *b.Components { 52 | if c.Name == name { 53 | return &c 54 | } 55 | } 56 | } 57 | return nil 58 | } 59 | 60 | func (m *DependenciesBOM) HasDependency(names ...string) bool { 61 | n := sets.NewString(names...) 62 | for _, b := range m.boms { 63 | if b.Components == nil { 64 | continue 65 | } 66 | for _, c := range *b.Components { 67 | if n.Has(c.Name) { 68 | return true 69 | } 70 | } 71 | } 72 | return false 73 | } 74 | 75 | func (m *DependenciesBOM) HasDependencyConstraint(name, constraint string) bool { 76 | d := m.Dependency(name) 77 | if d == nil { 78 | return false 79 | } 80 | v, err := semver.NewVersion(m.normalizeVersion(d.Version)) 81 | if err != nil { 82 | return false 83 | } 84 | c, err := semver.NewConstraint(constraint) 85 | if err != nil { 86 | return false 87 | } 88 | return c.Check(v) 89 | } 90 | 91 | func (m *DependenciesBOM) normalizeVersion(version string) string { 92 | r := regexp.MustCompile(`^([0-9]+\.[0-9]+\.[0-9]+)\.`) 93 | return r.ReplaceAllString(version, "$1-") 94 | } 95 | 96 | type dependenciesBOMKey struct{} 97 | 98 | func StashDependenciesBOM(ctx context.Context, props *DependenciesBOM) context.Context { 99 | return context.WithValue(ctx, dependenciesBOMKey{}, props) 100 | } 101 | 102 | func GetDependenciesBOM(ctx context.Context) *DependenciesBOM { 103 | value := ctx.Value(dependenciesBOMKey{}) 104 | if deps, ok := value.(*DependenciesBOM); ok { 105 | return deps 106 | } 107 | 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /samples/spring-convention-server/resources/serviceintent.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 VMware Inc. 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 resources 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | corev1 "k8s.io/api/core/v1" 24 | ) 25 | 26 | var _ Convention = (*SpringBootServiceIntent)(nil) 27 | 28 | type SpringBootServiceIntent struct { 29 | Id string 30 | LabelName string 31 | Dependencies []string 32 | } 33 | 34 | func (o *SpringBootServiceIntent) GetId() string { 35 | return o.Id 36 | } 37 | 38 | func (o *SpringBootServiceIntent) IsApplicable(ctx context.Context, metadata ImageMetadata) bool { 39 | deps := GetDependenciesBOM(ctx) 40 | return deps.HasDependency(o.Dependencies...) 41 | } 42 | 43 | func (o *SpringBootServiceIntent) ApplyConvention(ctx context.Context, target *corev1.PodTemplateSpec, containerIdx int, metadata ImageMetadata) error { 44 | deps := GetDependenciesBOM(ctx) 45 | for _, d := range o.Dependencies { 46 | if dbom := deps.Dependency(d); dbom != nil { 47 | setLabel(target, o.LabelName, target.Spec.Containers[containerIdx].Name) 48 | setAnnotation(target, o.LabelName, fmt.Sprintf("%s/%s", dbom.Name, dbom.Version)) 49 | break 50 | } 51 | } 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /samples/spring-convention-server/resources/spring_properties.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 VMware Inc. 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 resources 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "sort" 23 | "strings" 24 | 25 | corev1 "k8s.io/api/core/v1" 26 | ) 27 | 28 | type SpringApplicationProperties map[string]string 29 | 30 | func (p SpringApplicationProperties) Default(key string, defaultValue string) string { 31 | if _, ok := p[key]; !ok { 32 | p[key] = defaultValue 33 | } 34 | return p[key] 35 | } 36 | 37 | func (p SpringApplicationProperties) FromContainer(c *corev1.Container) { 38 | javaOpts := findEnvVar(*c, "JAVA_TOOL_OPTIONS") 39 | if javaOpts == nil { 40 | return 41 | } 42 | keep := []string{} 43 | for _, c := range strings.Split(javaOpts.Value, " ") { 44 | if !strings.HasPrefix(c, "-D") || !strings.Contains(c, "=") { 45 | keep = append(keep, c) 46 | continue 47 | } 48 | // TODO properly decode properties 49 | kv := strings.SplitN(c[2:], "=", 2) 50 | p[kv[0]] = kv[1] 51 | } 52 | // remove opts as they will be added back after the conventions are applied 53 | javaOpts.Value = strings.Join(keep, " ") 54 | } 55 | 56 | func (p SpringApplicationProperties) ToContainer(c *corev1.Container) { 57 | properties := []string{} 58 | propertyKeys := []string{} 59 | for key := range p { 60 | propertyKeys = append(propertyKeys, key) 61 | } 62 | sort.Strings(propertyKeys) 63 | for _, key := range propertyKeys { 64 | // TODO escape key values as needed 65 | properties = append(properties, fmt.Sprintf("-D%s=%s", key, p[key])) 66 | } 67 | 68 | // set application properties on JAVA_TOOL_OPTIONS 69 | javaOpts := findEnvVar(*c, "JAVA_TOOL_OPTIONS") 70 | if javaOpts != nil { 71 | javaOpts.Value = strings.TrimSpace(fmt.Sprintf("%s %s", javaOpts.Value, strings.Join(properties, " "))) 72 | } else { 73 | c.Env = append(c.Env, corev1.EnvVar{ 74 | Name: "JAVA_TOOL_OPTIONS", 75 | Value: strings.Join(properties, " "), 76 | }) 77 | } 78 | } 79 | 80 | type springApplicationPropertiesKey struct{} 81 | 82 | func StashSpringApplicationProperties(ctx context.Context, props SpringApplicationProperties) context.Context { 83 | return context.WithValue(ctx, springApplicationPropertiesKey{}, props) 84 | } 85 | 86 | func GetSpringApplicationProperties(ctx context.Context) SpringApplicationProperties { 87 | value := ctx.Value(springApplicationPropertiesKey{}) 88 | if props, ok := value.(SpringApplicationProperties); ok { 89 | return props 90 | } 91 | 92 | return SpringApplicationProperties{} 93 | } 94 | -------------------------------------------------------------------------------- /samples/spring-convention-server/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "log" 23 | "net/http" 24 | "os" 25 | 26 | "github.com/go-logr/logr" 27 | "github.com/go-logr/zapr" 28 | "go.uber.org/zap" 29 | corev1 "k8s.io/api/core/v1" 30 | 31 | "github.com/vmware-tanzu/cartographer-conventions/samples/spring-convention-server/resources" 32 | "github.com/vmware-tanzu/cartographer-conventions/webhook" 33 | ) 34 | 35 | func addSpringBootConventions(template *corev1.PodTemplateSpec, images []webhook.ImageConfig) ([]string, error) { 36 | imageMap := make(map[string]webhook.ImageConfig) 37 | for _, config := range images { 38 | imageMap[config.Image] = config 39 | } 40 | 41 | var appliedConventions []string 42 | for i := range template.Spec.Containers { 43 | // TODO how to best handle multiple spring boot containers? 44 | container := &template.Spec.Containers[i] 45 | image, ok := imageMap[container.Image] 46 | if !ok { 47 | // skip containers without metadata, this may be a container without an image 48 | continue 49 | } 50 | dependencyMetadata := resources.NewDependenciesBOM(image.BOMs) 51 | applicationProperties := resources.SpringApplicationProperties{} 52 | applicationProperties.FromContainer(container) 53 | 54 | ctx := context.Background() 55 | ctx = resources.StashSpringApplicationProperties(ctx, applicationProperties) 56 | ctx = resources.StashDependenciesBOM(ctx, &dependencyMetadata) 57 | for _, o := range resources.SpringBootConventions { 58 | // need to continue refining what metadata is passed to conventions 59 | if !o.IsApplicable(ctx, imageMap) { 60 | continue 61 | } 62 | appliedConventions = append(appliedConventions, o.GetId()) 63 | if err := o.ApplyConvention(ctx, template, i, imageMap); err != nil { 64 | return nil, err 65 | } 66 | } 67 | applicationProperties.ToContainer(container) 68 | } 69 | return appliedConventions, nil 70 | } 71 | 72 | func main() { 73 | ctx := context.Background() 74 | port := os.Getenv("PORT") 75 | if port == "" { 76 | port = "9000" 77 | } 78 | 79 | zapLog, err := zap.NewProductionConfig().Build() 80 | if err != nil { 81 | log.Fatalf("failed to create logger: %v", err) 82 | } 83 | 84 | logger := zapr.NewLogger(zapLog) 85 | ctx = logr.NewContext(ctx, logger) 86 | 87 | http.HandleFunc("/", webhook.ConventionHandler(ctx, addSpringBootConventions)) 88 | log.Fatal(webhook.NewConventionServer(ctx, fmt.Sprintf(":%s", port))) 89 | } 90 | -------------------------------------------------------------------------------- /samples/spring-convention-server/server.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: sample-spring-conventions 6 | 7 | --- 8 | # The following manifests contain a self-signed issuer CR and a certificate CR. 9 | # More document can be found at https://docs.cert-manager.io 10 | apiVersion: cert-manager.io/v1 11 | kind: Issuer 12 | metadata: 13 | name: spring-selfsigned-issuer 14 | namespace: sample-spring-conventions 15 | spec: 16 | selfSigned: {} 17 | 18 | --- 19 | apiVersion: cert-manager.io/v1 20 | kind: Certificate 21 | metadata: 22 | name: spring-webhook-cert 23 | namespace: sample-spring-conventions 24 | spec: 25 | subject: 26 | organizations: 27 | - vmware 28 | organizationalUnits: 29 | - tanzu 30 | commonName: spring-webhook.spring-conventions.svc 31 | dnsNames: 32 | - spring-webhook.sample-spring-conventions.svc 33 | - spring-webhook.sample-spring-conventions.svc.cluster.local 34 | issuerRef: 35 | kind: Issuer 36 | name: spring-selfsigned-issuer 37 | secretName: spring-webhook-cert 38 | revisionHistoryLimit: 10 39 | 40 | --- 41 | apiVersion: apps/v1 42 | kind: Deployment 43 | metadata: 44 | name: spring-webhook 45 | namespace: sample-spring-conventions 46 | spec: 47 | replicas: 1 48 | selector: 49 | matchLabels: 50 | app: spring-webhook 51 | template: 52 | metadata: 53 | labels: 54 | app: spring-webhook 55 | spec: 56 | containers: 57 | - name: webhook 58 | image: ko://github.com/vmware-tanzu/cartographer-conventions/samples/spring-convention-server 59 | env: 60 | - name: PORT 61 | value: "8443" 62 | ports: 63 | - containerPort: 8443 64 | name: webhook 65 | livenessProbe: 66 | httpGet: 67 | scheme: HTTPS 68 | port: webhook 69 | path: /healthz 70 | readinessProbe: 71 | httpGet: 72 | scheme: HTTPS 73 | port: webhook 74 | path: /healthz 75 | volumeMounts: 76 | - name: certs 77 | mountPath: /config/certs 78 | readOnly: true 79 | volumes: 80 | - name: certs 81 | secret: 82 | defaultMode: 420 83 | secretName: spring-webhook-cert 84 | 85 | --- 86 | apiVersion: v1 87 | kind: Service 88 | metadata: 89 | name: spring-webhook 90 | namespace: sample-spring-conventions 91 | spec: 92 | selector: 93 | app: spring-webhook 94 | ports: 95 | - protocol: TCP 96 | port: 443 97 | targetPort: webhook 98 | 99 | --- 100 | apiVersion: conventions.carto.run/v1alpha1 101 | kind: ClusterPodConvention 102 | metadata: 103 | name: spring-sample 104 | spec: 105 | webhook: 106 | certificate: 107 | namespace: sample-spring-conventions 108 | name: spring-webhook-cert 109 | clientConfig: 110 | service: 111 | name: spring-webhook 112 | namespace: sample-spring-conventions 113 | -------------------------------------------------------------------------------- /samples/spring-convention-server/workload.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: conventions.carto.run/v1alpha1 3 | kind: PodIntent 4 | metadata: 5 | name: spring-sample 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: workload 11 | image: krashed843/tanzu-java-app@sha256:9f90358e4c4eff2255bab81e3fa6316418ef435465dbae3bba74a2262f7c227d 12 | -------------------------------------------------------------------------------- /webhook/api/v1alpha1/groupverion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 VMware Inc. 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 webhooks v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | package v1alpha1 20 | 21 | import ( 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | "sigs.k8s.io/controller-runtime/pkg/scheme" 24 | ) 25 | 26 | var ( 27 | // GroupVersion is group version used to register these objects 28 | GroupVersion = schema.GroupVersion{Group: "webhooks.conventions.carto.run", Version: "v1alpha1"} 29 | 30 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 31 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 32 | 33 | // AddToScheme adds the types in this group-version to the given scheme. 34 | AddToScheme = SchemeBuilder.AddToScheme 35 | ) 36 | -------------------------------------------------------------------------------- /webhook/api/v1alpha1/podconventioncontext_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 VMware Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "encoding/json" 21 | 22 | "github.com/CycloneDX/cyclonedx-go" 23 | ggcrv1 "github.com/google/go-containerregistry/pkg/v1" 24 | corev1 "k8s.io/api/core/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | ) 28 | 29 | type PodConventionContextSpec struct { 30 | Template corev1.PodTemplateSpec `json:"template"` 31 | ImageConfig []ImageConfig `json:"imageConfig"` 32 | } 33 | 34 | type PodConventionContextStatus struct { 35 | Template corev1.PodTemplateSpec `json:"template"` 36 | AppliedConventions []string `json:"appliedConventions"` 37 | } 38 | 39 | type ImageConfig struct { 40 | Image string `json:"image"` 41 | BOMs []BOM `json:"boms,omitempty"` 42 | Config ggcrv1.ConfigFile `json:"config"` 43 | } 44 | 45 | type BOM struct { 46 | Name string `json:"name"` 47 | Raw []byte `json:"raw"` 48 | } 49 | 50 | func (b *BOM) AsCycloneDX() (*cyclonedx.BOM, error) { 51 | bom := &cyclonedx.BOM{} 52 | // TODO sniff the content to prevent non-cyclonedx boms from unmarshaling into a mismatched struct 53 | if err := json.Unmarshal(b.Raw, bom); err != nil { 54 | return nil, err 55 | } 56 | return bom, nil 57 | } 58 | 59 | type PodConventionContext struct { 60 | metav1.TypeMeta `json:",inline"` 61 | metav1.ObjectMeta `json:"metadata,omitempty"` 62 | Spec PodConventionContextSpec `json:"spec"` 63 | Status PodConventionContextStatus `json:"status"` 64 | } 65 | 66 | // DeepCopy is a deepcopy function, copying the receiver, creating a new PodConventionContext. 67 | func (in *PodConventionContext) DeepCopy() *PodConventionContext { 68 | // writing our own deep copy method using json marshaling to sidestep the cyclonedx BOM not 69 | // having its own DeepCopy method. 70 | 71 | if in == nil { 72 | return nil 73 | } 74 | copy, err := json.Marshal(in) 75 | if err != nil { 76 | panic(err) 77 | } 78 | out := &PodConventionContext{} 79 | if err := json.Unmarshal(copy, out); err != nil { 80 | panic(err) 81 | } 82 | return out 83 | } 84 | 85 | // DeepCopyObject is a deepcopy function, copying the receiver, creating a new runtime.Object. 86 | func (in *PodConventionContext) DeepCopyObject() runtime.Object { 87 | // NB: the webhook client expects the resource to implement runtime.Object, we don't expect 88 | // these methods to actually be used. 89 | if c := in.DeepCopy(); c != nil { 90 | return c 91 | } 92 | return nil 93 | } 94 | 95 | func init() { 96 | SchemeBuilder.Register(&PodConventionContext{}) 97 | } 98 | -------------------------------------------------------------------------------- /webhook/api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | /* 4 | Copyright 2020-2022 VMware Inc. 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 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 24 | func (in *BOM) DeepCopyInto(out *BOM) { 25 | *out = *in 26 | if in.Raw != nil { 27 | in, out := &in.Raw, &out.Raw 28 | *out = make([]byte, len(*in)) 29 | copy(*out, *in) 30 | } 31 | } 32 | 33 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BOM. 34 | func (in *BOM) DeepCopy() *BOM { 35 | if in == nil { 36 | return nil 37 | } 38 | out := new(BOM) 39 | in.DeepCopyInto(out) 40 | return out 41 | } 42 | 43 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 44 | func (in *ImageConfig) DeepCopyInto(out *ImageConfig) { 45 | *out = *in 46 | if in.BOMs != nil { 47 | in, out := &in.BOMs, &out.BOMs 48 | *out = make([]BOM, len(*in)) 49 | for i := range *in { 50 | (*in)[i].DeepCopyInto(&(*out)[i]) 51 | } 52 | } 53 | in.Config.DeepCopyInto(&out.Config) 54 | } 55 | 56 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageConfig. 57 | func (in *ImageConfig) DeepCopy() *ImageConfig { 58 | if in == nil { 59 | return nil 60 | } 61 | out := new(ImageConfig) 62 | in.DeepCopyInto(out) 63 | return out 64 | } 65 | 66 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 67 | func (in *PodConventionContext) DeepCopyInto(out *PodConventionContext) { 68 | clone := in.DeepCopy() 69 | *out = *clone 70 | } 71 | 72 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 73 | func (in *PodConventionContextSpec) DeepCopyInto(out *PodConventionContextSpec) { 74 | *out = *in 75 | in.Template.DeepCopyInto(&out.Template) 76 | if in.ImageConfig != nil { 77 | in, out := &in.ImageConfig, &out.ImageConfig 78 | *out = make([]ImageConfig, len(*in)) 79 | for i := range *in { 80 | (*in)[i].DeepCopyInto(&(*out)[i]) 81 | } 82 | } 83 | } 84 | 85 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodConventionContextSpec. 86 | func (in *PodConventionContextSpec) DeepCopy() *PodConventionContextSpec { 87 | if in == nil { 88 | return nil 89 | } 90 | out := new(PodConventionContextSpec) 91 | in.DeepCopyInto(out) 92 | return out 93 | } 94 | 95 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 96 | func (in *PodConventionContextStatus) DeepCopyInto(out *PodConventionContextStatus) { 97 | *out = *in 98 | in.Template.DeepCopyInto(&out.Template) 99 | if in.AppliedConventions != nil { 100 | in, out := &in.AppliedConventions, &out.AppliedConventions 101 | *out = make([]string, len(*in)) 102 | copy(*out, *in) 103 | } 104 | } 105 | 106 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodConventionContextStatus. 107 | func (in *PodConventionContextStatus) DeepCopy() *PodConventionContextStatus { 108 | if in == nil { 109 | return nil 110 | } 111 | out := new(PodConventionContextStatus) 112 | in.DeepCopyInto(out) 113 | return out 114 | } 115 | -------------------------------------------------------------------------------- /webhook/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vmware-tanzu/cartographer-conventions/webhook 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/CycloneDX/cyclonedx-go v0.9.2 9 | github.com/go-logr/logr v1.4.3 10 | github.com/google/go-containerregistry v0.20.5 11 | k8s.io/api v0.33.1 12 | k8s.io/apimachinery v0.33.1 13 | sigs.k8s.io/controller-runtime v0.21.0 14 | ) 15 | 16 | require ( 17 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 18 | github.com/gogo/protobuf v1.3.2 // indirect 19 | github.com/json-iterator/go v1.1.12 // indirect 20 | github.com/kr/text v0.2.0 // indirect 21 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 22 | github.com/modern-go/reflect2 v1.0.2 // indirect 23 | github.com/x448/float16 v0.8.4 // indirect 24 | golang.org/x/net v0.38.0 // indirect 25 | golang.org/x/text v0.23.0 // indirect 26 | gopkg.in/inf.v0 v0.9.1 // indirect 27 | k8s.io/klog/v2 v2.130.1 // indirect 28 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 29 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 30 | sigs.k8s.io/randfill v1.0.0 // indirect 31 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 32 | sigs.k8s.io/yaml v1.4.0 // indirect 33 | ) 34 | -------------------------------------------------------------------------------- /webhook/hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020-2022 VMware Inc. 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 | */ -------------------------------------------------------------------------------- /webhook/hack/boilerplate.ytt.txt: -------------------------------------------------------------------------------- 1 | #! Copyright 2020-2022 VMware Inc. 2 | #! 3 | #! Licensed under the Apache License, Version 2.0 (the "License"); 4 | #! you may not use this file except in compliance with the License. 5 | #! You may obtain a copy of the License at 6 | #! 7 | #! http://www.apache.org/licenses/LICENSE-2.0 8 | #! 9 | #! Unless required by applicable law or agreed to in writing, software 10 | #! distributed under the License is distributed on an "AS IS" BASIS, 11 | #! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | #! See the License for the specific language governing permissions and 13 | #! limitations under the License. 14 | -------------------------------------------------------------------------------- /webhook/hack/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vmware-tanzu/cartographer-conventions/tools 2 | 3 | go 1.24.2 4 | 5 | toolchain go1.24.3 6 | 7 | require ( 8 | carvel.dev/ytt v0.52.0 9 | golang.org/x/tools v0.33.0 10 | reconciler.io/dies/diegen v0.16.0 11 | sigs.k8s.io/controller-tools v0.18.0 12 | sigs.k8s.io/kustomize/kustomize/v5 v5.6.0 13 | ) 14 | 15 | require ( 16 | github.com/BurntSushi/toml v1.2.1 // indirect 17 | github.com/blang/semver/v4 v4.0.0 // indirect 18 | github.com/cppforlife/cobrautil v0.0.0-20200514214827-bb86e6965d72 // indirect 19 | github.com/cppforlife/go-cli-ui v0.0.0-20200505234325-512793797f05 // indirect 20 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 21 | github.com/fatih/color v1.18.0 // indirect 22 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 23 | github.com/go-errors/errors v1.4.2 // indirect 24 | github.com/go-logr/logr v1.4.2 // indirect 25 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 26 | github.com/go-openapi/jsonreference v0.20.2 // indirect 27 | github.com/go-openapi/swag v0.23.0 // indirect 28 | github.com/gobuffalo/flect v1.0.3 // indirect 29 | github.com/gogo/protobuf v1.3.2 // indirect 30 | github.com/google/gnostic-models v0.6.9 // indirect 31 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 32 | github.com/hashicorp/go-version v1.6.0 // indirect 33 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 34 | github.com/josharian/intern v1.0.0 // indirect 35 | github.com/json-iterator/go v1.1.12 // indirect 36 | github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 // indirect 37 | github.com/mailru/easyjson v0.7.7 // indirect 38 | github.com/mattn/go-colorable v0.1.13 // indirect 39 | github.com/mattn/go-isatty v0.0.20 // indirect 40 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 41 | github.com/modern-go/reflect2 v1.0.2 // indirect 42 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 43 | github.com/pkg/errors v0.9.1 // indirect 44 | github.com/sergi/go-diff v1.2.0 // indirect 45 | github.com/spf13/cobra v1.9.1 // indirect 46 | github.com/spf13/pflag v1.0.6 // indirect 47 | github.com/x448/float16 v0.8.4 // indirect 48 | github.com/xlab/treeprint v1.2.0 // indirect 49 | golang.org/x/mod v0.24.0 // indirect 50 | golang.org/x/net v0.40.0 // indirect 51 | golang.org/x/sync v0.14.0 // indirect 52 | golang.org/x/sys v0.33.0 // indirect 53 | golang.org/x/text v0.25.0 // indirect 54 | google.golang.org/protobuf v1.36.5 // indirect 55 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 56 | gopkg.in/inf.v0 v0.9.1 // indirect 57 | gopkg.in/yaml.v2 v2.4.0 // indirect 58 | gopkg.in/yaml.v3 v3.0.1 // indirect 59 | k8s.io/api v0.33.0 // indirect 60 | k8s.io/apiextensions-apiserver v0.33.0 // indirect 61 | k8s.io/apimachinery v0.33.0 // indirect 62 | k8s.io/code-generator v0.33.0 // indirect 63 | k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 // indirect 64 | k8s.io/klog/v2 v2.130.1 // indirect 65 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 66 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 67 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 68 | sigs.k8s.io/kustomize/api v0.19.0 // indirect 69 | sigs.k8s.io/kustomize/cmd/config v0.19.0 // indirect 70 | sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect 71 | sigs.k8s.io/randfill v1.0.0 // indirect 72 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 73 | sigs.k8s.io/yaml v1.4.0 // indirect 74 | ) 75 | -------------------------------------------------------------------------------- /webhook/hack/hello-sbom-layer.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-tanzu/cartographer-conventions/a18491a0abe039656766dcca5f14d0df090a4ae4/webhook/hack/hello-sbom-layer.tar.gz -------------------------------------------------------------------------------- /webhook/hack/hello.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | LABEL hello=world 3 | COPY boilerplate.go.txt /LICENSE 4 | -------------------------------------------------------------------------------- /webhook/hack/hello.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-tanzu/cartographer-conventions/a18491a0abe039656766dcca5f14d0df090a4ae4/webhook/hack/hello.tar.gz -------------------------------------------------------------------------------- /webhook/hack/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | // This package imports things required by build scripts, to force `go mod` to see them as dependencies 5 | package tools 6 | 7 | import ( 8 | _ "carvel.dev/ytt/cmd/ytt" 9 | _ "golang.org/x/tools/cmd/goimports" 10 | _ "reconciler.io/dies/diegen" 11 | _ "sigs.k8s.io/controller-tools/cmd/controller-gen" 12 | _ "sigs.k8s.io/kustomize/kustomize/v5" 13 | ) 14 | -------------------------------------------------------------------------------- /webhook/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 VMware Inc. 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 webhook 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "crypto/tls" 23 | "crypto/x509" 24 | "encoding/json" 25 | "io" 26 | "net" 27 | "net/http" 28 | "os" 29 | "path/filepath" 30 | "sync" 31 | "time" 32 | 33 | "github.com/go-logr/logr" 34 | corev1 "k8s.io/api/core/v1" 35 | 36 | webhookv1alpha1 "github.com/vmware-tanzu/cartographer-conventions/webhook/api/v1alpha1" 37 | ) 38 | 39 | const ( 40 | CertMountPath = "/config/certs" 41 | ) 42 | 43 | type Convention func(*corev1.PodTemplateSpec, []webhookv1alpha1.ImageConfig) ([]string, error) 44 | type ImageConfig = webhookv1alpha1.ImageConfig 45 | 46 | func NewConventionServer(ctx context.Context, addr string) error { 47 | http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { 48 | w.WriteHeader(http.StatusOK) 49 | }) 50 | 51 | watcher := certWatcher{ 52 | CrtFile: filepath.Join(CertMountPath, "tls.crt"), 53 | KeyFile: filepath.Join(CertMountPath, "tls.key"), 54 | } 55 | if err := watcher.Load(ctx); err != nil { 56 | return err 57 | } 58 | go watcher.Watch(ctx) 59 | 60 | server := &http.Server{ 61 | Addr: addr, 62 | TLSConfig: &tls.Config{ 63 | GetCertificate: func(_ *tls.ClientHelloInfo) (*tls.Certificate, error) { 64 | cert := watcher.GetCertificate() 65 | return cert, nil 66 | }, 67 | PreferServerCipherSuites: true, 68 | MinVersion: tls.VersionTLS13, 69 | }, 70 | BaseContext: func(_ net.Listener) context.Context { 71 | return ctx 72 | }, 73 | } 74 | go func() { 75 | <-ctx.Done() 76 | server.Close() 77 | }() 78 | 79 | return server.ListenAndServeTLS("", "") 80 | } 81 | 82 | func ConventionHandler(ctx context.Context, convention Convention) func(http.ResponseWriter, *http.Request) { 83 | return func(w http.ResponseWriter, r *http.Request) { 84 | logger := logr.FromContextOrDiscard(ctx) 85 | 86 | logger.Info("received request") 87 | wc := &webhookv1alpha1.PodConventionContext{} 88 | if r.Body != nil { 89 | reqBody, err := io.ReadAll(r.Body) 90 | if err != nil { 91 | logger.Error(err, "error reading request body") 92 | w.WriteHeader(http.StatusBadRequest) 93 | return 94 | } 95 | decoder := json.NewDecoder(bytes.NewBuffer(reqBody)) 96 | if derr := decoder.Decode(wc); derr != nil { 97 | logger.Error(derr, "the request body could not be decoded into a PodConventionContext type") 98 | w.WriteHeader(http.StatusBadRequest) 99 | return 100 | } 101 | } 102 | w.Header().Set("Content-Type", "application/json") 103 | pts := wc.Spec.Template.DeepCopy() 104 | appliedConventions, err := convention(pts, wc.Spec.ImageConfig) 105 | if err != nil { 106 | logger.Error(err, "error applying conventions") 107 | w.WriteHeader(http.StatusInternalServerError) 108 | } 109 | wc.Status.AppliedConventions = appliedConventions 110 | wc.Status.Template = *pts 111 | if err := json.NewEncoder(w).Encode(wc); err != nil { 112 | logger.Error(err, "failed to encode the PodConventionContext. Unable to create response for received request.") 113 | return 114 | } 115 | } 116 | } 117 | 118 | type certWatcher struct { 119 | CrtFile string 120 | KeyFile string 121 | 122 | m sync.Mutex 123 | keyPair *tls.Certificate 124 | } 125 | 126 | func (w *certWatcher) Watch(ctx context.Context) error { 127 | logger := logr.FromContextOrDiscard(ctx) 128 | // refresh the certs periodically even if we miss a fs event 129 | ticker := time.NewTicker(5 * time.Minute) 130 | defer ticker.Stop() 131 | go func() { 132 | for { 133 | select { 134 | case <-ctx.Done(): 135 | return 136 | case <-ticker.C: 137 | if err := w.Load(ctx); err != nil { 138 | logger.Error(err, "error loading TLS key pair") 139 | } 140 | } 141 | } 142 | }() 143 | 144 | <-ctx.Done() 145 | return nil 146 | } 147 | 148 | func (w *certWatcher) Load(ctx context.Context) error { 149 | logger := logr.FromContextOrDiscard(ctx) 150 | w.m.Lock() 151 | defer w.m.Unlock() 152 | 153 | crt, err := os.ReadFile(w.CrtFile) 154 | if err != nil { 155 | return err 156 | } 157 | key, err := os.ReadFile(w.KeyFile) 158 | if err != nil { 159 | return err 160 | } 161 | keyPair, err := tls.X509KeyPair(crt, key) 162 | if err != nil { 163 | return err 164 | } 165 | leaf := keyPair.Leaf 166 | if leaf == nil { 167 | leaf, err = x509.ParseCertificate(keyPair.Certificate[0]) 168 | if err != nil { 169 | return err 170 | } 171 | } 172 | w.keyPair = &keyPair 173 | logger.Info("loaded TLS key pair", "not-after", leaf.NotAfter) 174 | return nil 175 | } 176 | 177 | func (w *certWatcher) GetCertificate() *tls.Certificate { 178 | w.m.Lock() 179 | defer w.m.Unlock() 180 | 181 | return w.keyPair 182 | } 183 | --------------------------------------------------------------------------------