├── .github ├── actions │ ├── builder │ │ ├── Dockerfile │ │ ├── action.yaml │ │ └── builder │ │ │ ├── README.md.tpl │ │ │ └── main.py │ └── updater │ │ ├── Dockerfile │ │ ├── Pipfile │ │ ├── Pipfile.lock │ │ ├── action.yaml │ │ ├── repos │ │ └── .gitignore │ │ └── updater │ │ ├── catalog.py │ │ ├── entry.py │ │ ├── main.py │ │ ├── repo.py │ │ └── settings.py └── workflows │ ├── main.yml │ ├── promote.yml │ └── updater.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Makefile ├── README.md ├── assets └── favicon.png ├── repositories ├── .terraform.lock.hcl ├── README.md.tpl ├── main.tf ├── providers.tf ├── state.tf └── variables.tf ├── src ├── _terraform_module │ ├── configuration.tf │ ├── data_source.tf │ ├── main._tf │ ├── test_default_variant.tf │ ├── test_kustomization │ │ ├── Kustomization │ │ ├── configmap.yaml │ │ ├── deployment.yaml │ │ └── namespace.yaml │ ├── test_outputs.tf │ ├── test_provider.tf │ ├── tests │ │ ├── common_annotations │ │ │ └── test.tf │ │ ├── common_labels │ │ │ └── test.tf │ │ ├── config_map_generator │ │ │ ├── env │ │ │ └── test.tf │ │ ├── generator_options │ │ │ └── test.tf │ │ ├── images │ │ │ └── test.tf │ │ ├── inheritance │ │ │ └── test.tf │ │ ├── patches │ │ │ ├── patch_deployment_resources.yaml │ │ │ └── test.tf │ │ ├── replicas │ │ │ └── test.tf │ │ └── secret_generator │ │ │ ├── env │ │ │ └── test.tf │ ├── variables.tf │ └── versions.tf ├── argo-cd │ ├── _updater │ │ ├── metadata.yaml │ │ └── run.sh │ ├── configuration.tf │ ├── data_source.tf │ ├── default_variant.tf │ ├── ha │ │ ├── install.yaml │ │ ├── kustomization.yaml │ │ └── namespace.yaml │ ├── main.tf │ ├── normal │ │ ├── install.yaml │ │ ├── kustomization.yaml │ │ └── namespace.yaml │ ├── variables.tf │ └── versions.tf ├── cert-manager │ ├── _updater │ │ ├── metadata.yaml │ │ └── run.sh │ ├── base │ │ ├── cert-manager.yaml │ │ └── kustomization.yaml │ ├── configuration.tf │ ├── data_source.tf │ ├── default_variant.tf │ ├── main.tf │ ├── variables.tf │ └── versions.tf ├── custom-manifests │ ├── configuration.tf │ ├── data_source.tf │ ├── default_variant.tf │ ├── empty │ │ └── Kustomization │ ├── main.tf │ ├── variables.tf │ └── versions.tf ├── flux │ ├── _updater │ │ ├── metadata.yaml │ │ └── run.sh │ ├── base │ │ ├── flux-account.yaml │ │ ├── flux-deployment.yaml │ │ ├── flux-ns.yaml │ │ ├── flux-secret.yaml │ │ ├── kustomization.yaml │ │ ├── memcache-dep.yaml │ │ └── memcache-svc.yaml │ ├── configuration.tf │ ├── data_source.tf │ ├── default_variant.tf │ ├── main.tf │ ├── variables.tf │ └── versions.tf ├── nginx │ ├── _updater │ │ ├── metadata.yaml │ │ └── run.sh │ ├── base │ │ ├── kustomization.yaml │ │ └── mandatory.yaml │ ├── configuration.tf │ ├── data_source.tf │ ├── default-ingress │ │ ├── kustomization.yaml │ │ └── patch-namespace.yaml │ ├── default_variant.tf │ ├── main.tf │ ├── variables.tf │ └── versions.tf ├── opa-gatekeeper │ ├── _updater │ │ ├── metadata.yaml │ │ └── run.sh │ ├── base │ │ ├── gatekeeper.yaml │ │ └── kustomization.yaml │ ├── configuration.tf │ ├── data_source.tf │ ├── default_variant.tf │ ├── main.tf │ ├── variables.tf │ └── versions.tf ├── pinniped │ ├── _updater │ │ ├── metadata.yaml │ │ └── run.sh │ ├── concierge-base │ │ ├── crds.yaml │ │ ├── kustomization.yaml │ │ └── resources.yaml │ ├── configuration.tf │ ├── data_source.tf │ ├── default_variant.tf │ ├── main.tf │ ├── supervisor-base │ │ ├── kustomization.yaml │ │ └── resources.yaml │ ├── variables.tf │ └── versions.tf ├── postgresql │ ├── _updater │ │ ├── metadata.yaml │ │ └── run.sh │ ├── base │ │ ├── configmap.yaml │ │ ├── kustomization.yaml │ │ └── postgres-operator.yaml │ ├── clusterwide │ │ ├── kustomization.yaml │ │ ├── namespace.yaml │ │ ├── patch-deployment-env.yaml │ │ └── rbac.yaml │ ├── configuration.tf │ ├── data_source.tf │ ├── default_variant.tf │ ├── main.tf │ ├── variables.tf │ └── versions.tf ├── prometheus │ ├── _updater │ │ ├── metadata.yaml │ │ └── run.sh │ ├── base │ │ ├── bundle.yaml │ │ └── kustomization.yaml │ ├── clusterwide │ │ ├── instance-cluster-role.yaml │ │ ├── kustomization.yaml │ │ └── namespace.yaml │ ├── configuration.tf │ ├── data_source.tf │ ├── default_variant.tf │ ├── main.tf │ ├── variables.tf │ └── versions.tf ├── sealed-secrets │ ├── _updater │ │ ├── metadata.yaml │ │ └── run.sh │ ├── base │ │ ├── controller.yaml │ │ └── kustomization.yaml │ ├── configuration.tf │ ├── data_source.tf │ ├── default_variant.tf │ ├── main.tf │ ├── variables.tf │ └── versions.tf ├── tektoncd │ ├── _updater │ │ ├── metadata.yaml │ │ └── run.sh │ ├── base │ │ ├── kustomization.yaml │ │ └── release.yaml │ ├── configuration.tf │ ├── data_source.tf │ ├── default_variant.tf │ ├── main.tf │ ├── variables.tf │ └── versions.tf └── test │ ├── base │ ├── Kustomization │ ├── configmap.yaml │ ├── deployment.yaml │ └── namespace.yaml │ ├── configuration.tf │ ├── data_source.tf │ ├── default_variant.tf │ ├── main.tf │ ├── overlay │ ├── Kustomization │ └── patch-namespace.yaml │ ├── variables.tf │ └── versions.tf └── test └── k3d ├── Dockerfile ├── Pipfile ├── Pipfile.lock ├── main.py └── main.tf.tpl /.github/actions/builder/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | COPY builder/ /opt/builder/ 4 | 5 | CMD ["python", "/opt/builder/main.py"] 6 | -------------------------------------------------------------------------------- /.github/actions/builder/action.yaml: -------------------------------------------------------------------------------- 1 | name: "Catalog Builder" 2 | description: "Build artifacts from catalog entries." 3 | runs: 4 | using: 'docker' 5 | image: 'Dockerfile' 6 | -------------------------------------------------------------------------------- /.github/actions/builder/builder/README.md.tpl: -------------------------------------------------------------------------------- 1 | # $title Terraform Module 2 | 3 | This module installs $title on Kubernetes. 4 | It is maintained as part of [Kubestack, the Terraform framework for Kubernetes platform engineering](https://www.kubestack.com/). 5 | The module bundles an upstream release of $title and makes it fully configurable with Kustomizations set as input attributes. 6 | 7 | ## Usage with Kubestack 8 | 9 | Kubestack leverages configuration inheritance to avoid drift between environments. 10 | The example below shows how to use the module with Kubestack's default environment names `ops` and `apps`. 11 | You can learn more about [Kubestack's inheritance model in the documentation](https://www.kubestack.com/framework/documentation/inheritance-model/). 12 | For usage instructions without Kubestack see below. 13 | 14 | ```hcl 15 | terraform { 16 | required_providers { 17 | kustomization = { 18 | source = "kbst/kustomization" 19 | } 20 | } 21 | } 22 | 23 | provider "kustomization" { 24 | alias = "local" 25 | 26 | kubeconfig_path = "~/.kube/config" 27 | } 28 | 29 | module "example_$module_name" { 30 | providers = { 31 | kustomization = kustomization.local 32 | } 33 | 34 | source = "kubestack-modules/$name/kustomization" 35 | version = "v0.0.0-kbst.0" 36 | 37 | configuration = { 38 | apps = { 39 | # change the namespace of all resources 40 | namespace = var.example_argo_cd_namespace 41 | 42 | # or add an annotation 43 | common_annotations = { 44 | "terraform-workspace" = terraform.workspace 45 | } 46 | 47 | # use images to pull from an internal proxy 48 | # and avoid being rate limited 49 | images = [{ 50 | # refers to the 'pod.spec.container.name' to modify the 'image' attribute of 51 | name = "container-name" 52 | 53 | # customize the 'registry/name' part of the image 54 | new_name = "reg.example.com/nginx" 55 | }] 56 | } 57 | 58 | ops = { 59 | # scale down replicas in ops 60 | replicas = [{ 61 | # refers to the 'metadata.name' of the resource to scale 62 | name = "example" 63 | 64 | # sets the desired number of replicas 65 | count = 1 66 | }] 67 | } 68 | } 69 | } 70 | ``` 71 | 72 | The Kubestack website has more documentation and examples on the available [Kustomization attributes](https://www.kubestack.com/framework/documentation/services/#configuration). 73 | 74 | ## Usage without Kubestack 75 | 76 | Modules are fully usable without Kubestack and without inheritance by setting `configuration_base_key` and a `configuration` with a single key matching your workspace name, e.g. `default`. 77 | The Kubestack website has a complete guide on [how to use the Kubestack modules without the Kubestack framework](https://www.kubestack.com/guides/catalog-using-kubestack-catalog-modules-standalone/). 78 | 79 | ```hcl 80 | terraform { 81 | required_providers { 82 | kustomization = { 83 | source = "kbst/kustomization" 84 | } 85 | } 86 | } 87 | 88 | provider "kustomization" { 89 | alias = "local" 90 | 91 | kubeconfig_path = "~/.kube/config" 92 | } 93 | 94 | module "example_$module_name" { 95 | providers = { 96 | # we're using the alias provider we configured above 97 | kustomization = kustomization.local 98 | } 99 | 100 | source = "kubestack-modules/$name/kustomization" 101 | version = "v0.0.0-kbst.0" 102 | 103 | # the configuration here assumes you're using Terraform's default workspace 104 | # use `terraform workspace list` to see the workspaces 105 | configuration_base_key = "default" 106 | configuration = { 107 | default = { 108 | replicas = [{ 109 | name = "example" 110 | count = 5 111 | }] 112 | } 113 | } 114 | } 115 | ``` 116 | 117 | ## Versions 118 | 119 | The module versions are the upstream release version (e.g. `v0.0.0`) plus a packaging suffix (e.g. `-kbst.0`). 120 | Should a new module release be necessary without changing the upstream version, the packaging suffix is incremented by one. 121 | Due to the packaging suffix, [Terraform version constraints](https://developer.hashicorp.com/terraform/language/expressions/version-constraints) can not be used for Kubestack modules. 122 | 123 | ## Contributing 124 | 125 | All Kubestack modules are built from the https://github.com/kbst/catalog/ repository. 126 | To contribute please head over to GitHub. 127 | -------------------------------------------------------------------------------- /.github/actions/builder/builder/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from json import dumps 4 | from os import environ, listdir, mkdir 5 | from os.path import isdir, isfile, join, dirname 6 | from shutil import copytree, ignore_patterns, make_archive, rmtree, unpack_archive 7 | from sys import exit 8 | from tempfile import TemporaryDirectory 9 | from string import Template, capwords 10 | 11 | 12 | def create_archive(name, version): 13 | src = join(SRCDIR, name) 14 | 15 | module_dist = join(DISTDIR, f'module-{name}') 16 | module = join(DISTDIR, f'module-{name}-{version}') 17 | 18 | copytree(src, module_dist, ignore=ignore_patterns('_*')) 19 | 20 | with open(join(dirname(__file__), 'README.md.tpl'), 'r') as tf: 21 | t = Template(tf.read()) 22 | md = t.substitute({ 23 | "title": capwords(name), 24 | "name": name, 25 | "module_name": name.replace("-", "_"), 26 | }) 27 | tf.close() 28 | 29 | with open(join(module_dist, "README.md"), 'w') as f: 30 | f.write(md) 31 | f.close() 32 | 33 | make_archive(module, 'zip', module_dist) 34 | 35 | 36 | def get_build_targets(ref): 37 | """ Returns build targets 38 | 39 | Returns a list of name and version tuples. 40 | 41 | If the ref name is prefixed with one of the available names, 42 | it returns a list of length one. 43 | 44 | If the ref is a branch, a short commit hash will be appended 45 | to the version. 46 | 47 | Refuse to build multiple artifacts for tags. Releases need 48 | to be for a single entry. 49 | """ 50 | name = None 51 | version = None 52 | 53 | if ref.startswith('refs/tags/'): 54 | ref_name = ref.replace('refs/tags/', '') 55 | is_tag = True 56 | 57 | if ref.startswith('refs/heads/'): 58 | ref_name = ref.replace('refs/heads/', '') 59 | is_tag = False 60 | 61 | if ref.startswith('refs/heads/release-'): 62 | ref_name = ref.replace('refs/heads/release-', '') 63 | is_tag = False 64 | 65 | available_names = [n for n in listdir(SRCDIR) if not n.startswith('_')] 66 | 67 | targets = [] 68 | for name in available_names: 69 | prefix = f'{name}-' 70 | 71 | # Version based on tag (e.g. refs/tags/nginx-v0.43.1-kbst.0) 72 | version = ref_name.replace(prefix, '') 73 | 74 | # Version based on branch (e.g. refs/heads/nginx-mychange) 75 | if not is_tag: 76 | hash = environ.get('GITHUB_SHA', None) 77 | if not hash: 78 | exit(f"[ERROR] `GITHUB_SHA` env var not set") 79 | version = hash 80 | 81 | if ref_name.startswith(prefix): 82 | # We're building a specific target 83 | return [(name, version)] 84 | else: 85 | # We build all entries 86 | targets.append((name, version)) 87 | 88 | if ref.startswith('refs/tags/') and len(targets) != 1: 89 | exit(f"[ERROR] Invalid `GITHUB_REF` '{ref}'. " + 90 | f"Tags must be prefixed with one of {available_names}") 91 | 92 | if ref.startswith('refs/heads/all-'): 93 | # Building all targets takes very long, we only do so 94 | # when the branch name starts with `all-` 95 | return targets 96 | 97 | # if neither a specifc nor all entries were requested 98 | # we default to the test entry 99 | return [("test", hash)] 100 | 101 | if __name__ == "__main__": 102 | SRCDIR = 'src' 103 | DISTDIR = '_dist' 104 | OUTPUTSFILE = environ.get('GITHUB_OUTPUT') 105 | 106 | # 107 | # 108 | # Clean DISTDIR 109 | if isdir(DISTDIR): 110 | rmtree(DISTDIR) 111 | mkdir(DISTDIR) 112 | 113 | # 114 | # 115 | # Build artifacts 116 | ref = environ.get('GITHUB_REF', None) 117 | if not ref: 118 | exit(f"[ERROR] `GITHUB_REF` env var not set") 119 | 120 | if not ref.startswith('refs/tags/') and not ref.startswith('refs/heads/'): 121 | exit(f"[ERROR] unexpected `GITHUB_REF`: {ref}") 122 | 123 | build_targets = get_build_targets(ref) 124 | 125 | for name, version in build_targets: 126 | create_archive(name, version) 127 | 128 | # 129 | # 130 | # Generate all entries output for publish-gh job 131 | all_targets = get_build_targets("refs/heads/all-targets") 132 | 133 | names = [] 134 | for name, _ in all_targets: 135 | names.append(f'"{name}"') 136 | 137 | tf_var_names_output = f'TF_VAR_names=[{",".join(names)}]' 138 | 139 | # 140 | # 141 | # Generate matrix output for test job 142 | matrix_output_data = { 143 | "include": [] 144 | } 145 | 146 | for name in listdir(DISTDIR): 147 | if not isfile(name) and (not name.startswith('module-') or not name.endswith('.zip')): 148 | continue 149 | 150 | with TemporaryDirectory() as root: 151 | mut = join(root, "mut") 152 | archive = join(DISTDIR, name) 153 | unpack_archive(archive, mut, "zip") 154 | for variant in listdir(mut): 155 | variant_path = join(mut, variant) 156 | if not isdir(variant_path): 157 | continue 158 | 159 | matrix_output_data["include"].append({ 160 | "variant": variant, 161 | "name": name 162 | }) 163 | 164 | matrix_output = f'matrix={dumps(matrix_output_data)}' 165 | 166 | 167 | # 168 | # 169 | # Write or print outputs 170 | if OUTPUTSFILE: 171 | with open(OUTPUTSFILE, 'a') as f: 172 | f.write(f'{tf_var_names_output}\n') 173 | f.write(f'{matrix_output}\n') 174 | f.close() 175 | exit(0) 176 | 177 | print(tf_var_names_output) 178 | print(matrix_output) 179 | exit(0) 180 | -------------------------------------------------------------------------------- /.github/actions/updater/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | ARG KUSTOMIZE_VERSION=3.8.1 4 | 5 | RUN KUSTOMIZE_BINARY_PATH="https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v${KUSTOMIZE_VERSION}/kustomize_v${KUSTOMIZE_VERSION}_linux_amd64.tar.gz"; \ 6 | curl -LOs ${KUSTOMIZE_BINARY_PATH} && \ 7 | tar -xf kustomize_v${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \ 8 | mv kustomize /usr/local/bin/kustomize && \ 9 | chmod +x /usr/local/bin/kustomize && \ 10 | kustomize version; 11 | 12 | COPY Pipfile Pipfile.lock /opt/ 13 | 14 | WORKDIR /opt 15 | RUN pip install --no-cache-dir pipenv &&\ 16 | PIPENV_VENV_IN_PROJECT=true pipenv install 17 | 18 | COPY repos /opt/repos 19 | COPY updater /opt/updater 20 | 21 | ENV PATH=/opt/.venv/bin:$PATH 22 | CMD ["python", "/opt/updater/main.py"] 23 | -------------------------------------------------------------------------------- /.github/actions/updater/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | gitpython = "*" 10 | pyyaml = "*" 11 | 12 | [requires] 13 | python_version = "3" 14 | -------------------------------------------------------------------------------- /.github/actions/updater/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "008de50a8b083b9a1d065913b0718c3e1ec16e5da0416d5a8a51cdf3e6635ad1" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "gitdb": { 20 | "hashes": [ 21 | "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4", 22 | "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b" 23 | ], 24 | "markers": "python_version >= '3.7'", 25 | "version": "==4.0.11" 26 | }, 27 | "gitpython": { 28 | "hashes": [ 29 | "sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c", 30 | "sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048" 31 | ], 32 | "index": "pypi", 33 | "markers": "python_version >= '3.7'", 34 | "version": "==3.1.41" 35 | }, 36 | "pyyaml": { 37 | "hashes": [ 38 | "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", 39 | "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", 40 | "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", 41 | "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", 42 | "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", 43 | "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", 44 | "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", 45 | "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", 46 | "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", 47 | "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", 48 | "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", 49 | "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", 50 | "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", 51 | "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", 52 | "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", 53 | "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", 54 | "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", 55 | "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", 56 | "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", 57 | "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", 58 | "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", 59 | "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", 60 | "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", 61 | "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", 62 | "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", 63 | "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", 64 | "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", 65 | "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", 66 | "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", 67 | "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", 68 | "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", 69 | "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", 70 | "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", 71 | "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", 72 | "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", 73 | "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", 74 | "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", 75 | "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", 76 | "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", 77 | "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", 78 | "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", 79 | "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", 80 | "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", 81 | "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", 82 | "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", 83 | "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", 84 | "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", 85 | "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", 86 | "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", 87 | "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" 88 | ], 89 | "index": "pypi", 90 | "markers": "python_version >= '3.6'", 91 | "version": "==6.0.1" 92 | }, 93 | "smmap": { 94 | "hashes": [ 95 | "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62", 96 | "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da" 97 | ], 98 | "markers": "python_version >= '3.7'", 99 | "version": "==5.0.1" 100 | } 101 | }, 102 | "develop": {} 103 | } 104 | -------------------------------------------------------------------------------- /.github/actions/updater/action.yaml: -------------------------------------------------------------------------------- 1 | name: "Catalog Updater" 2 | description: "Update catalog entries with latest upstream releases." 3 | inputs: 4 | github_token: 5 | description: 'GitHub token used to push branches' 6 | required: true 7 | runs: 8 | using: 'docker' 9 | image: 'Dockerfile' 10 | -------------------------------------------------------------------------------- /.github/actions/updater/repos/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore contents of repos directory 2 | * 3 | 4 | # Except this .gitignore 5 | !.gitignore 6 | -------------------------------------------------------------------------------- /.github/actions/updater/updater/catalog.py: -------------------------------------------------------------------------------- 1 | import re 2 | import logging 3 | import subprocess 4 | from os import listdir, path 5 | 6 | from git.exc import GitCommandError 7 | 8 | from repo import clone_or_reset_repo 9 | 10 | import settings 11 | 12 | import yaml 13 | 14 | 15 | class Catalog(): 16 | def __init__(self): 17 | self.clone_or_reset() 18 | 19 | def clone_or_reset(self): 20 | self.repo = clone_or_reset_repo(settings.CATALOG_NAME, 21 | settings.CATALOG_URL, 22 | settings.CATALOG_REF) 23 | 24 | # set git identity for commits 25 | self.repo.git.config('user.name', settings.GIT_USER_NAME) 26 | self.repo.git.config('user.email', settings.GIT_USER_EMAIL) 27 | 28 | def get_src_dir(self): 29 | return path.join(self.repo.working_tree_dir, 'src') 30 | 31 | def get_entries(self): 32 | src_dir = self.get_src_dir() 33 | return sorted([n for n in listdir(src_dir) if not n.startswith('_')]) 34 | 35 | def get_variants(self, entry): 36 | entry_dir = path.join(self.get_src_dir(), entry.name) 37 | variants = [] 38 | for v in listdir(entry_dir): 39 | if path.isdir(v) and not v.startswith('_'): 40 | variants.append(v) 41 | return sorted(variants) 42 | 43 | def run_script(self, entry, target_path): 44 | run_script_path = path.join(target_path, '_updater', 'run.sh') 45 | 46 | cmd = ['/bin/sh', 47 | run_script_path, 48 | entry.repo.working_tree_dir, 49 | target_path, 50 | str(entry.tag)] 51 | 52 | try: 53 | subprocess.run(cmd, 54 | check=True, 55 | capture_output=True, 56 | text=True) 57 | except subprocess.CalledProcessError as e: 58 | logging.error(f'updating {entry.name} failed, ' 59 | f'stderr: "{e.stderr}"') 60 | self.clone_or_reset() 61 | self.repo.git.branch('-d', self.branch_name) 62 | return False 63 | else: 64 | logging.info(f'updated {entry.name} successfully') 65 | return True 66 | 67 | def update_version_annotation(self, kpath, version): 68 | kustomization_path = path.join(kpath, 'kustomization.yaml') 69 | with open(kustomization_path, 'r+') as stream: 70 | try: 71 | kf = yaml.safe_load(stream) 72 | except yaml.YAMLError as e: 73 | logging.exception(e) 74 | if 'commonAnnotations' not in kf: 75 | kf['commonAnnotations'] = {} 76 | kf['commonAnnotations']['app.kubernetes.io/version'] = version 77 | yaml.dump(kf, 78 | default_flow_style=False, 79 | indent=2, 80 | sort_keys=False) 81 | stream.seek(0) 82 | stream.write(yaml.dump(kf, 83 | default_flow_style=False, 84 | indent=2, 85 | sort_keys=False)) 86 | stream.truncate() 87 | 88 | def update_entry(self, entry): 89 | current_version = str(entry.tag) 90 | 91 | # skip tags not matching regex 92 | if 'filter_tags' in entry.metadata: 93 | regex = entry.metadata['filter_tags'] 94 | match = re.match(regex, current_version) 95 | if match: 96 | current_version = match.group(1) 97 | else: 98 | # if the regex does not match, we skip this tag 99 | return 100 | 101 | # in case upstream prefixes v, remove it 102 | # because we always prefix v for our tags 103 | current_version = current_version.lstrip('v') 104 | release_version = f'v{current_version}' 105 | release_tag = f'{entry.name}-{release_version}-kbst.0' 106 | 107 | if release_tag in entry.releases: 108 | # skip the upstream tag, if we already have a kbst.0 release for it 109 | logging.debug(f'skipping {release_tag}, already exists') 110 | return 111 | 112 | # checkout tag to build in source repo 113 | entry.repo.git.checkout('-f', entry.tag) 114 | 115 | self.branch_name = f'release-{release_tag}' 116 | self.repo.git.checkout(settings.CATALOG_REF) 117 | self.repo.git.clean('-xdf') 118 | self.repo.git.checkout('-B', self.branch_name) 119 | 120 | target_path = path.join(self.get_src_dir(), entry.name) 121 | 122 | # run entry updater script 123 | script_success = self.run_script(entry, target_path) 124 | if not script_success: 125 | return 126 | 127 | # update or set version annotation on all variants 128 | for variant in self.get_variants(entry): 129 | variant_path = path.join(target_path, variant) 130 | self.update_version_annotation(variant_path, release_version) 131 | 132 | self.repo.git.add('.') 133 | self.repo.git.commit('-m', f'Release {release_tag}') 134 | try: 135 | self.repo.git.push('origin', self.branch_name) 136 | except GitCommandError as e: 137 | logging.error(f'push of {self.branch_name} branch rejected') 138 | logging.error(f'git error: {e}') 139 | return 140 | else: 141 | logging.info(f'push of {self.branch_name} branch successful') 142 | -------------------------------------------------------------------------------- /.github/actions/updater/updater/entry.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from datetime import datetime 3 | from os import path 4 | 5 | from git.objects.commit import Commit 6 | from git.objects.tag import TagObject 7 | 8 | from repo import clone_or_reset_repo 9 | 10 | import yaml 11 | 12 | 13 | class Entry(): 14 | def __init__(self, catalog, name): 15 | self.catalog = catalog 16 | self.name = name 17 | self.metadata = None 18 | self.repo = None 19 | self.tag = None 20 | self.date = None 21 | 22 | def get_tag_date(self, tag): 23 | # lightweight tags don't have tagged_date 24 | # use the commit object's date then 25 | date = 0 26 | logging.debug(f'getting date of {tag} for {self.name}') 27 | object = tag.object 28 | if type(object) is Commit: 29 | date = object.committed_date 30 | elif type(object) is TagObject: 31 | date = object.tagged_date 32 | else: 33 | logging.warning(f'skipping {tag} for {self.name} ' 34 | f'due to unknow object type {type(object)}') 35 | return date 36 | 37 | def get_metadata(self): 38 | src_dir = self.catalog.get_src_dir() 39 | metadata_path = path.join(src_dir, 40 | self.name, 41 | '_updater', 42 | 'metadata.yaml') 43 | try: 44 | with open(metadata_path) as stream: 45 | try: 46 | metadata = yaml.safe_load(stream) 47 | except yaml.YAMLError as e: 48 | logging.exception(e) 49 | except FileNotFoundError: 50 | logging.warning(f'skipping {self.name} ' 51 | 'due to missing metadata') 52 | else: 53 | return metadata 54 | return None 55 | 56 | def check_releases(self): 57 | logging.debug(f'start updating {self.name}') 58 | 59 | self.metadata = self.get_metadata() 60 | if not self.metadata: 61 | return 62 | 63 | self.repo = clone_or_reset_repo( 64 | self.name, 65 | self.metadata.get('url'), 66 | self.metadata.get('ref', 'master')) 67 | entry_tags = sorted(self.repo.tags, 68 | key=lambda x: self.get_tag_date(x)) 69 | if not entry_tags: 70 | return 71 | 72 | catalog_tags = self.catalog.repo.tags 73 | self.releases = list(filter( 74 | lambda x: str(x).startswith(self.name), 75 | catalog_tags)) 76 | 77 | latest_release = None 78 | self.latest_release_date = 0 79 | if self.releases: 80 | latest_release = sorted(self.releases, 81 | key=lambda x: self.get_tag_date(x))[-1] 82 | self.latest_release_date = self.get_tag_date(latest_release) 83 | 84 | for tag in entry_tags: 85 | if self.get_tag_date(tag) < self.latest_release_date: 86 | # skip tags older than the last release in the catalog 87 | dt = datetime.utcfromtimestamp(self.latest_release_date) 88 | log_date = dt.strftime('%Y-%m-%d %H:%M:%S') 89 | logging.debug(f'skipping {self.name} {tag}, ' 90 | f'older than {log_date}') 91 | continue 92 | 93 | self.tag = tag 94 | self.date = self.get_tag_date(self.tag) 95 | 96 | self.catalog.update_entry(self) 97 | 98 | self.repo.close() 99 | logging.debug(f'finished updating {self.name}') 100 | -------------------------------------------------------------------------------- /.github/actions/updater/updater/main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from catalog import Catalog 4 | 5 | from entry import Entry 6 | 7 | 8 | if __name__ == '__main__': 9 | logging.basicConfig(level=logging.INFO) 10 | 11 | catalog = Catalog() 12 | for entry_name in catalog.get_entries(): 13 | Entry(catalog, entry_name).check_releases() 14 | catalog.repo.close() 15 | -------------------------------------------------------------------------------- /.github/actions/updater/updater/repo.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from os import path 3 | 4 | from git import Repo 5 | from git.exc import GitCommandError 6 | 7 | REPO_DIR = path.abspath('repos') 8 | 9 | 10 | def clone_or_reset_repo(name, url, ref='master'): 11 | logging.debug(f'start cloning or resetting {url}') 12 | repo_path = path.join(REPO_DIR, name) 13 | repo = Repo.init(repo_path) 14 | 15 | try: 16 | repo.delete_remote('origin') 17 | except GitCommandError: 18 | pass 19 | finally: 20 | repo.create_remote('origin', url) 21 | 22 | repo.git.fetch('--tags', 'origin') 23 | repo.git.checkout('-f', f'origin/{ref}') 24 | repo.git.reset('--hard', f'origin/{ref}') 25 | logging.debug(f'finished cloning or resetting {url}') 26 | return repo 27 | -------------------------------------------------------------------------------- /.github/actions/updater/updater/settings.py: -------------------------------------------------------------------------------- 1 | from os import getenv 2 | 3 | CATALOG_NAME = getenv('CATALOG_NAME', 'catalog') 4 | CATALOG_REF = getenv('CATALOG_REF', 'master') 5 | 6 | GITHUB_ACTOR = getenv('GITHUB_ACTOR', None) 7 | GITHUB_TOKEN = getenv('INPUT_GITHUB_TOKEN', None) 8 | 9 | CATALOG_URL = getenv('CATALOG_URL', 'github.com/kbst/catalog.git') 10 | if GITHUB_ACTOR and GITHUB_TOKEN: 11 | CATALOG_URL = f'{GITHUB_ACTOR}:{GITHUB_TOKEN}@{CATALOG_URL}' 12 | CATALOG_URL = f'https://{CATALOG_URL}' 13 | 14 | GIT_USER_NAME = getenv('GIT_USER_NAME', 'Catalog Updater') 15 | GIT_USER_EMAIL = getenv('GIT_USER_EMAIL', 'catalog-updater@ghactions') 16 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | run-name: 'Build & test :: ${{ github.ref }}' 3 | 4 | on: 5 | push: 6 | branches: 7 | - "*" # run for branches 8 | tags: 9 | - "!*" # do not run for tags 10 | 11 | env: 12 | TERRAFORM_VERSION: "1.5.7" 13 | 14 | jobs: 15 | # 16 | # 17 | # Build artifacts 18 | build: 19 | runs-on: ubuntu-latest 20 | 21 | outputs: 22 | TF_VAR_names: ${{ steps.build.outputs.TF_VAR_names }} 23 | matrix: ${{ steps.build.outputs.matrix }} 24 | 25 | steps: 26 | - uses: actions/checkout@v4.0.0 27 | 28 | # Run builder 29 | - id: build 30 | name: Run builder 31 | uses: ./.github/actions/builder 32 | 33 | # Upload artifacts 34 | - name: 'Upload artifacts' 35 | uses: actions/upload-artifact@v4.6.0 36 | with: 37 | name: _dist 38 | path: _dist/*.zip 39 | 40 | 41 | # 42 | # 43 | # Run `terraform test` 44 | test-terraform: 45 | runs-on: ubuntu-latest 46 | 47 | steps: 48 | # Checkout 49 | - uses: actions/checkout@v4.0.0 50 | 51 | # Setup Terraform 52 | - name: Setup Terraform 53 | uses: hashicorp/setup-terraform@v2.0.3 54 | with: 55 | terraform_wrapper: false 56 | terraform_version: "${{ env.TERRAFORM_VERSION }}" 57 | 58 | # Run tests 59 | - name: Run tests 60 | run: make test-terraform 61 | 62 | 63 | # 64 | # 65 | # Test deploy to k3d 66 | test-k3d: 67 | runs-on: ubuntu-latest 68 | needs: build 69 | 70 | strategy: 71 | fail-fast: false 72 | matrix: ${{ fromJSON(needs.build.outputs.matrix) }} 73 | 74 | steps: 75 | # Checkout 76 | - uses: actions/checkout@v4.0.0 77 | 78 | # Setup k3d 79 | - name: Setup k3d 80 | uses: rinx/setup-k3d@v0.0.4 81 | with: 82 | skipClusterCreation: true 83 | 84 | - name: Create k3d cluster 85 | run: make k3d 86 | 87 | # Download build artifacts 88 | - name: 'Download build-artifacts' 89 | uses: actions/download-artifact@v4.1.8 90 | with: 91 | name: _dist 92 | path: _dist 93 | 94 | # Deploy to cluster 95 | - name: Run test container 96 | run: make test-k3d test-name=${{ matrix.name }} test-variant=${{ matrix.variant}} 97 | 98 | 99 | # 100 | # 101 | # 102 | publish-gh: 103 | runs-on: ubuntu-latest 104 | concurrency: 105 | group: publish-gh 106 | cancel-in-progress: false 107 | needs: [build, test-k3d, test-terraform] 108 | if: startsWith(github.ref, 'refs/heads/release-') 109 | 110 | env: 111 | GITHUB_TOKEN: ${{ secrets.MODULES_PAT }} 112 | TF_VAR_names: ${{ needs.build.outputs.TF_VAR_names }} 113 | 114 | permissions: 115 | contents: read 116 | id-token: write 117 | 118 | steps: 119 | - uses: actions/checkout@v4.0.0 120 | 121 | - uses: google-github-actions/auth@v1.1.1 122 | with: 123 | workload_identity_provider: projects/168092815911/locations/global/workloadIdentityPools/oidc/providers/github 124 | service_account: kbst-catalog-repos-tf@nice-road-159709.iam.gserviceaccount.com 125 | 126 | # Apply target kubestack-modules repository 127 | - run: terraform init 128 | working-directory: repositories 129 | 130 | - run: terraform apply --auto-approve 131 | working-directory: repositories 132 | 133 | - name: 'Download build-artifacts' 134 | uses: actions/download-artifact@v4.1.8 135 | with: 136 | name: _dist 137 | path: _dist 138 | 139 | # Push artifact content as brach to target repo 140 | - run: | 141 | export BRANCH=$(echo $GITHUB_REF | sed -e "s#^refs/heads/##") 142 | export NAMEVERSION=$(echo $GITHUB_REF | sed -e "s#^refs/heads/release-##") 143 | export NAME=$(echo $NAMEVERSION | sed -r 's#-v[0-9]+.*$##') 144 | 145 | mkdir "terraform-kustomization-${NAME}" 146 | cd "terraform-kustomization-${NAME}" 147 | 148 | git init . 149 | git config user.name "Kubestack Modules" 150 | git config user.email "hello+modules@kubestack.com" 151 | 152 | git branch -m "${BRANCH}" 153 | git remote add target "https://${{ secrets.MODULES_PAT }}@github.com/kubestack-modules/terraform-kustomization-${NAME}.git" 154 | 155 | unzip ../_dist/module-${NAME}-${GITHUB_SHA}.zip 156 | 157 | git add . 158 | git commit -m "Release ${NAMEVERSION}" 159 | git push target "${BRANCH}" 160 | 161 | 162 | # 163 | # 164 | # 165 | publish-gcs: 166 | runs-on: ubuntu-latest 167 | needs: [build, test-k3d, test-terraform] 168 | if: startsWith(github.ref, 'refs/heads/release-') 169 | 170 | permissions: 171 | id-token: write 172 | 173 | steps: 174 | # Download build artifacts 175 | - name: 'Download build-artifacts' 176 | uses: actions/download-artifact@v4.1.8 177 | with: 178 | name: _dist 179 | path: _dist 180 | 181 | # Upload archive 182 | - uses: google-github-actions/auth@v1.1.1 183 | with: 184 | workload_identity_provider: projects/168092815911/locations/global/workloadIdentityPools/oidc/providers/github 185 | service_account: github-actions-catalog-uploade@nice-road-159709.iam.gserviceaccount.com 186 | 187 | - uses: google-github-actions/setup-gcloud@v1.1.1 188 | 189 | - run: gsutil -m cp _dist/*.zip gs://dev.catalog.kubestack.com 190 | 191 | 192 | # 193 | # 194 | # trigger promotion by tagging release 195 | trigger-promote: 196 | runs-on: ubuntu-latest 197 | needs: [publish-gcs, publish-gh] 198 | if: startsWith(github.ref, 'refs/heads/release-') 199 | 200 | permissions: 201 | contents: write # required for createRef 202 | actions: write # required for createWorkflowDispatch 203 | 204 | steps: 205 | - uses: actions/github-script@v6 206 | with: 207 | script: | 208 | const tag = context.ref.replace("refs/heads/release-", "refs/tags/") 209 | 210 | await github.rest.git.createRef({ 211 | owner: context.repo.owner, 212 | repo: context.repo.repo, 213 | ref: tag, 214 | sha: context.sha, 215 | }); 216 | 217 | await github.rest.actions.createWorkflowDispatch({ 218 | owner: context.repo.owner, 219 | repo: context.repo.repo, 220 | workflow_id: "promote.yml", 221 | ref: tag, 222 | }); 223 | -------------------------------------------------------------------------------- /.github/workflows/promote.yml: -------------------------------------------------------------------------------- 1 | name: Promote 2 | run-name: 'Promote :: ${{ github.ref }}' 3 | 4 | on: workflow_dispatch 5 | 6 | jobs: 7 | # 8 | # 9 | # Promote tagged releases 10 | # from `dev.catalog.kubestack.com` to `catalog.kubestack.com` 11 | promote-gcs: 12 | runs-on: ubuntu-latest 13 | 14 | # promote only for tags 15 | if: startsWith(github.ref, 'refs/tags/') 16 | 17 | permissions: 18 | id-token: write 19 | 20 | steps: 21 | # Setup gcloud CLI 22 | - uses: google-github-actions/auth@v1.1.1 23 | with: 24 | workload_identity_provider: projects/168092815911/locations/global/workloadIdentityPools/oidc/providers/github 25 | service_account: github-actions-catalog-uploade@nice-road-159709.iam.gserviceaccount.com 26 | 27 | - uses: google-github-actions/setup-gcloud@v1.1.1 28 | 29 | # Promote archive 30 | - run: | 31 | export TAG=$(echo $GITHUB_REF | sed -e "s#^refs/tags/##") 32 | export NAME=$(echo $TAG | sed -r 's#-v[0-9]+.*$##') 33 | set +e 34 | while true 35 | do 36 | gsutil cp gs://dev.catalog.kubestack.com/module-${NAME}-${GITHUB_SHA}.zip gs://catalog.kubestack.com/module-${TAG}.zip &&\ 37 | break 38 | sleep 15 39 | done 40 | 41 | # Check promotion 42 | - name: Check promotion 43 | run: | 44 | export TAG=$(echo $GITHUB_REF | sed -e "s#^refs/tags/##") 45 | wget https://storage.googleapis.com/catalog.kubestack.com/module-${TAG}.zip 46 | 47 | promote-gh: 48 | runs-on: ubuntu-latest 49 | 50 | # promote only for tags 51 | if: startsWith(github.ref, 'refs/tags/') 52 | 53 | steps: 54 | - uses: actions/github-script@v6 55 | with: 56 | github-token: ${{ secrets.MODULES_PAT }} 57 | script: | 58 | const nameVersion = context.ref.replace("refs/tags/", "") 59 | const name = nameVersion.replace(/-v[0-9]+.*$/i, "") 60 | const version = nameVersion.replace(`${name}-`, "") 61 | 62 | const targetOwner = "kubestack-modules" 63 | const targetRepo = `terraform-kustomization-${name}` 64 | const targetRef = `refs/tags/${version}` 65 | 66 | const refResp = await github.rest.git.getRef({ 67 | owner: targetOwner, 68 | repo: targetRepo, 69 | ref: context.ref.replace("refs/tags/", "heads/release-"), 70 | }); 71 | 72 | await github.rest.git.createRef({ 73 | owner: targetOwner, 74 | repo: targetRepo, 75 | ref: targetRef, 76 | sha: refResp.data.object.sha, 77 | }); 78 | -------------------------------------------------------------------------------- /.github/workflows/updater.yml: -------------------------------------------------------------------------------- 1 | name: Update 2 | 3 | on: 4 | schedule: 5 | - cron: "0 6 * * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | update: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v1 14 | 15 | # 16 | # 17 | # Run updater 18 | - name: Run updater 19 | uses: ./.github/actions/updater 20 | with: 21 | github_token: "${{ secrets.UPDATER_PTA }}" 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | _dist/ 3 | .terraform/ 4 | 5 | *.tfstate 6 | *.tfstate.* -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at hello@kubestack.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Please follow common sense when creating issues and [the GitHub flow](https://guides.github.com/introduction/flow/) after forking when creating merge requests. 4 | 5 | ## Issues 6 | 7 | Issues are very valuable to this project. 8 | 9 | - Ideas are a valuable source of contributions others can make 10 | - Problems show where this project is lacking 11 | - With a question, you show where contributors can improve the user experience 12 | 13 | Thank you for creating them. 14 | 15 | ## Merge Requests 16 | 17 | Merge requests are a great way to get your ideas into this repository. 18 | 19 | When deciding if I merge in a merge request I look at the following things: 20 | 21 | ### Does it state intent 22 | 23 | You should be clear about which problem you're trying to solve with your 24 | contribution. 25 | 26 | For example: 27 | 28 | > Add link to code of conduct in README.md 29 | 30 | Doesn't tell me anything about why you're doing that 31 | 32 | > Add link to code of conduct in README.md because users don't always look in the CONTRIBUTING.md 33 | 34 | Tells me the problem that you have found, and the merge request shows me the action you have taken to solve it. 35 | 36 | ### Is it of good quality 37 | 38 | - There are no spelling mistakes 39 | - It reads well 40 | - For English language contributions: Has a good score on [Grammarly](grammarly.com) or [Hemingway Editor](http://www.hemingwayapp.com/) 41 | 42 | ### Does it move this repository closer to my vision for the repository 43 | 44 | The aim of this repository is documented at [README](README.md). 45 | 46 | ### Does it follow the contributor covenant 47 | 48 | This repository has a [code of conduct](./CODE_OF_CONDUCT.md), I will remove things that do not respect it. 49 | 50 | The origin of this document is from [PurpleBooth Templates](https://github.com/PurpleBooth/a-good-readme-template). 51 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | _all: test-terraform dist 2 | 3 | GITHUB_REF ?= $(shell echo "refs/heads/"`git rev-parse --abbrev-ref HEAD`) 4 | GITHUB_SHA ?= $(shell echo `git rev-parse --verify HEAD^{commit}`) 5 | 6 | test-terraform: 7 | cd src/_terraform_module/; terraform test 8 | 9 | dist: 10 | docker build -t catalog:dist-${GITHUB_SHA} .github/actions/builder 11 | docker run --rm -v `pwd`:/workdir:z --workdir=/workdir -e GITHUB_REF=${GITHUB_REF} -e GITHUB_SHA=${GITHUB_SHA} catalog:dist-${GITHUB_SHA} 12 | 13 | k3d: 14 | k3d cluster delete catalog-tests 15 | k3d cluster create catalog-tests -s 1 -a 3 --no-lb --k3s-arg "--disable=traefik@server:*" --k3s-arg "--disable=servicelb@server:*" --k3s-arg="--node-label=ingress-ready=true@agent:*" 16 | kubectl config use-context k3d-catalog-tests 17 | kubectl cluster-info 18 | 19 | get-matrix: dist 20 | docker build -t catalog:get-matrix-${GITHUB_SHA} .github/actions/get-matrix 21 | docker run --network host --rm -v `pwd`/_dist:/_dist:z catalog:get-matrix-${GITHUB_SHA} 22 | 23 | test-k3d: dist 24 | docker build -t catalog:test-k3d-${GITHUB_SHA} test/k3d/ 25 | docker run --network host --rm -v `pwd`/_dist:/_dist:z -v ${HOME}/.kube/config:/opt/test/.kubeconfig:z catalog:test-k3d-${GITHUB_SHA} $(test-name) $(test-variant) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Kubestack, The Open Source Gitops Framework 3 |

4 | 5 |

Kubestack Catalog

6 |

Manifest Catalog for the Kubestack Gitops Framework

7 | 8 |
9 | 10 | [![Status](https://img.shields.io/badge/status-active-success.svg)]() 11 | [![GitHub Issues](https://img.shields.io/github/issues/kbst/catalog.svg)](https://github.com/kbst/catalog/issues) 12 | [![GitHub Pull Requests](https://img.shields.io/github/issues-pr/kbst/catalog.svg)](https://github.com/kbst/catalog/pulls) 13 | 14 |
15 | 16 |
17 | 18 | ![GitHub Repo stars](https://img.shields.io/github/stars/kbst/catalog?style=social) 19 | ![Twitter Follow](https://img.shields.io/twitter/follow/kubestack?style=social) 20 | 21 |
22 | 23 | 24 |

Join Our Contributors!

25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | ## Introduction 35 | 36 | This repository holds the kustomize source manifests and build toolchain for the [Kubestack catalog of Kustomize bases](https://www.kubestack.com/catalog). 37 | 38 | This is maintained as part of the [Terraform GitOps framework Kubestack](https://www.kubestack.com/). 39 | 40 | 41 | ## Getting Started with Kubestack 42 | 43 | For the easiest way to get started, [visit the official Kubestack quickstart](https://www.kubestack.com/infrastructure/documentation/quickstart). This tutorial will help you get started with the Kubestack GitOps framework. It is divided into three steps. 44 | 45 | 1. Develop Locally 46 | * Scaffold your repository and tweak your config in a local development environment that simulates your actual cloud configuration using Kubernetes in Docker (KinD). 47 | 3. Provision Infrastructure 48 | * Set-up cloud prerequisites and bootstrap Kubestack's environment and clusters on your cloud provider for the first time. 49 | 4. Set-up Automation 50 | * Integrate CI/CD to automate changes following Kubestack's GitOps workflow. 51 | 52 | 53 | ## Getting Help 54 | 55 | **Official Documentation** 56 | Refer to the [official documentation](https://www.kubestack.com/framework/documentation) for a deeper dive into how to use and configure Kubetack. 57 | 58 | **Community Help** 59 | If you have any questions while following the tutorial, join the [#kubestack](https://app.slack.com/client/T09NY5SBT/CMBCT7XRQ) channel on the Kubernetes community. To create an account request an [invitation](https://slack.k8s.io/). 60 | 61 | **Professional Services** 62 | For organizations interested in accelerating their GitOps journey, [professional services](https://www.kubestack.com/lp/professional-services) are available. 63 | 64 | 65 | ## Contributing 66 | Contributions to the Kubestack framework are welcome and encouraged. Before contributing, please read the [Contributing](./CONTRIBUTING.md) and [Code of Conduct](./CODE_OF_CONDUCT.md) Guidelines. 67 | 68 | One super simple way to contribute to the success of this project is to give it a star. 69 | 70 |
71 | 72 | ![GitHub Repo stars](https://img.shields.io/github/stars/kbst/catalog?style=social) 73 | 74 |
75 | 76 | 77 | ## Development Workflow 78 | 79 | 1. Fork this repository 80 | 1. Work in a feature branch 81 | 1. Validate your changes locally 82 | ``` 83 | # Build the helper image 84 | # optional `--build-arg KUSTOMIZE_VERSION=3.2.3` 85 | docker build -t python3-kustomize . 86 | 87 | # Run dist.py to generate the archives 88 | docker run \ 89 | --rm \ 90 | -u `id -u`:`id -g` \ 91 | -v `pwd`:/workspace \ 92 | -w /workspace \ 93 | -e GIT_SHA=`git rev-parse --verify HEAD^{commit}` \ 94 | -e GIT_REF=refs/heads/`git rev-parse --abbrev-ref HEAD` \ 95 | python3-kustomize \ 96 | ./dist.py 97 | 98 | # Run test.py to test your changes 99 | docker run \ 100 | --rm \ 101 | -u `id -u`:`id -g` \ 102 | -v `pwd`:/workspace \ 103 | -w /workspace \ 104 | python3-kustomize \ 105 | ./test.py 106 | 107 | ``` 108 | 1. Send a pull-request 109 | 110 | 111 | ## Making a Release 112 | 113 | 1. Create a Git tag in the format `name-version` 114 | * name must be the name of the catalog entry to release, e.g. `memcached` 115 | * version must be in format `major.minor.patch` prefixed with a `v`, 116 | e.g. `v0.0.1` 117 | 2. Push the tag to trigger CI/CD 118 | 119 | 120 | ## Kubestack Repositories 121 | * [kbst/terraform-kubestack](https://github.com/kbst/terraform-kubestack) 122 | * Terraform GitOps Framework - Everything you need to build reliable automation for AKS, EKS and GKE Kubernetes clusters in one free and open-source framework. 123 | * [kbst/kbst](https://github.com/kbst/kbst) 124 | * Kubestack Framework CLI - All-in-one CLI to scaffold your Infrastructure as Code repository and deploy your entire platform stack locally for faster iteration. 125 | * [kbst/terraform-provider-kustomization](https://github.com/kbst/terraform-provider-kustomization) 126 | * Kustomize Terraform Provider - A Kubestack maintained Terraform provider for Kustomize, available in the [Terraform registry](https://registry.terraform.io/providers/kbst/kustomization/latest). 127 | * [kbst/catalog](https://github.com/kbst/catalog) (this repository) 128 | * Catalog of cluster services as Kustomize bases - Continuously tested and updated Kubernetes services, installed and customizable using native Terraform syntax. 129 | 130 | -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kbst/catalog/79d2a797940685d7e576a89f50d8ce761ddbc3b6/assets/favicon.png -------------------------------------------------------------------------------- /repositories/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/integrations/github" { 5 | version = "5.36.0" 6 | constraints = "~> 5.0" 7 | hashes = [ 8 | "h1:a1w2jOfagQ7EtArkrvYf0scbI41ySFo9PmBmVmCyDiI=", 9 | "zh:16a8018d4bf8febca9a5482ce66559646f760184e89bb39ed34592451681a252", 10 | "zh:1c2781f9938df9ef571d90281c4319fbc1d961afdce6aec4a8faac12da16871a", 11 | "zh:48b2155411f1e6dd996ab4abf3846decf6d1404d86ceeb5652e4a982195de4b9", 12 | "zh:4f21a97e4d9e5569373ba59a7cf564ab6db9cdea1930f760179ac349f2d03030", 13 | "zh:57d39fb320b3f3ffc55d80ae8dd205a2cfc7d9fafb270a371500370f526e0baa", 14 | "zh:89b444491a25d789288a80a32531991a29565b6873af5f37face588d04fcff35", 15 | "zh:8db5d4bdc47bb0fd71971954bb0c22dcd25c9550b1598f5075fd0a92c040d7ac", 16 | "zh:95913de9a0c348bdc0cc1fa727fa8f4c092a311a56981ab7c48959a82a363a70", 17 | "zh:9e9aeac23cec279cc0eea6e21eeb8977e291ed9cf831a8d09a147eeda4ef22d8", 18 | "zh:bc7a19a4cacb20e26a4021942acefa7b1a05de22250651ef5e3fb36272a5e85b", 19 | "zh:c68d27aedec0e6f6551e4db79cc86b6098c626594272265bb3d84bb0bdf99b47", 20 | "zh:ca37ae109cf7537b1cb188244ee028df3da34db803a10e9d64d44da9d88b02e7", 21 | "zh:db1ac55a2b2d15678845f5a3cde8fae75ef6b67d1830e37488a9e65fbd2caf09", 22 | "zh:f14b1b46320455edaed88b74c7324ecea43b5daf382c4cd891fd88addb8b8940", 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /repositories/README.md.tpl: -------------------------------------------------------------------------------- 1 | # Kubestack ${title(name)} Terraform Module for Kubernetes 2 | 3 | This repository exists to publish the `${name}` module on the Terraform registry. 4 | Module versions built from upstream releases are stored in `release-$version` branches. 5 | The branch will be tagged to publish on the Terraform registry. 6 | 7 | ## Learn more about the ${title(name)} platform service module 8 | 9 | * [Catalog page](https://www.kubestack.com/catalog/${name}) 10 | * [Kubestack module types](https://www.kubestack.com/framework/documentation/module-types/) 11 | 12 | ## Contribute to the ${title(name)} module 13 | 14 | All content here is generated from upstream releases. 15 | Any contributions have to happen in the [kbst/catalog](https://github.com/kbst/catalog) repository. 16 | -------------------------------------------------------------------------------- /repositories/main.tf: -------------------------------------------------------------------------------- 1 | resource "github_repository" "module" { 2 | for_each = var.names 3 | 4 | name = "terraform-kustomization-${each.key}" 5 | description = "${title(each.key)} Terraform Module for Kubernetes by Kubestack" 6 | homepage_url = "https://www.kubestack.com/catalog/${each.key}/" 7 | 8 | visibility = "public" 9 | 10 | auto_init = true 11 | 12 | has_issues = false 13 | has_discussions = false 14 | has_projects = false 15 | has_wiki = false 16 | has_downloads = false 17 | 18 | archive_on_destroy = true 19 | 20 | topics = [ 21 | "kubernetes", 22 | "terraform", 23 | "terraform-module", 24 | "kustomize", 25 | "kubestack", 26 | each.key, 27 | ] 28 | 29 | lifecycle { 30 | prevent_destroy = true 31 | } 32 | } 33 | 34 | resource "github_branch" "main" { 35 | for_each = var.names 36 | 37 | repository = github_repository.module[each.key].name 38 | branch = "main" 39 | 40 | lifecycle { 41 | prevent_destroy = true 42 | } 43 | } 44 | 45 | resource "github_branch_default" "default" { 46 | for_each = var.names 47 | 48 | repository = github_repository.module[each.key].name 49 | branch = github_branch.main[each.key].branch 50 | 51 | lifecycle { 52 | prevent_destroy = true 53 | } 54 | } 55 | 56 | resource "github_repository_file" "readme" { 57 | for_each = var.names 58 | 59 | repository = github_repository.module[each.key].name 60 | branch = github_branch.main[each.key].branch 61 | 62 | file = "README.md" 63 | content = templatefile( 64 | "${path.root}/README.md.tpl", 65 | { name : each.key } 66 | ) 67 | 68 | commit_message = "Create placeholder README.md for main branch" 69 | commit_author = "Philipp Strube" 70 | commit_email = "pst@kubestack.com" 71 | overwrite_on_create = true 72 | 73 | lifecycle { 74 | prevent_destroy = true 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /repositories/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | github = { 4 | source = "integrations/github" 5 | version = "~> 5.0" 6 | } 7 | } 8 | } 9 | 10 | provider "github" { 11 | owner = var.owner 12 | } 13 | -------------------------------------------------------------------------------- /repositories/state.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "gcs" { 3 | bucket = "terraform-state-kubestack-catalog-repositories-d18c22a" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /repositories/variables.tf: -------------------------------------------------------------------------------- 1 | variable "names" { 2 | type = set(string) 3 | description = "List of entry names to create repositories for." 4 | } 5 | 6 | variable "owner" { 7 | type = string 8 | description = "The name of the organization owning the repositories." 9 | default = "kubestack-modules" 10 | } 11 | -------------------------------------------------------------------------------- /src/_terraform_module/configuration.tf: -------------------------------------------------------------------------------- 1 | module "configuration" { 2 | source = "github.com/kbst/terraform-kubestack//common/configuration?ref=v0.18.0-beta.0" 3 | configuration = var.configuration 4 | base_key = var.configuration_base_key 5 | } 6 | 7 | locals { 8 | # current workspace config 9 | cfg = lookup(module.configuration.merged, terraform.workspace) 10 | 11 | variant = local.cfg["variant"] != null ? local.cfg["variant"] : local.default_variant 12 | 13 | additional_resources = local.cfg["additional_resources"] != null ? local.cfg["additional_resources"] : [] 14 | } 15 | -------------------------------------------------------------------------------- /src/_terraform_module/data_source.tf: -------------------------------------------------------------------------------- 1 | data "kustomization_overlay" "current" { 2 | common_annotations = local.cfg["common_annotations"] 3 | 4 | common_labels = local.cfg["common_labels"] 5 | 6 | components = local.cfg["components"] 7 | 8 | dynamic "config_map_generator" { 9 | for_each = local.cfg["config_map_generator"] != null ? local.cfg["config_map_generator"] : [] 10 | iterator = i 11 | content { 12 | name = i.value["name"] 13 | namespace = i.value["namespace"] 14 | behavior = i.value["behavior"] 15 | envs = i.value["envs"] 16 | files = i.value["files"] 17 | literals = i.value["literals"] 18 | options { 19 | labels = lookup(i.value, "options") != null ? i.value["options"]["labels"] : null 20 | annotations = lookup(i.value, "options") != null ? i.value["options"]["annotations"] : null 21 | disable_name_suffix_hash = lookup(i.value, "options") != null ? i.value["options"]["disable_name_suffix_hash"] : null 22 | } 23 | } 24 | } 25 | 26 | crds = local.cfg["crds"] 27 | 28 | generators = local.cfg["generators"] 29 | 30 | dynamic "generator_options" { 31 | for_each = local.cfg["generator_options"] != null ? [local.cfg["generator_options"]] : [] 32 | iterator = i 33 | content { 34 | labels = i.value["labels"] 35 | annotations = i.value["annotations"] 36 | disable_name_suffix_hash = i.value["disable_name_suffix_hash"] 37 | } 38 | } 39 | 40 | dynamic "images" { 41 | for_each = lookup(local.cfg, "images") != null ? lookup(local.cfg, "images") : [] 42 | iterator = i 43 | content { 44 | name = i.value["name"] 45 | new_name = i.value["new_name"] 46 | new_tag = i.value["new_tag"] 47 | digest = i.value["digest"] 48 | } 49 | } 50 | 51 | name_prefix = local.cfg["name_prefix"] 52 | 53 | namespace = local.cfg["namespace"] 54 | 55 | name_suffix = local.cfg["name_suffix"] 56 | 57 | dynamic "patches" { 58 | for_each = local.cfg["patches"] != null ? local.cfg["patches"] : [] 59 | iterator = i 60 | content { 61 | path = i.value["path"] 62 | patch = i.value["patch"] 63 | 64 | dynamic "target" { 65 | for_each = i.value["target"] != null ? toset([i.value["target"]]) : toset([]) 66 | iterator = j 67 | content { 68 | group = j.value["group"] 69 | version = j.value["version"] 70 | kind = j.value["kind"] 71 | name = j.value["name"] 72 | namespace = j.value["namespace"] 73 | label_selector = j.value["label_selector"] 74 | annotation_selector = j.value["annotation_selector"] 75 | } 76 | } 77 | } 78 | } 79 | 80 | dynamic "replicas" { 81 | for_each = local.cfg["replicas"] != null ? local.cfg["replicas"] : [] 82 | iterator = i 83 | content { 84 | name = i.value["name"] 85 | count = i.value["count"] 86 | } 87 | } 88 | 89 | dynamic "secret_generator" { 90 | for_each = local.cfg["secret_generator"] != null ? local.cfg["secret_generator"] : [] 91 | iterator = i 92 | content { 93 | name = i.value["name"] 94 | namespace = i.value["namespace"] 95 | behavior = i.value["behavior"] 96 | type = i.value["type"] 97 | envs = i.value["envs"] 98 | files = i.value["files"] 99 | literals = i.value["literals"] 100 | options { 101 | labels = lookup(i.value, "options") != null ? i.value["options"]["labels"] : null 102 | annotations = lookup(i.value, "options") != null ? i.value["options"]["annotations"] : null 103 | disable_name_suffix_hash = lookup(i.value, "options") != null ? i.value["options"]["disable_name_suffix_hash"] : null 104 | } 105 | } 106 | } 107 | 108 | transformers = local.cfg["transformers"] 109 | 110 | dynamic "vars" { 111 | for_each = local.cfg["vars"] != null ? local.cfg["vars"] : [] 112 | iterator = i 113 | content { 114 | name = i.value["name"] 115 | obj_ref { 116 | api_version = lookup(i.value, "obj_ref") != null ? i.value["obj_ref"]["api_version"] : null 117 | group = lookup(i.value, "obj_ref") != null ? i.value["obj_ref"]["group"] : null 118 | version = lookup(i.value, "obj_ref") != null ? i.value["obj_ref"]["version"] : null 119 | kind = lookup(i.value, "obj_ref") != null ? i.value["obj_ref"]["kind"] : null 120 | name = lookup(i.value, "obj_ref") != null ? i.value["obj_ref"]["name"] : null 121 | namespace = lookup(i.value, "obj_ref") != null ? i.value["obj_ref"]["namespace"] : null 122 | } 123 | field_ref { 124 | field_path = lookup(i.value, "field_ref") != null ? i.value["field_ref"]["field_path"] : null 125 | } 126 | } 127 | } 128 | 129 | resources = local.cfg["resources"] != null ? local.cfg["resources"] : concat(["${path.module}/${local.variant}/"], local.additional_resources) 130 | } 131 | -------------------------------------------------------------------------------- /src/_terraform_module/main._tf: -------------------------------------------------------------------------------- 1 | resource "kustomization_resource" "p0" { 2 | for_each = data.kustomization_overlay.current.ids_prio[0] 3 | 4 | manifest = ( 5 | contains(var.sensitive_group_kinds, regex("(?P.*/.*)/.*/.*", each.value)["group_kind"]) 6 | ? sensitive(data.kustomization_overlay.current.manifests[each.value]) 7 | : data.kustomization_overlay.current.manifests[each.value] 8 | ) 9 | } 10 | 11 | resource "kustomization_resource" "p1" { 12 | for_each = data.kustomization_overlay.current.ids_prio[1] 13 | 14 | manifest = ( 15 | contains(var.sensitive_group_kinds, regex("(?P.*/.*)/.*/.*", each.value)["group_kind"]) 16 | ? sensitive(data.kustomization_overlay.current.manifests[each.value]) 17 | : data.kustomization_overlay.current.manifests[each.value] 18 | ) 19 | 20 | depends_on = [kustomization_resource.p0] 21 | } 22 | 23 | resource "kustomization_resource" "p2" { 24 | for_each = data.kustomization_overlay.current.ids_prio[2] 25 | 26 | manifest = ( 27 | contains(var.sensitive_group_kinds, regex("(?P.*/.*)/.*/.*", each.value)["group_kind"]) 28 | ? sensitive(data.kustomization_overlay.current.manifests[each.value]) 29 | : data.kustomization_overlay.current.manifests[each.value] 30 | ) 31 | 32 | depends_on = [kustomization_resource.p1] 33 | } 34 | -------------------------------------------------------------------------------- /src/_terraform_module/test_default_variant.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default_variant = "test_kustomization" 3 | } 4 | -------------------------------------------------------------------------------- /src/_terraform_module/test_kustomization/Kustomization: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | namespace: test 5 | 6 | resources: 7 | - namespace.yaml 8 | - configmap.yaml 9 | - deployment.yaml 10 | -------------------------------------------------------------------------------- /src/_terraform_module/test_kustomization/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | creationTimestamp: null 5 | name: test 6 | annotations: 7 | test: test 8 | labels: 9 | test: test 10 | -------------------------------------------------------------------------------- /src/_terraform_module/test_kustomization/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app: test 7 | name: test 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: test 13 | strategy: {} 14 | template: 15 | metadata: 16 | creationTimestamp: null 17 | labels: 18 | app: test 19 | spec: 20 | containers: 21 | - image: busybox 22 | name: busybox 23 | command: 24 | - sleep 25 | - "60" 26 | resources: {} 27 | status: {} 28 | -------------------------------------------------------------------------------- /src/_terraform_module/test_kustomization/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: test -------------------------------------------------------------------------------- /src/_terraform_module/test_outputs.tf: -------------------------------------------------------------------------------- 1 | output "ids" { 2 | value = data.kustomization_overlay.current.ids 3 | } 4 | 5 | output "ids_prio" { 6 | value = data.kustomization_overlay.current.ids_prio 7 | } 8 | 9 | output "manifests" { 10 | value = data.kustomization_overlay.current.manifests 11 | } 12 | -------------------------------------------------------------------------------- /src/_terraform_module/test_provider.tf: -------------------------------------------------------------------------------- 1 | provider "kustomization" { 2 | kubeconfig_raw = yamlencode({ 3 | apiVersion = "v1" 4 | current-context = "test" 5 | clusters = [ 6 | { 7 | name = "test" 8 | cluster = { 9 | certificate-authority-data = "" 10 | server = "https://127.0.0.1:8080" 11 | } 12 | } 13 | ] 14 | users = [ 15 | { 16 | name = "test" 17 | user = { 18 | token = "" 19 | } 20 | } 21 | ] 22 | contexts = [ 23 | { 24 | name = "test" 25 | context = { 26 | cluster = "test" 27 | user = "test" 28 | } 29 | } 30 | ] 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /src/_terraform_module/tests/common_annotations/test.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | test = { 4 | source = "terraform.io/builtin/test" 5 | } 6 | } 7 | } 8 | 9 | module "mut" { 10 | source = "../.." 11 | 12 | configuration = { 13 | (terraform.workspace) = { 14 | common_annotations = { 15 | "terraform-workspace" = terraform.workspace 16 | } 17 | } 18 | } 19 | 20 | configuration_base_key = terraform.workspace 21 | } 22 | 23 | resource "test_assertions" "common_annotations" { 24 | component = "common_annotations" 25 | 26 | equal "metadata_annotations_is_correct" { 27 | description = "metadata_annotations_is_correct" 28 | got = jsondecode(module.mut.manifests["_/Namespace/_/test"]).metadata.annotations 29 | want = { 30 | "terraform-workspace" = terraform.workspace 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/_terraform_module/tests/common_labels/test.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | test = { 4 | source = "terraform.io/builtin/test" 5 | } 6 | } 7 | } 8 | 9 | module "mut" { 10 | source = "../.." 11 | 12 | configuration = { 13 | (terraform.workspace) = { 14 | common_labels = { 15 | "terraform-workspace" = terraform.workspace 16 | } 17 | } 18 | } 19 | 20 | configuration_base_key = terraform.workspace 21 | } 22 | 23 | resource "test_assertions" "common_labels" { 24 | component = "common_labels" 25 | 26 | equal "metadata_labels_are_correct" { 27 | description = "metadata_labels_are_correct" 28 | got = jsondecode(module.mut.manifests["_/Namespace/_/test"]).metadata.labels 29 | want = { 30 | "terraform-workspace" = terraform.workspace 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/_terraform_module/tests/config_map_generator/env: -------------------------------------------------------------------------------- 1 | TEST=value 2 | -------------------------------------------------------------------------------- /src/_terraform_module/tests/config_map_generator/test.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | test = { 4 | source = "terraform.io/builtin/test" 5 | } 6 | } 7 | } 8 | 9 | locals { 10 | test_key = "test" 11 | test_value = "value" 12 | 13 | test_file = "env" 14 | } 15 | 16 | module "mut" { 17 | source = "../.." 18 | 19 | configuration = { 20 | apps = { 21 | namespace = "test-cmgen" 22 | 23 | config_map_generator = [ 24 | { 25 | name = "test" 26 | behavior = "merge" 27 | literals = [ 28 | "${local.test_key}=${local.test_value}" 29 | ] 30 | options = { 31 | annotations = { 32 | source = "from_base_merged" 33 | } 34 | } 35 | }, 36 | { 37 | name = "test-literal" 38 | behavior = "create" 39 | literals = [ 40 | "${local.test_key}=${local.test_value}" 41 | ] 42 | options = { 43 | labels = { 44 | source = "from_literal" 45 | } 46 | } 47 | }, 48 | { 49 | name = "test-envs" 50 | envs = [ 51 | "${path.module}/${local.test_file}" 52 | ] 53 | options = { 54 | disable_name_suffix_hash = true 55 | } 56 | }, 57 | { 58 | name = "test-files" 59 | files = [ 60 | "${path.module}/${local.test_file}" 61 | ] 62 | } 63 | ] 64 | } 65 | 66 | (terraform.workspace) = {} 67 | } 68 | 69 | configuration_base_key = "apps" 70 | } 71 | 72 | locals { 73 | manifests = { for k, v in module.mut.manifests : k => jsondecode(v) } 74 | } 75 | 76 | //resource "test_assertions" "debug" { 77 | // component = "config_map_generator" 78 | // 79 | // equal "value_correct_debug" { 80 | // description = "key in configmap data has correct value" 81 | // got = local.manifests 82 | // want = {} 83 | // } 84 | //} 85 | 86 | resource "test_assertions" "from_base_merge" { 87 | component = "config_map_generator" 88 | 89 | check "key_exists_from_base_merge" { 90 | description = "key_exists_from_base_merge" 91 | condition = contains(keys(local.manifests["_/ConfigMap/test-cmgen/test"].data), local.test_key) 92 | } 93 | 94 | equal "value_correct_from_base_merge" { 95 | description = "value_correct_from_base_merge" 96 | got = local.manifests["_/ConfigMap/test-cmgen/test"].data[local.test_key] 97 | want = local.test_value 98 | } 99 | 100 | equal "annotation_correct_from_base_merge" { 101 | description = "annotation_correct_from_base_merge" 102 | got = local.manifests["_/ConfigMap/test-cmgen/test"].metadata.annotations["source"] 103 | want = "from_base_merged" 104 | } 105 | } 106 | 107 | resource "test_assertions" "from_literal" { 108 | component = "config_map_generator" 109 | 110 | check "key_exists_from_literal" { 111 | description = "key_exists_from_literal" 112 | condition = contains(keys(local.manifests["_/ConfigMap/test-cmgen/test-literal-cc854d6db8"].data), local.test_key) 113 | } 114 | 115 | equal "value_correct_from_literal" { 116 | description = "value_correct_from_literal" 117 | got = local.manifests["_/ConfigMap/test-cmgen/test-literal-cc854d6db8"].data[local.test_key] 118 | want = local.test_value 119 | } 120 | 121 | equal "annotation_correct_from_base_merge" { 122 | description = "annotation_correct_from_base_merge" 123 | got = local.manifests["_/ConfigMap/test-cmgen/test-literal-cc854d6db8"].metadata.labels.source 124 | want = "from_literal" 125 | } 126 | } 127 | 128 | resource "test_assertions" "from_envs" { 129 | component = "config_map_generator" 130 | 131 | check "key_exists_from_envs" { 132 | description = "key_exists_from_envs" 133 | condition = contains(keys(local.manifests["_/ConfigMap/test-cmgen/test-envs"].data), upper(local.test_key)) 134 | } 135 | 136 | equal "value_correct_from_envs" { 137 | description = "value_correct_from_envs" 138 | got = local.manifests["_/ConfigMap/test-cmgen/test-envs"].data[upper(local.test_key)] 139 | want = local.test_value 140 | } 141 | } 142 | 143 | resource "test_assertions" "from_files" { 144 | component = "config_map_generator" 145 | 146 | check "key_exists_from_files" { 147 | description = "key_exists_from_files" 148 | condition = contains(keys(local.manifests["_/ConfigMap/test-cmgen/test-files-k7fk56g6g8"].data), local.test_file) 149 | } 150 | 151 | equal "value_correct_from_files" { 152 | description = "value_correct_from_files" 153 | got = local.manifests["_/ConfigMap/test-cmgen/test-files-k7fk56g6g8"].data[local.test_file] 154 | want = file("${path.module}/${local.test_file}") 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/_terraform_module/tests/generator_options/test.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | test = { 4 | source = "terraform.io/builtin/test" 5 | } 6 | } 7 | } 8 | 9 | locals { 10 | test_key = "test" 11 | test_value = "value" 12 | 13 | test_file = "env" 14 | } 15 | 16 | module "mut" { 17 | source = "../.." 18 | 19 | configuration = { 20 | apps = { 21 | namespace = "test-genopts" 22 | 23 | generator_options = { 24 | annotations = { 25 | (local.test_key) = local.test_value 26 | } 27 | 28 | labels = { 29 | (local.test_key) = local.test_value 30 | } 31 | 32 | disable_name_suffix_hash = true 33 | } 34 | 35 | config_map_generator = [ 36 | { 37 | name = "test-literal" 38 | behavior = "create" 39 | literals = [] 40 | }, 41 | ] 42 | 43 | secret_generator = [ 44 | { 45 | name = "test-literal" 46 | behavior = "create" 47 | literals = [] 48 | }, 49 | ] 50 | } 51 | 52 | (terraform.workspace) = {} 53 | } 54 | 55 | configuration_base_key = "apps" 56 | } 57 | 58 | locals { 59 | manifests = { for k, v in module.mut.manifests : k => jsondecode(v) } 60 | } 61 | 62 | resource "test_assertions" "configmap" { 63 | component = "generator_options" 64 | 65 | check "cm_has_no_name_suffix" { 66 | description = "cm_has_no_name_suffix" 67 | condition = contains(keys(local.manifests), "_/ConfigMap/test-genopts/test-literal") 68 | } 69 | 70 | equal "cm_annotation_correct" { 71 | description = "cm_annotation_correct" 72 | got = local.manifests["_/ConfigMap/test-genopts/test-literal"].metadata.annotations[local.test_key] 73 | want = local.test_value 74 | } 75 | 76 | equal "cm_labels_correct" { 77 | description = "cm_labels_correct" 78 | got = local.manifests["_/ConfigMap/test-genopts/test-literal"].metadata.labels[local.test_key] 79 | want = local.test_value 80 | } 81 | } 82 | 83 | resource "test_assertions" "secret" { 84 | component = "generator_options" 85 | 86 | check "secret_has_no_name_suffix" { 87 | description = "secret_has_no_name_suffix" 88 | condition = contains(keys(local.manifests), "_/Secret/test-genopts/test-literal") 89 | } 90 | 91 | equal "secret_annotation_correct" { 92 | description = "secret_annotation_correct" 93 | got = local.manifests["_/Secret/test-genopts/test-literal"].metadata.annotations[local.test_key] 94 | want = local.test_value 95 | } 96 | 97 | equal "secret_labels_correct" { 98 | description = "secret_labels_correct" 99 | got = local.manifests["_/Secret/test-genopts/test-literal"].metadata.labels[local.test_key] 100 | want = local.test_value 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/_terraform_module/tests/images/test.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | test = { 4 | source = "terraform.io/builtin/test" 5 | } 6 | } 7 | } 8 | 9 | module "mut" { 10 | source = "../.." 11 | 12 | configuration = { 13 | (terraform.workspace) = { 14 | images = [ 15 | { 16 | name = "busybox" 17 | new_name = "new_name" 18 | new_tag = "new_tag" 19 | } 20 | ] 21 | } 22 | } 23 | 24 | configuration_base_key = terraform.workspace 25 | } 26 | 27 | resource "test_assertions" "images" { 28 | component = "images" 29 | 30 | equal "image_is_correct" { 31 | description = "image_is_correct" 32 | got = jsondecode(module.mut.manifests["apps/Deployment/test/test"]).spec.template.spec.containers[0].image 33 | want = "new_name:new_tag" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/_terraform_module/tests/inheritance/test.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | test = { 4 | source = "terraform.io/builtin/test" 5 | } 6 | } 7 | } 8 | 9 | module "mut_upstream" { 10 | source = "../.." 11 | 12 | configuration = { 13 | upstream = { 14 | namespace = "from-upstream" 15 | name_prefix = "pre-" 16 | name_suffix = "-suf" 17 | } 18 | 19 | (terraform.workspace) = {} 20 | } 21 | 22 | configuration_base_key = "upstream" 23 | } 24 | 25 | resource "test_assertions" "from_upstream" { 26 | component = "configuration_inheritance_from_upstream" 27 | 28 | equal "namespace_name_changed" { 29 | description = "namespace_name_changed" 30 | got = jsondecode(module.mut_upstream.manifests["_/Namespace/_/from-upstream"]).metadata.name 31 | want = "from-upstream" 32 | } 33 | 34 | equal "deployment_has_prefix_and_suffix" { 35 | description = "deployment_has_prefix_and_suffix" 36 | got = jsondecode(module.mut_upstream.manifests["apps/Deployment/from-upstream/pre-test-suf"]).metadata.name 37 | want = "pre-test-suf" 38 | } 39 | } 40 | 41 | module "mut_workspace" { 42 | source = "../.." 43 | 44 | configuration = { 45 | upstream = { 46 | namespace = "from-upstream" 47 | } 48 | 49 | (terraform.workspace) = { 50 | namespace = "from-workspace" 51 | } 52 | } 53 | 54 | configuration_base_key = "upstream" 55 | } 56 | 57 | resource "test_assertions" "from_workspace" { 58 | component = "configuration_overwritten_by_workspace" 59 | 60 | equal "configuration_gets_overwritten_by_workspace" { 61 | description = "configuration_gets_overwritten_by_workspace" 62 | got = jsondecode(module.mut_workspace.manifests["_/Namespace/_/from-workspace"]).metadata.name 63 | want = "from-workspace" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/_terraform_module/tests/patches/patch_deployment_resources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: test 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name: busybox 10 | resources: 11 | requests: 12 | cpu: 100m 13 | memory: 100Mi 14 | limits: 15 | cpu: 100m 16 | memory: 100Mi 17 | -------------------------------------------------------------------------------- /src/_terraform_module/tests/patches/test.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | test = { 4 | source = "terraform.io/builtin/test" 5 | } 6 | } 7 | } 8 | 9 | module "mut" { 10 | source = "../.." 11 | 12 | configuration = { 13 | (terraform.workspace) = { 14 | patches = [ 15 | { 16 | path = "${path.module}/patch_deployment_resources.yaml" 17 | }, 18 | { 19 | patch = <<-EOF 20 | - op: replace 21 | path: /metadata/name 22 | value: newname 23 | EOF 24 | target = { 25 | group = "" 26 | version = "v1" 27 | kind = "ConfigMap" 28 | name = "test" 29 | namespace = "test" 30 | label_selector = "test" 31 | annotation_selector = "test" 32 | } 33 | } 34 | ] 35 | } 36 | } 37 | 38 | configuration_base_key = terraform.workspace 39 | } 40 | 41 | locals { 42 | manifests = { for k, v in module.mut.manifests : k => jsondecode(v) } 43 | } 44 | 45 | resource "test_assertions" "patches_deployment_resources" { 46 | component = "patches_deployment_resources" 47 | 48 | equal "resources_are_correct" { 49 | description = "resources_are_correct" 50 | got = local.manifests["apps/Deployment/test/test"].spec.template.spec.containers[0].resources 51 | want = { 52 | limits = { 53 | cpu = "100m" 54 | memory = "100Mi" 55 | } 56 | requests = { 57 | cpu = "100m" 58 | memory = "100Mi" 59 | } 60 | } 61 | } 62 | } 63 | 64 | resource "test_assertions" "patches_rename_configmap" { 65 | component = "patches_rename_configmap" 66 | 67 | check "changed_configmap_name_in_manifests" { 68 | description = "changed_configmap_name_in_manifests" 69 | condition = contains(keys(local.manifests), "_/ConfigMap/test/newname") 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/_terraform_module/tests/replicas/test.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | test = { 4 | source = "terraform.io/builtin/test" 5 | } 6 | } 7 | } 8 | 9 | module "mut" { 10 | source = "../.." 11 | 12 | configuration = { 13 | (terraform.workspace) = { 14 | replicas = [ 15 | { 16 | name = "test" 17 | count = 3 18 | } 19 | ] 20 | } 21 | } 22 | 23 | configuration_base_key = terraform.workspace 24 | } 25 | 26 | resource "test_assertions" "replicas" { 27 | component = "replicas" 28 | 29 | equal "replicas_is_correct" { 30 | description = "replicas_is_correct" 31 | got = jsondecode(module.mut.manifests["apps/Deployment/test/test"]).spec.replicas 32 | want = 3 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/_terraform_module/tests/secret_generator/env: -------------------------------------------------------------------------------- 1 | TEST=value 2 | -------------------------------------------------------------------------------- /src/_terraform_module/tests/secret_generator/test.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | test = { 4 | source = "terraform.io/builtin/test" 5 | } 6 | } 7 | } 8 | 9 | locals { 10 | test_key = "test" 11 | test_value = "value" 12 | 13 | test_file = "env" 14 | } 15 | 16 | module "mut" { 17 | source = "../.." 18 | 19 | configuration = { 20 | apps = { 21 | namespace = "test-secgen" 22 | 23 | secret_generator = [ 24 | { 25 | name = "test-literal" 26 | behavior = "create" 27 | literals = [ 28 | "${local.test_key}=${local.test_value}" 29 | ] 30 | options = { 31 | labels = { 32 | source = "from_literal" 33 | } 34 | } 35 | }, 36 | { 37 | name = "test-envs" 38 | envs = [ 39 | "${path.module}/${local.test_file}" 40 | ] 41 | options = { 42 | disable_name_suffix_hash = true 43 | } 44 | }, 45 | { 46 | name = "test-files" 47 | files = [ 48 | "${path.module}/${local.test_file}" 49 | ] 50 | } 51 | ] 52 | } 53 | 54 | (terraform.workspace) = {} 55 | } 56 | 57 | configuration_base_key = "apps" 58 | } 59 | 60 | locals { 61 | manifests = { for k, v in module.mut.manifests : k => jsondecode(v) } 62 | } 63 | 64 | //resource "test_assertions" "debug" { 65 | // component = "secret_generator" 66 | // 67 | // equal "value_correct_debug" { 68 | // description = "key in configmap data has correct value" 69 | // got = local.manifests 70 | // want = {} 71 | // } 72 | //} 73 | 74 | resource "test_assertions" "from_literal" { 75 | component = "secret_generator" 76 | 77 | check "key_exists_from_literal" { 78 | description = "key_exists_from_literal" 79 | condition = contains(keys(local.manifests["_/Secret/test-secgen/test-literal-9kf247ck92"].data), local.test_key) 80 | } 81 | 82 | equal "value_correct_from_literal" { 83 | description = "value_correct_from_literal" 84 | got = local.manifests["_/Secret/test-secgen/test-literal-9kf247ck92"].data[local.test_key] 85 | want = base64encode(local.test_value) 86 | } 87 | 88 | equal "annotation_correct_from_base_merge" { 89 | description = "annotation_correct_from_base_merge" 90 | got = local.manifests["_/Secret/test-secgen/test-literal-9kf247ck92"].metadata.labels.source 91 | want = "from_literal" 92 | } 93 | } 94 | 95 | resource "test_assertions" "from_envs" { 96 | component = "secret_generator" 97 | 98 | check "key_exists_from_envs" { 99 | description = "key_exists_from_envs" 100 | condition = contains(keys(local.manifests["_/Secret/test-secgen/test-envs"].data), upper(local.test_key)) 101 | } 102 | 103 | equal "value_correct_from_envs" { 104 | description = "value_correct_from_envs" 105 | got = local.manifests["_/Secret/test-secgen/test-envs"].data[upper(local.test_key)] 106 | want = base64encode(local.test_value) 107 | } 108 | } 109 | 110 | resource "test_assertions" "from_files" { 111 | component = "secret_generator" 112 | 113 | check "key_exists_from_files" { 114 | description = "key_exists_from_files" 115 | condition = contains(keys(local.manifests["_/Secret/test-secgen/test-files-49cdkh47f9"].data), local.test_file) 116 | } 117 | 118 | equal "value_correct_from_files" { 119 | description = "value_correct_from_files" 120 | got = local.manifests["_/Secret/test-secgen/test-files-49cdkh47f9"].data[local.test_file] 121 | want = base64encode(file("${path.module}/${local.test_file}")) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/_terraform_module/variables.tf: -------------------------------------------------------------------------------- 1 | variable "configuration" { 2 | type = map(object({ 3 | # Variant is specific to these modules 4 | # it selects which of the bundled Kustomizations 5 | # to include in the resources list. 6 | # Each module has a default_variant 7 | variant = optional(string) 8 | 9 | # if set, will overwrite all of the module's 10 | # upstream resources, only useful in edge cases 11 | # see additional_resources instead 12 | resources = optional(list(string)) 13 | 14 | # concat additional resources to the list of 15 | # included upstream resources 16 | additional_resources = optional(list(string)) 17 | 18 | # Below are all Kustomization built-in 19 | # attributes that modules pass through to the 20 | # kustomizatoin_overlay data source 21 | common_annotations = optional(map(string)) 22 | common_labels = optional(map(string)) 23 | components = optional(list(string)) 24 | config_map_generator = optional(list(object({ 25 | name = optional(string) 26 | namespace = optional(string) 27 | behavior = optional(string) 28 | envs = optional(list(string)) 29 | files = optional(list(string)) 30 | literals = optional(list(string)) 31 | options = optional(object({ 32 | labels = optional(map(string)) 33 | annotations = optional(map(string)) 34 | disable_name_suffix_hash = optional(bool) 35 | })) 36 | }))) 37 | crds = optional(list(string)) 38 | generators = optional(list(string)) 39 | generator_options = optional(object({ 40 | labels = optional(map(string)) 41 | annotations = optional(map(string)) 42 | disable_name_suffix_hash = optional(bool) 43 | })) 44 | images = optional(list(object({ 45 | name = optional(string) 46 | new_name = optional(string) 47 | new_tag = optional(string) 48 | digest = optional(string) 49 | }))) 50 | name_prefix = optional(string) 51 | namespace = optional(string) 52 | name_suffix = optional(string) 53 | patches = optional(list(object({ 54 | path = optional(string) 55 | patch = optional(string) 56 | target = optional(object({ 57 | group = optional(string) 58 | version = optional(string) 59 | kind = optional(string) 60 | name = optional(string) 61 | namespace = optional(string) 62 | label_selector = optional(string) 63 | annotation_selector = optional(string) 64 | })) 65 | }))) 66 | replicas = optional(list(object({ 67 | name = optional(string) 68 | count = optional(number) 69 | }))) 70 | secret_generator = optional(list(object({ 71 | name = optional(string) 72 | namespace = optional(string) 73 | behavior = optional(string) 74 | type = optional(string) 75 | envs = optional(list(string)) 76 | files = optional(list(string)) 77 | literals = optional(list(string)) 78 | options = optional(object({ 79 | labels = optional(map(string)) 80 | annotations = optional(map(string)) 81 | disable_name_suffix_hash = optional(bool) 82 | })) 83 | }))) 84 | transformers = optional(list(string)) 85 | vars = optional(list(object({ 86 | name = optional(string) 87 | obj_ref = optional(object({ 88 | api_version = optional(string) 89 | group = optional(string) 90 | version = optional(string) 91 | kind = optional(string) 92 | name = optional(string) 93 | namespace = optional(string) 94 | })) 95 | field_ref = optional(object({ 96 | field_path = optional(string) 97 | })) 98 | }))) 99 | })) 100 | description = "Map with per workspace module configuration." 101 | default = { apps = {}, ops = {}, loc = {} } 102 | } 103 | 104 | variable "configuration_base_key" { 105 | type = string 106 | description = "The key in the configuration map all other keys inherit from." 107 | default = "apps" 108 | } 109 | 110 | variable "sensitive_group_kinds" { 111 | type = list(string) 112 | description = "List of GroupKinds to mark sensitive. Defaults to [\"_/Secret\"]" 113 | default = ["_/Secret"] 114 | } 115 | -------------------------------------------------------------------------------- /src/_terraform_module/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | kustomization = { 4 | source = "kbst/kustomization" 5 | version = ">= 0.9.0" 6 | } 7 | } 8 | 9 | required_version = ">= 1.3.0" 10 | } 11 | -------------------------------------------------------------------------------- /src/argo-cd/_updater/metadata.yaml: -------------------------------------------------------------------------------- 1 | url: https://github.com/argoproj/argo-cd.git 2 | filter_tags: ^v(.+) 3 | -------------------------------------------------------------------------------- /src/argo-cd/_updater/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | source_path=$1 5 | target_path=$2 6 | version=$3 7 | 8 | # copy install.yaml 9 | cp $source_path/manifests/install.yaml $target_path/normal/install.yaml 10 | 11 | cd $target_path/normal/ 12 | 13 | # update version annotation 14 | kustomize edit add annotation -f app.kubernetes.io/version:$version 15 | 16 | cd - 17 | 18 | # copy ha/install.yaml 19 | cp $source_path/manifests/ha/install.yaml $target_path/ha/install.yaml 20 | 21 | cd $target_path/ha/ 22 | 23 | # update version annotation 24 | kustomize edit add annotation -f app.kubernetes.io/version:$version 25 | -------------------------------------------------------------------------------- /src/argo-cd/configuration.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/configuration.tf -------------------------------------------------------------------------------- /src/argo-cd/data_source.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/data_source.tf -------------------------------------------------------------------------------- /src/argo-cd/default_variant.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default_variant = "ha" 3 | } 4 | -------------------------------------------------------------------------------- /src/argo-cd/ha/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: argocd 4 | commonAnnotations: 5 | app.kubernetes.io/managed-by: kubestack 6 | app.kubernetes.io/version: v2.6.7 7 | catalog.kubestack.com/heritage: kubestack.com/catalog/argo-cd 8 | catalog.kubestack.com/variant: ha 9 | resources: 10 | - namespace.yaml 11 | - install.yaml 12 | -------------------------------------------------------------------------------- /src/argo-cd/ha/namespace.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: argocd 6 | -------------------------------------------------------------------------------- /src/argo-cd/main.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/main._tf -------------------------------------------------------------------------------- /src/argo-cd/normal/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: argocd 4 | commonAnnotations: 5 | app.kubernetes.io/managed-by: kubestack 6 | app.kubernetes.io/version: v2.6.7 7 | catalog.kubestack.com/heritage: kubestack.com/catalog/argo-cd 8 | catalog.kubestack.com/variant: normal 9 | resources: 10 | - namespace.yaml 11 | - install.yaml 12 | -------------------------------------------------------------------------------- /src/argo-cd/normal/namespace.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: argocd 6 | -------------------------------------------------------------------------------- /src/argo-cd/variables.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/variables.tf -------------------------------------------------------------------------------- /src/argo-cd/versions.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/versions.tf -------------------------------------------------------------------------------- /src/cert-manager/_updater/metadata.yaml: -------------------------------------------------------------------------------- 1 | url: https://github.com/jetstack/cert-manager.git 2 | -------------------------------------------------------------------------------- /src/cert-manager/_updater/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | source_path=$1 5 | target_path=$2 6 | version=$3 7 | 8 | curl -Lo $target_path/base/cert-manager.yaml https://github.com/jetstack/cert-manager/releases/download/$version/cert-manager.yaml 9 | -------------------------------------------------------------------------------- /src/cert-manager/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | commonAnnotations: 4 | app.kubernetes.io/version: v1.3.1 5 | catalog.kubestack.com/heritage: kubestack.com/catalog/cert-manager 6 | catalog.kubestack.com/variant: base 7 | commonLabels: 8 | app.kubernetes.io/managed-by: kubestack 9 | resources: 10 | - cert-manager.yaml 11 | -------------------------------------------------------------------------------- /src/cert-manager/configuration.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/configuration.tf -------------------------------------------------------------------------------- /src/cert-manager/data_source.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/data_source.tf -------------------------------------------------------------------------------- /src/cert-manager/default_variant.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default_variant = "base" 3 | } 4 | -------------------------------------------------------------------------------- /src/cert-manager/main.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/main._tf -------------------------------------------------------------------------------- /src/cert-manager/variables.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/variables.tf -------------------------------------------------------------------------------- /src/cert-manager/versions.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/versions.tf -------------------------------------------------------------------------------- /src/custom-manifests/configuration.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/configuration.tf -------------------------------------------------------------------------------- /src/custom-manifests/data_source.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/data_source.tf -------------------------------------------------------------------------------- /src/custom-manifests/default_variant.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default_variant = "empty" 3 | } 4 | -------------------------------------------------------------------------------- /src/custom-manifests/empty/Kustomization: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | -------------------------------------------------------------------------------- /src/custom-manifests/main.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/main._tf -------------------------------------------------------------------------------- /src/custom-manifests/variables.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/variables.tf -------------------------------------------------------------------------------- /src/custom-manifests/versions.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/versions.tf -------------------------------------------------------------------------------- /src/flux/_updater/metadata.yaml: -------------------------------------------------------------------------------- 1 | url: https://github.com/fluxcd/flux.git 2 | filter_tags: ^v(.+) 3 | -------------------------------------------------------------------------------- /src/flux/_updater/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | source_path=$1 5 | target_path=$2 6 | version=$3 7 | 8 | rm -rf $target_path/base 9 | mkdir -p $target_path/base 10 | cp $source_path/deploy/* $target_path/base/ 11 | 12 | cd $target_path/base/ 13 | 14 | # set commonAnnotations 15 | kustomize edit add annotation catalog.kubestack.com/heritage:kubestack.com/catalog/flux,catalog.kubestack.com/variant:base,app.kubernetes.io/version:$version 16 | 17 | # set commonLabels 18 | kustomize edit add label app.kubernetes.io/component:controller,app.kubernetes.io/managed-by:kubestack,app.kubernetes.io/name:flux 19 | -------------------------------------------------------------------------------- /src/flux/base/flux-account.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # The service account, cluster roles, and cluster role binding are 3 | # only needed for Kubernetes with role-based access control (RBAC). 4 | apiVersion: v1 5 | kind: ServiceAccount 6 | metadata: 7 | labels: 8 | name: flux 9 | name: flux 10 | namespace: flux 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | labels: 16 | name: flux 17 | name: flux 18 | rules: 19 | - apiGroups: ['*'] 20 | resources: ['*'] 21 | verbs: ['*'] 22 | - nonResourceURLs: ['*'] 23 | verbs: ['*'] 24 | --- 25 | apiVersion: rbac.authorization.k8s.io/v1 26 | kind: ClusterRoleBinding 27 | metadata: 28 | labels: 29 | name: flux 30 | name: flux 31 | roleRef: 32 | apiGroup: rbac.authorization.k8s.io 33 | kind: ClusterRole 34 | name: flux 35 | subjects: 36 | - kind: ServiceAccount 37 | name: flux 38 | namespace: flux 39 | -------------------------------------------------------------------------------- /src/flux/base/flux-deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: flux 6 | namespace: flux 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | name: flux 12 | strategy: 13 | type: Recreate 14 | template: 15 | metadata: 16 | annotations: 17 | prometheus.io/port: "3031" # tell prometheus to scrape /metrics endpoint's port. 18 | labels: 19 | name: flux 20 | spec: 21 | nodeSelector: 22 | beta.kubernetes.io/os: linux 23 | serviceAccountName: flux 24 | volumes: 25 | - name: git-key 26 | secret: 27 | secretName: flux-git-deploy 28 | defaultMode: 0400 # when mounted read-only, we won't be able to chmod 29 | 30 | # This is a tmpfs used for generating SSH keys. In K8s >= 1.10, 31 | # mounted secrets are read-only, so we need a separate volume we 32 | # can write to. 33 | - name: git-keygen 34 | emptyDir: 35 | medium: Memory 36 | 37 | # The following volume is for using a customised known_hosts 38 | # file, which you will need to do if you host your own git 39 | # repo rather than using github or the like. You'll also need to 40 | # mount it into the container, below. See 41 | # https://fluxcd.io/legacy/flux/guides/use-private-git-host/ 42 | # - name: ssh-config 43 | # configMap: 44 | # name: flux-ssh-config 45 | 46 | # The following volume is for using a customised .kube/config, 47 | # which you will need to do if you wish to have a different 48 | # default namespace. You will also need to provide the configmap 49 | # with an entry for `config`, and uncomment the volumeMount and 50 | # env entries below. 51 | # - name: kubeconfig 52 | # configMap: 53 | # name: flux-kubeconfig 54 | 55 | # The following volume is used to import GPG keys (for signing 56 | # and verification purposes). You will also need to provide the 57 | # secret with the keys, and uncomment the volumeMount and args 58 | # below. 59 | # - name: gpg-keys 60 | # secret: 61 | # secretName: flux-gpg-keys 62 | # defaultMode: 0400 63 | 64 | containers: 65 | - name: flux 66 | # There are no ":latest" images for flux. Find the most recent 67 | # release or image version at https://hub.docker.com/r/fluxcd/flux/tags 68 | # and replace the tag here. 69 | image: docker.io/fluxcd/flux:1.25.4 70 | imagePullPolicy: IfNotPresent 71 | resources: 72 | requests: 73 | cpu: 50m 74 | memory: 64Mi 75 | ports: 76 | - containerPort: 3030 # informational 77 | livenessProbe: 78 | httpGet: 79 | port: 3030 80 | path: /api/flux/v6/identity.pub 81 | initialDelaySeconds: 5 82 | timeoutSeconds: 5 83 | readinessProbe: 84 | httpGet: 85 | port: 3030 86 | path: /api/flux/v6/identity.pub 87 | initialDelaySeconds: 5 88 | timeoutSeconds: 5 89 | volumeMounts: 90 | - name: git-key 91 | mountPath: /etc/fluxd/ssh # to match location given in image's /etc/ssh/config 92 | readOnly: true # this will be the case perforce in K8s >=1.10 93 | - name: git-keygen 94 | mountPath: /var/fluxd/keygen # to match location given in image's /etc/ssh/config 95 | 96 | # Include this if you need to mount a customised known_hosts 97 | # file; you'll also need the volume declared above. 98 | # - name: ssh-config 99 | # mountPath: /root/.ssh 100 | 101 | # Include this and the volume "kubeconfig" above, and the 102 | # environment entry "KUBECONFIG" below, to override the config 103 | # used by kubectl. 104 | # - name: kubeconfig 105 | # mountPath: /etc/fluxd/kube 106 | 107 | # Include this to point kubectl at a different config; you 108 | # will need to do this if you have mounted an alternate config 109 | # from a configmap, as in commented blocks above. 110 | # env: 111 | # - name: KUBECONFIG 112 | # value: /etc/fluxd/kube/config 113 | 114 | # Include this and the volume "gpg-keys" above, and the 115 | # args below. 116 | # - name: gpg-keys 117 | # mountPath: /root/gpg-import 118 | # readOnly: true 119 | 120 | # Include this if you want to supply HTTP basic auth credentials for git 121 | # via the `GIT_AUTHUSER` and `GIT_AUTHKEY` environment variables using a 122 | # secret. 123 | # envFrom: 124 | # - secretRef: 125 | # name: flux-git-auth 126 | 127 | args: 128 | 129 | # If you deployed memcached in a different namespace to flux, 130 | # or with a different service name, you can supply these 131 | # following two arguments to tell fluxd how to connect to it. 132 | # - --memcached-hostname=memcached.default.svc.cluster.local 133 | 134 | # Use the memcached ClusterIP service name by setting the 135 | # memcached-service to string empty 136 | - --memcached-service= 137 | 138 | # This must be supplied, and be in the tmpfs (emptyDir) 139 | # mounted above, for K8s >= 1.10 140 | - --ssh-keygen-dir=/var/fluxd/keygen 141 | 142 | # Replace the following URL to change the Git repository used by Flux. 143 | # HTTP basic auth credentials can be supplied using environment variables: 144 | # https://$(GIT_AUTHUSER):$(GIT_AUTHKEY)@github.com/user/repository.git 145 | - --git-url=git@github.com:fluxcd/flux-get-started 146 | - --git-branch=master 147 | # Include this if you want to restrict the manifests considered by flux 148 | # to those under the following relative paths in the git repository 149 | # - --git-path=subdir1,subdir2 150 | - --git-label=flux-sync 151 | - --git-user=Flux automation 152 | - --git-email=flux@example.com 153 | 154 | # Include these two to enable git commit signing 155 | # - --git-gpg-key-import=/root/gpg-import 156 | # - --git-signing-key= 157 | 158 | # Include this to enable git signature verification 159 | # - --git-verify-signatures 160 | 161 | # Tell flux it has readonly access to the repo (default `false`) 162 | # - --git-readonly 163 | 164 | # Instruct flux where to put sync bookkeeping (default "git", meaning use a tag in the upstream git repo) 165 | # - --sync-state=git 166 | 167 | # Include these next two to connect to an "upstream" service 168 | # (e.g., Weave Cloud). The token is particular to the service. 169 | # - --connect=wss://cloud.weave.works/api/flux 170 | # - --token=abc123abc123abc123abc123 171 | 172 | # Enable manifest generation (default `false`) 173 | # - --manifest-generation=false 174 | 175 | # Serve /metrics endpoint at different port; 176 | # make sure to set prometheus' annotation to scrape the port value. 177 | - --listen-metrics=:3031 178 | 179 | # Optional DNS settings, configuring the ndots option may resolve 180 | # nslookup issues on some Kubernetes setups. 181 | # dnsPolicy: "None" 182 | # dnsConfig: 183 | # options: 184 | # - name: ndots 185 | # value: "1" 186 | -------------------------------------------------------------------------------- /src/flux/base/flux-ns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: flux 6 | -------------------------------------------------------------------------------- /src/flux/base/flux-secret.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: flux-git-deploy 6 | namespace: flux 7 | type: Opaque 8 | -------------------------------------------------------------------------------- /src/flux/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - flux-ns.yaml 3 | - memcache-svc.yaml 4 | - memcache-dep.yaml 5 | - flux-account.yaml 6 | - flux-secret.yaml 7 | - flux-deployment.yaml 8 | apiVersion: kustomize.config.k8s.io/v1beta1 9 | kind: Kustomization 10 | commonAnnotations: 11 | app.kubernetes.io/version: v1.25.4 12 | catalog.kubestack.com/heritage: kubestack.com/catalog/flux 13 | catalog.kubestack.com/variant: base 14 | commonLabels: 15 | app.kubernetes.io/component: controller 16 | app.kubernetes.io/managed-by: kubestack 17 | app.kubernetes.io/name: flux 18 | -------------------------------------------------------------------------------- /src/flux/base/memcache-dep.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # memcached deployment used by Flux to cache 3 | # container image metadata. 4 | apiVersion: apps/v1 5 | kind: Deployment 6 | metadata: 7 | name: memcached 8 | namespace: flux 9 | spec: 10 | replicas: 1 11 | selector: 12 | matchLabels: 13 | name: memcached 14 | template: 15 | metadata: 16 | labels: 17 | name: memcached 18 | spec: 19 | nodeSelector: 20 | beta.kubernetes.io/os: linux 21 | containers: 22 | - name: memcached 23 | image: memcached:1.6.10-alpine 24 | imagePullPolicy: IfNotPresent 25 | args: 26 | - -m 512 # Maximum memory to use, in megabytes 27 | - -I 5m # Maximum size for one item 28 | - -p 11211 # Default port 29 | # - -vv # Uncomment to get logs of each request and response. 30 | ports: 31 | - name: clients 32 | containerPort: 11211 33 | securityContext: 34 | runAsUser: 11211 35 | runAsGroup: 11211 36 | allowPrivilegeEscalation: false 37 | -------------------------------------------------------------------------------- /src/flux/base/memcache-svc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: memcached 6 | namespace: flux 7 | spec: 8 | ports: 9 | - name: memcached 10 | port: 11211 11 | selector: 12 | name: memcached 13 | -------------------------------------------------------------------------------- /src/flux/configuration.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/configuration.tf -------------------------------------------------------------------------------- /src/flux/data_source.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/data_source.tf -------------------------------------------------------------------------------- /src/flux/default_variant.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default_variant = "base" 3 | } 4 | -------------------------------------------------------------------------------- /src/flux/main.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/main._tf -------------------------------------------------------------------------------- /src/flux/variables.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/variables.tf -------------------------------------------------------------------------------- /src/flux/versions.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/versions.tf -------------------------------------------------------------------------------- /src/nginx/_updater/metadata.yaml: -------------------------------------------------------------------------------- 1 | url: https://github.com/kubernetes/ingress-nginx.git 2 | ref: main 3 | filter_tags: ^controller-(.*) 4 | -------------------------------------------------------------------------------- /src/nginx/_updater/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | source_path=$1 5 | target_path=$2 6 | 7 | cp $source_path/deploy/static/provider/cloud/deploy.yaml $target_path/base/mandatory.yaml 8 | -------------------------------------------------------------------------------- /src/nginx/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: ingress-nginx 4 | commonAnnotations: 5 | app.kubernetes.io/version: v0.46.0 6 | catalog.kubestack.com/heritage: kubestack.com/catalog/nginx 7 | catalog.kubestack.com/variant: base 8 | commonLabels: 9 | app.kubernetes.io/component: ingress-controller 10 | app.kubernetes.io/managed-by: kubestack 11 | app.kubernetes.io/name: nginx 12 | resources: 13 | - mandatory.yaml 14 | replicas: 15 | - name: ingress-nginx-controller 16 | count: 2 17 | -------------------------------------------------------------------------------- /src/nginx/base/mandatory.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | app.kubernetes.io/instance: ingress-nginx 6 | app.kubernetes.io/name: ingress-nginx 7 | name: ingress-nginx 8 | --- 9 | apiVersion: v1 10 | automountServiceAccountToken: true 11 | kind: ServiceAccount 12 | metadata: 13 | labels: 14 | app.kubernetes.io/component: controller 15 | app.kubernetes.io/instance: ingress-nginx 16 | app.kubernetes.io/name: ingress-nginx 17 | app.kubernetes.io/part-of: ingress-nginx 18 | app.kubernetes.io/version: 1.7.0 19 | name: ingress-nginx 20 | namespace: ingress-nginx 21 | --- 22 | apiVersion: v1 23 | kind: ServiceAccount 24 | metadata: 25 | labels: 26 | app.kubernetes.io/component: admission-webhook 27 | app.kubernetes.io/instance: ingress-nginx 28 | app.kubernetes.io/name: ingress-nginx 29 | app.kubernetes.io/part-of: ingress-nginx 30 | app.kubernetes.io/version: 1.7.0 31 | name: ingress-nginx-admission 32 | namespace: ingress-nginx 33 | --- 34 | apiVersion: rbac.authorization.k8s.io/v1 35 | kind: Role 36 | metadata: 37 | labels: 38 | app.kubernetes.io/component: controller 39 | app.kubernetes.io/instance: ingress-nginx 40 | app.kubernetes.io/name: ingress-nginx 41 | app.kubernetes.io/part-of: ingress-nginx 42 | app.kubernetes.io/version: 1.7.0 43 | name: ingress-nginx 44 | namespace: ingress-nginx 45 | rules: 46 | - apiGroups: 47 | - "" 48 | resources: 49 | - namespaces 50 | verbs: 51 | - get 52 | - apiGroups: 53 | - "" 54 | resources: 55 | - configmaps 56 | - pods 57 | - secrets 58 | - endpoints 59 | verbs: 60 | - get 61 | - list 62 | - watch 63 | - apiGroups: 64 | - "" 65 | resources: 66 | - services 67 | verbs: 68 | - get 69 | - list 70 | - watch 71 | - apiGroups: 72 | - networking.k8s.io 73 | resources: 74 | - ingresses 75 | verbs: 76 | - get 77 | - list 78 | - watch 79 | - apiGroups: 80 | - networking.k8s.io 81 | resources: 82 | - ingresses/status 83 | verbs: 84 | - update 85 | - apiGroups: 86 | - networking.k8s.io 87 | resources: 88 | - ingressclasses 89 | verbs: 90 | - get 91 | - list 92 | - watch 93 | - apiGroups: 94 | - coordination.k8s.io 95 | resourceNames: 96 | - ingress-nginx-leader 97 | resources: 98 | - leases 99 | verbs: 100 | - get 101 | - update 102 | - apiGroups: 103 | - coordination.k8s.io 104 | resources: 105 | - leases 106 | verbs: 107 | - create 108 | - apiGroups: 109 | - "" 110 | resources: 111 | - events 112 | verbs: 113 | - create 114 | - patch 115 | - apiGroups: 116 | - discovery.k8s.io 117 | resources: 118 | - endpointslices 119 | verbs: 120 | - list 121 | - watch 122 | - get 123 | --- 124 | apiVersion: rbac.authorization.k8s.io/v1 125 | kind: Role 126 | metadata: 127 | labels: 128 | app.kubernetes.io/component: admission-webhook 129 | app.kubernetes.io/instance: ingress-nginx 130 | app.kubernetes.io/name: ingress-nginx 131 | app.kubernetes.io/part-of: ingress-nginx 132 | app.kubernetes.io/version: 1.7.0 133 | name: ingress-nginx-admission 134 | namespace: ingress-nginx 135 | rules: 136 | - apiGroups: 137 | - "" 138 | resources: 139 | - secrets 140 | verbs: 141 | - get 142 | - create 143 | --- 144 | apiVersion: rbac.authorization.k8s.io/v1 145 | kind: ClusterRole 146 | metadata: 147 | labels: 148 | app.kubernetes.io/instance: ingress-nginx 149 | app.kubernetes.io/name: ingress-nginx 150 | app.kubernetes.io/part-of: ingress-nginx 151 | app.kubernetes.io/version: 1.7.0 152 | name: ingress-nginx 153 | rules: 154 | - apiGroups: 155 | - "" 156 | resources: 157 | - configmaps 158 | - endpoints 159 | - nodes 160 | - pods 161 | - secrets 162 | - namespaces 163 | verbs: 164 | - list 165 | - watch 166 | - apiGroups: 167 | - coordination.k8s.io 168 | resources: 169 | - leases 170 | verbs: 171 | - list 172 | - watch 173 | - apiGroups: 174 | - "" 175 | resources: 176 | - nodes 177 | verbs: 178 | - get 179 | - apiGroups: 180 | - "" 181 | resources: 182 | - services 183 | verbs: 184 | - get 185 | - list 186 | - watch 187 | - apiGroups: 188 | - networking.k8s.io 189 | resources: 190 | - ingresses 191 | verbs: 192 | - get 193 | - list 194 | - watch 195 | - apiGroups: 196 | - "" 197 | resources: 198 | - events 199 | verbs: 200 | - create 201 | - patch 202 | - apiGroups: 203 | - networking.k8s.io 204 | resources: 205 | - ingresses/status 206 | verbs: 207 | - update 208 | - apiGroups: 209 | - networking.k8s.io 210 | resources: 211 | - ingressclasses 212 | verbs: 213 | - get 214 | - list 215 | - watch 216 | - apiGroups: 217 | - discovery.k8s.io 218 | resources: 219 | - endpointslices 220 | verbs: 221 | - list 222 | - watch 223 | - get 224 | --- 225 | apiVersion: rbac.authorization.k8s.io/v1 226 | kind: ClusterRole 227 | metadata: 228 | labels: 229 | app.kubernetes.io/component: admission-webhook 230 | app.kubernetes.io/instance: ingress-nginx 231 | app.kubernetes.io/name: ingress-nginx 232 | app.kubernetes.io/part-of: ingress-nginx 233 | app.kubernetes.io/version: 1.7.0 234 | name: ingress-nginx-admission 235 | rules: 236 | - apiGroups: 237 | - admissionregistration.k8s.io 238 | resources: 239 | - validatingwebhookconfigurations 240 | verbs: 241 | - get 242 | - update 243 | --- 244 | apiVersion: rbac.authorization.k8s.io/v1 245 | kind: RoleBinding 246 | metadata: 247 | labels: 248 | app.kubernetes.io/component: controller 249 | app.kubernetes.io/instance: ingress-nginx 250 | app.kubernetes.io/name: ingress-nginx 251 | app.kubernetes.io/part-of: ingress-nginx 252 | app.kubernetes.io/version: 1.7.0 253 | name: ingress-nginx 254 | namespace: ingress-nginx 255 | roleRef: 256 | apiGroup: rbac.authorization.k8s.io 257 | kind: Role 258 | name: ingress-nginx 259 | subjects: 260 | - kind: ServiceAccount 261 | name: ingress-nginx 262 | namespace: ingress-nginx 263 | --- 264 | apiVersion: rbac.authorization.k8s.io/v1 265 | kind: RoleBinding 266 | metadata: 267 | labels: 268 | app.kubernetes.io/component: admission-webhook 269 | app.kubernetes.io/instance: ingress-nginx 270 | app.kubernetes.io/name: ingress-nginx 271 | app.kubernetes.io/part-of: ingress-nginx 272 | app.kubernetes.io/version: 1.7.0 273 | name: ingress-nginx-admission 274 | namespace: ingress-nginx 275 | roleRef: 276 | apiGroup: rbac.authorization.k8s.io 277 | kind: Role 278 | name: ingress-nginx-admission 279 | subjects: 280 | - kind: ServiceAccount 281 | name: ingress-nginx-admission 282 | namespace: ingress-nginx 283 | --- 284 | apiVersion: rbac.authorization.k8s.io/v1 285 | kind: ClusterRoleBinding 286 | metadata: 287 | labels: 288 | app.kubernetes.io/instance: ingress-nginx 289 | app.kubernetes.io/name: ingress-nginx 290 | app.kubernetes.io/part-of: ingress-nginx 291 | app.kubernetes.io/version: 1.7.0 292 | name: ingress-nginx 293 | roleRef: 294 | apiGroup: rbac.authorization.k8s.io 295 | kind: ClusterRole 296 | name: ingress-nginx 297 | subjects: 298 | - kind: ServiceAccount 299 | name: ingress-nginx 300 | namespace: ingress-nginx 301 | --- 302 | apiVersion: rbac.authorization.k8s.io/v1 303 | kind: ClusterRoleBinding 304 | metadata: 305 | labels: 306 | app.kubernetes.io/component: admission-webhook 307 | app.kubernetes.io/instance: ingress-nginx 308 | app.kubernetes.io/name: ingress-nginx 309 | app.kubernetes.io/part-of: ingress-nginx 310 | app.kubernetes.io/version: 1.7.0 311 | name: ingress-nginx-admission 312 | roleRef: 313 | apiGroup: rbac.authorization.k8s.io 314 | kind: ClusterRole 315 | name: ingress-nginx-admission 316 | subjects: 317 | - kind: ServiceAccount 318 | name: ingress-nginx-admission 319 | namespace: ingress-nginx 320 | --- 321 | apiVersion: v1 322 | data: 323 | allow-snippet-annotations: "true" 324 | kind: ConfigMap 325 | metadata: 326 | labels: 327 | app.kubernetes.io/component: controller 328 | app.kubernetes.io/instance: ingress-nginx 329 | app.kubernetes.io/name: ingress-nginx 330 | app.kubernetes.io/part-of: ingress-nginx 331 | app.kubernetes.io/version: 1.7.0 332 | name: ingress-nginx-controller 333 | namespace: ingress-nginx 334 | --- 335 | apiVersion: v1 336 | kind: Service 337 | metadata: 338 | labels: 339 | app.kubernetes.io/component: controller 340 | app.kubernetes.io/instance: ingress-nginx 341 | app.kubernetes.io/name: ingress-nginx 342 | app.kubernetes.io/part-of: ingress-nginx 343 | app.kubernetes.io/version: 1.7.0 344 | name: ingress-nginx-controller 345 | namespace: ingress-nginx 346 | spec: 347 | externalTrafficPolicy: Local 348 | ipFamilies: 349 | - IPv4 350 | ipFamilyPolicy: SingleStack 351 | ports: 352 | - appProtocol: http 353 | name: http 354 | port: 80 355 | protocol: TCP 356 | targetPort: http 357 | - appProtocol: https 358 | name: https 359 | port: 443 360 | protocol: TCP 361 | targetPort: https 362 | selector: 363 | app.kubernetes.io/component: controller 364 | app.kubernetes.io/instance: ingress-nginx 365 | app.kubernetes.io/name: ingress-nginx 366 | type: LoadBalancer 367 | --- 368 | apiVersion: v1 369 | kind: Service 370 | metadata: 371 | labels: 372 | app.kubernetes.io/component: controller 373 | app.kubernetes.io/instance: ingress-nginx 374 | app.kubernetes.io/name: ingress-nginx 375 | app.kubernetes.io/part-of: ingress-nginx 376 | app.kubernetes.io/version: 1.7.0 377 | name: ingress-nginx-controller-admission 378 | namespace: ingress-nginx 379 | spec: 380 | ports: 381 | - appProtocol: https 382 | name: https-webhook 383 | port: 443 384 | targetPort: webhook 385 | selector: 386 | app.kubernetes.io/component: controller 387 | app.kubernetes.io/instance: ingress-nginx 388 | app.kubernetes.io/name: ingress-nginx 389 | type: ClusterIP 390 | --- 391 | apiVersion: apps/v1 392 | kind: Deployment 393 | metadata: 394 | labels: 395 | app.kubernetes.io/component: controller 396 | app.kubernetes.io/instance: ingress-nginx 397 | app.kubernetes.io/name: ingress-nginx 398 | app.kubernetes.io/part-of: ingress-nginx 399 | app.kubernetes.io/version: 1.7.0 400 | name: ingress-nginx-controller 401 | namespace: ingress-nginx 402 | spec: 403 | minReadySeconds: 0 404 | revisionHistoryLimit: 10 405 | selector: 406 | matchLabels: 407 | app.kubernetes.io/component: controller 408 | app.kubernetes.io/instance: ingress-nginx 409 | app.kubernetes.io/name: ingress-nginx 410 | template: 411 | metadata: 412 | labels: 413 | app.kubernetes.io/component: controller 414 | app.kubernetes.io/instance: ingress-nginx 415 | app.kubernetes.io/name: ingress-nginx 416 | app.kubernetes.io/part-of: ingress-nginx 417 | app.kubernetes.io/version: 1.7.0 418 | spec: 419 | containers: 420 | - args: 421 | - /nginx-ingress-controller 422 | - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller 423 | - --election-id=ingress-nginx-leader 424 | - --controller-class=k8s.io/ingress-nginx 425 | - --ingress-class=nginx 426 | - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller 427 | - --validating-webhook=:8443 428 | - --validating-webhook-certificate=/usr/local/certificates/cert 429 | - --validating-webhook-key=/usr/local/certificates/key 430 | env: 431 | - name: POD_NAME 432 | valueFrom: 433 | fieldRef: 434 | fieldPath: metadata.name 435 | - name: POD_NAMESPACE 436 | valueFrom: 437 | fieldRef: 438 | fieldPath: metadata.namespace 439 | - name: LD_PRELOAD 440 | value: /usr/local/lib/libmimalloc.so 441 | image: registry.k8s.io/ingress-nginx/controller:v1.7.0@sha256:7612338342a1e7b8090bef78f2a04fffcadd548ccaabe8a47bf7758ff549a5f7 442 | imagePullPolicy: IfNotPresent 443 | lifecycle: 444 | preStop: 445 | exec: 446 | command: 447 | - /wait-shutdown 448 | livenessProbe: 449 | failureThreshold: 5 450 | httpGet: 451 | path: /healthz 452 | port: 10254 453 | scheme: HTTP 454 | initialDelaySeconds: 10 455 | periodSeconds: 10 456 | successThreshold: 1 457 | timeoutSeconds: 1 458 | name: controller 459 | ports: 460 | - containerPort: 80 461 | name: http 462 | protocol: TCP 463 | - containerPort: 443 464 | name: https 465 | protocol: TCP 466 | - containerPort: 8443 467 | name: webhook 468 | protocol: TCP 469 | readinessProbe: 470 | failureThreshold: 3 471 | httpGet: 472 | path: /healthz 473 | port: 10254 474 | scheme: HTTP 475 | initialDelaySeconds: 10 476 | periodSeconds: 10 477 | successThreshold: 1 478 | timeoutSeconds: 1 479 | resources: 480 | requests: 481 | cpu: 100m 482 | memory: 90Mi 483 | securityContext: 484 | allowPrivilegeEscalation: true 485 | capabilities: 486 | add: 487 | - NET_BIND_SERVICE 488 | drop: 489 | - ALL 490 | runAsUser: 101 491 | volumeMounts: 492 | - mountPath: /usr/local/certificates/ 493 | name: webhook-cert 494 | readOnly: true 495 | dnsPolicy: ClusterFirst 496 | nodeSelector: 497 | kubernetes.io/os: linux 498 | serviceAccountName: ingress-nginx 499 | terminationGracePeriodSeconds: 300 500 | volumes: 501 | - name: webhook-cert 502 | secret: 503 | secretName: ingress-nginx-admission 504 | --- 505 | apiVersion: batch/v1 506 | kind: Job 507 | metadata: 508 | labels: 509 | app.kubernetes.io/component: admission-webhook 510 | app.kubernetes.io/instance: ingress-nginx 511 | app.kubernetes.io/name: ingress-nginx 512 | app.kubernetes.io/part-of: ingress-nginx 513 | app.kubernetes.io/version: 1.7.0 514 | name: ingress-nginx-admission-create 515 | namespace: ingress-nginx 516 | spec: 517 | template: 518 | metadata: 519 | labels: 520 | app.kubernetes.io/component: admission-webhook 521 | app.kubernetes.io/instance: ingress-nginx 522 | app.kubernetes.io/name: ingress-nginx 523 | app.kubernetes.io/part-of: ingress-nginx 524 | app.kubernetes.io/version: 1.7.0 525 | name: ingress-nginx-admission-create 526 | spec: 527 | containers: 528 | - args: 529 | - create 530 | - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc 531 | - --namespace=$(POD_NAMESPACE) 532 | - --secret-name=ingress-nginx-admission 533 | env: 534 | - name: POD_NAMESPACE 535 | valueFrom: 536 | fieldRef: 537 | fieldPath: metadata.namespace 538 | image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230312-helm-chart-4.5.2-28-g66a760794@sha256:01d181618f270f2a96c04006f33b2699ad3ccb02da48d0f89b22abce084b292f 539 | imagePullPolicy: IfNotPresent 540 | name: create 541 | securityContext: 542 | allowPrivilegeEscalation: false 543 | nodeSelector: 544 | kubernetes.io/os: linux 545 | restartPolicy: OnFailure 546 | securityContext: 547 | fsGroup: 2000 548 | runAsNonRoot: true 549 | runAsUser: 2000 550 | serviceAccountName: ingress-nginx-admission 551 | --- 552 | apiVersion: batch/v1 553 | kind: Job 554 | metadata: 555 | labels: 556 | app.kubernetes.io/component: admission-webhook 557 | app.kubernetes.io/instance: ingress-nginx 558 | app.kubernetes.io/name: ingress-nginx 559 | app.kubernetes.io/part-of: ingress-nginx 560 | app.kubernetes.io/version: 1.7.0 561 | name: ingress-nginx-admission-patch 562 | namespace: ingress-nginx 563 | spec: 564 | template: 565 | metadata: 566 | labels: 567 | app.kubernetes.io/component: admission-webhook 568 | app.kubernetes.io/instance: ingress-nginx 569 | app.kubernetes.io/name: ingress-nginx 570 | app.kubernetes.io/part-of: ingress-nginx 571 | app.kubernetes.io/version: 1.7.0 572 | name: ingress-nginx-admission-patch 573 | spec: 574 | containers: 575 | - args: 576 | - patch 577 | - --webhook-name=ingress-nginx-admission 578 | - --namespace=$(POD_NAMESPACE) 579 | - --patch-mutating=false 580 | - --secret-name=ingress-nginx-admission 581 | - --patch-failure-policy=Fail 582 | env: 583 | - name: POD_NAMESPACE 584 | valueFrom: 585 | fieldRef: 586 | fieldPath: metadata.namespace 587 | image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230312-helm-chart-4.5.2-28-g66a760794@sha256:01d181618f270f2a96c04006f33b2699ad3ccb02da48d0f89b22abce084b292f 588 | imagePullPolicy: IfNotPresent 589 | name: patch 590 | securityContext: 591 | allowPrivilegeEscalation: false 592 | nodeSelector: 593 | kubernetes.io/os: linux 594 | restartPolicy: OnFailure 595 | securityContext: 596 | fsGroup: 2000 597 | runAsNonRoot: true 598 | runAsUser: 2000 599 | serviceAccountName: ingress-nginx-admission 600 | --- 601 | apiVersion: networking.k8s.io/v1 602 | kind: IngressClass 603 | metadata: 604 | labels: 605 | app.kubernetes.io/component: controller 606 | app.kubernetes.io/instance: ingress-nginx 607 | app.kubernetes.io/name: ingress-nginx 608 | app.kubernetes.io/part-of: ingress-nginx 609 | app.kubernetes.io/version: 1.7.0 610 | name: nginx 611 | spec: 612 | controller: k8s.io/ingress-nginx 613 | --- 614 | apiVersion: admissionregistration.k8s.io/v1 615 | kind: ValidatingWebhookConfiguration 616 | metadata: 617 | labels: 618 | app.kubernetes.io/component: admission-webhook 619 | app.kubernetes.io/instance: ingress-nginx 620 | app.kubernetes.io/name: ingress-nginx 621 | app.kubernetes.io/part-of: ingress-nginx 622 | app.kubernetes.io/version: 1.7.0 623 | name: ingress-nginx-admission 624 | webhooks: 625 | - admissionReviewVersions: 626 | - v1 627 | clientConfig: 628 | service: 629 | name: ingress-nginx-controller-admission 630 | namespace: ingress-nginx 631 | path: /networking/v1/ingresses 632 | failurePolicy: Fail 633 | matchPolicy: Equivalent 634 | name: validate.nginx.ingress.kubernetes.io 635 | rules: 636 | - apiGroups: 637 | - networking.k8s.io 638 | apiVersions: 639 | - v1 640 | operations: 641 | - CREATE 642 | - UPDATE 643 | resources: 644 | - ingresses 645 | sideEffects: None 646 | -------------------------------------------------------------------------------- /src/nginx/configuration.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/configuration.tf -------------------------------------------------------------------------------- /src/nginx/data_source.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/data_source.tf -------------------------------------------------------------------------------- /src/nginx/default-ingress/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | bases: 4 | - ../base/ 5 | namespace: ingress-kbst-default 6 | commonAnnotations: 7 | catalog.kubestack.com/heritage: kubestack.com/catalog/nginx 8 | catalog.kubestack.com/variant: default-ingress 9 | app.kubernetes.io/version: v0.46.0 10 | commonLabels: 11 | app.kubernetes.io/component: ingress-controller 12 | app.kubernetes.io/managed-by: kubestack 13 | app.kubernetes.io/name: nginx 14 | kubestack.com/ingress-default: 'true' 15 | patchesJson6902: 16 | - path: patch-namespace.yaml 17 | target: 18 | kind: Namespace 19 | group: '' 20 | name: ingress-nginx 21 | version: v1 22 | -------------------------------------------------------------------------------- /src/nginx/default-ingress/patch-namespace.yaml: -------------------------------------------------------------------------------- 1 | - op: replace 2 | path: /metadata/name 3 | value: ingress-kbst-default 4 | -------------------------------------------------------------------------------- /src/nginx/default_variant.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default_variant = "base" 3 | } 4 | -------------------------------------------------------------------------------- /src/nginx/main.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/main._tf -------------------------------------------------------------------------------- /src/nginx/variables.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/variables.tf -------------------------------------------------------------------------------- /src/nginx/versions.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/versions.tf -------------------------------------------------------------------------------- /src/opa-gatekeeper/_updater/metadata.yaml: -------------------------------------------------------------------------------- 1 | url: https://github.com/open-policy-agent/gatekeeper.git 2 | filter_tags: ^v([0-9.]+)$ 3 | -------------------------------------------------------------------------------- /src/opa-gatekeeper/_updater/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | source_path=$1 5 | target_path=$2 6 | version=$3 7 | 8 | cp $source_path/deploy/gatekeeper.yaml $target_path/base/gatekeeper.yaml 9 | -------------------------------------------------------------------------------- /src/opa-gatekeeper/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | commonAnnotations: 4 | catalog.kubestack.com/heritage: kubestack.com/catalog/opa-gatekeeper 5 | catalog.kubestack.com/variant: base 6 | app.kubernetes.io/version: v3.4.0 7 | commonLabels: 8 | app.kubernetes.io/component: admission-controller 9 | app.kubernetes.io/managed-by: kubestack 10 | app.kubernetes.io/name: opa-gatekeeper 11 | resources: 12 | - gatekeeper.yaml 13 | -------------------------------------------------------------------------------- /src/opa-gatekeeper/configuration.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/configuration.tf -------------------------------------------------------------------------------- /src/opa-gatekeeper/data_source.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/data_source.tf -------------------------------------------------------------------------------- /src/opa-gatekeeper/default_variant.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default_variant = "base" 3 | } 4 | -------------------------------------------------------------------------------- /src/opa-gatekeeper/main.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/main._tf -------------------------------------------------------------------------------- /src/opa-gatekeeper/variables.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/variables.tf -------------------------------------------------------------------------------- /src/opa-gatekeeper/versions.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/versions.tf -------------------------------------------------------------------------------- /src/pinniped/_updater/metadata.yaml: -------------------------------------------------------------------------------- 1 | url: https://github.com/vmware-tanzu/pinniped.git 2 | ref: main 3 | filter_tags: ^v(.+) 4 | -------------------------------------------------------------------------------- /src/pinniped/_updater/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | source_path=$1 5 | target_path=$2 6 | version=$3 7 | 8 | # concierge 9 | curl -Lo $target_path/concierge-base/crds.yaml https://get.pinniped.dev/$version/install-pinniped-concierge-crds.yaml 10 | curl -Lo $target_path/concierge-base/resources.yaml https://get.pinniped.dev/$version/install-pinniped-concierge-resources.yaml 11 | 12 | cd $target_path/concierge-base/ 13 | 14 | # set commonAnnotations 15 | kustomize edit add annotation -f app.kubernetes.io/version:$version 16 | 17 | cd - 18 | 19 | # supervisor 20 | curl -Lo $target_path/supervisor-base/resources.yaml https://get.pinniped.dev/$version/install-pinniped-supervisor.yaml 21 | 22 | cd $target_path/concierge-base/ 23 | 24 | # set commonAnnotations 25 | kustomize edit add annotation -f app.kubernetes.io/version:$version 26 | -------------------------------------------------------------------------------- /src/pinniped/concierge-base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | commonAnnotations: 4 | app.kubernetes.io/version: v0.23.0 5 | catalog.kubestack.com/heritage: kubestack.com/catalog/pinniped 6 | catalog.kubestack.com/variant: base 7 | resources: 8 | - crds.yaml 9 | - resources.yaml 10 | -------------------------------------------------------------------------------- /src/pinniped/concierge-base/resources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: pinniped-concierge-config 5 | namespace: pinniped-concierge 6 | labels: 7 | app: pinniped-concierge 8 | data: 9 | pinniped.yaml: "discovery:\n url: null\napi:\n servingCertificate:\n durationSeconds: 2592000\n renewBeforeSeconds: 2160000\napiGroupSuffix: pinniped.dev\n# aggregatedAPIServerPort may be set here, although other YAML references to the default port (10250) may also need to be updated\n# impersonationProxyServerPort may be set here, although other YAML references to the default port (8444) may also need to be updated\nnames:\n servingCertificateSecret: pinniped-concierge-api-tls-serving-certificate\n credentialIssuer: pinniped-concierge-config\n apiService: pinniped-concierge-api\n impersonationLoadBalancerService: pinniped-concierge-impersonation-proxy-load-balancer\n impersonationClusterIPService: pinniped-concierge-impersonation-proxy-cluster-ip\n impersonationTLSCertificateSecret: pinniped-concierge-impersonation-proxy-tls-serving-certificate\n impersonationCACertificateSecret: pinniped-concierge-impersonation-proxy-ca-certificate\n impersonationSignerSecret: pinniped-concierge-impersonation-proxy-signer-ca-certificate\n agentServiceAccount: pinniped-concierge-kube-cert-agent\nlabels: {\"app\":\"pinniped-concierge\"}\nkubeCertAgent:\n namePrefix: pinniped-concierge-kube-cert-agent-\n \n \n image: projects.registry.vmware.com/pinniped/pinniped-server:v0.23.0@sha256:3549526b0ecc850469a8cfbaf8701876680b522636bd84d573ed80b54552feb2\n \n \n \n\n" 10 | --- 11 | apiVersion: apps/v1 12 | kind: Deployment 13 | metadata: 14 | name: pinniped-concierge 15 | namespace: pinniped-concierge 16 | labels: 17 | app: pinniped-concierge 18 | spec: 19 | replicas: 2 20 | selector: 21 | matchLabels: 22 | app: pinniped-concierge 23 | template: 24 | metadata: 25 | labels: 26 | app: pinniped-concierge 27 | deployment.pinniped.dev: concierge 28 | annotations: 29 | scheduler.alpha.kubernetes.io/critical-pod: "" 30 | spec: 31 | securityContext: 32 | runAsUser: 65532 33 | runAsGroup: 65532 34 | serviceAccountName: pinniped-concierge 35 | containers: 36 | - name: pinniped-concierge 37 | image: projects.registry.vmware.com/pinniped/pinniped-server:v0.23.0@sha256:3549526b0ecc850469a8cfbaf8701876680b522636bd84d573ed80b54552feb2 38 | imagePullPolicy: IfNotPresent 39 | securityContext: 40 | readOnlyRootFilesystem: true 41 | runAsNonRoot: true 42 | allowPrivilegeEscalation: false 43 | capabilities: 44 | drop: 45 | - ALL 46 | seccompProfile: 47 | type: RuntimeDefault 48 | resources: 49 | requests: 50 | cpu: 100m 51 | memory: 128Mi 52 | limits: 53 | cpu: 100m 54 | memory: 128Mi 55 | command: 56 | - pinniped-concierge 57 | - --config=/etc/config/pinniped.yaml 58 | - --downward-api-path=/etc/podinfo 59 | volumeMounts: 60 | - name: tmp 61 | mountPath: /tmp 62 | - name: config-volume 63 | mountPath: /etc/config 64 | readOnly: true 65 | - name: podinfo 66 | mountPath: /etc/podinfo 67 | readOnly: true 68 | - name: impersonation-proxy 69 | mountPath: /var/run/secrets/impersonation-proxy.concierge.pinniped.dev/serviceaccount 70 | readOnly: true 71 | env: [] 72 | livenessProbe: 73 | httpGet: 74 | path: /healthz 75 | port: 10250 76 | scheme: HTTPS 77 | initialDelaySeconds: 2 78 | timeoutSeconds: 15 79 | periodSeconds: 10 80 | failureThreshold: 5 81 | readinessProbe: 82 | httpGet: 83 | path: /healthz 84 | port: 10250 85 | scheme: HTTPS 86 | initialDelaySeconds: 2 87 | timeoutSeconds: 3 88 | periodSeconds: 10 89 | failureThreshold: 3 90 | volumes: 91 | - name: tmp 92 | emptyDir: 93 | medium: Memory 94 | sizeLimit: 100Mi 95 | - name: config-volume 96 | configMap: 97 | name: pinniped-concierge-config 98 | - name: impersonation-proxy 99 | secret: 100 | secretName: pinniped-concierge-impersonation-proxy 101 | items: 102 | - key: token 103 | path: token 104 | - name: podinfo 105 | downwardAPI: 106 | items: 107 | - path: labels 108 | fieldRef: 109 | fieldPath: metadata.labels 110 | - path: name 111 | fieldRef: 112 | fieldPath: metadata.name 113 | - path: namespace 114 | fieldRef: 115 | fieldPath: metadata.namespace 116 | tolerations: 117 | - key: CriticalAddonsOnly 118 | operator: Exists 119 | - key: node-role.kubernetes.io/master 120 | effect: NoSchedule 121 | - key: node-role.kubernetes.io/control-plane 122 | effect: NoSchedule 123 | affinity: 124 | podAntiAffinity: 125 | preferredDuringSchedulingIgnoredDuringExecution: 126 | - weight: 50 127 | podAffinityTerm: 128 | labelSelector: 129 | matchLabels: 130 | deployment.pinniped.dev: concierge 131 | topologyKey: kubernetes.io/hostname 132 | --- 133 | apiVersion: v1 134 | kind: Service 135 | metadata: 136 | name: pinniped-concierge-api 137 | namespace: pinniped-concierge 138 | labels: 139 | app: pinniped-concierge 140 | annotations: 141 | kapp.k14s.io/disable-default-label-scoping-rules: "" 142 | spec: 143 | type: ClusterIP 144 | selector: 145 | deployment.pinniped.dev: concierge 146 | ports: 147 | - protocol: TCP 148 | port: 443 149 | targetPort: 10250 150 | --- 151 | apiVersion: v1 152 | kind: Service 153 | metadata: 154 | name: pinniped-concierge-proxy 155 | namespace: pinniped-concierge 156 | labels: 157 | app: pinniped-concierge 158 | annotations: 159 | kapp.k14s.io/disable-default-label-scoping-rules: "" 160 | spec: 161 | type: ClusterIP 162 | selector: 163 | deployment.pinniped.dev: concierge 164 | ports: 165 | - protocol: TCP 166 | port: 443 167 | targetPort: 8444 168 | --- 169 | apiVersion: apiregistration.k8s.io/v1 170 | kind: APIService 171 | metadata: 172 | name: v1alpha1.login.concierge.pinniped.dev 173 | labels: 174 | app: pinniped-concierge 175 | spec: 176 | version: v1alpha1 177 | group: login.concierge.pinniped.dev 178 | groupPriorityMinimum: 9900 179 | versionPriority: 15 180 | service: 181 | name: pinniped-concierge-api 182 | namespace: pinniped-concierge 183 | port: 443 184 | --- 185 | apiVersion: apiregistration.k8s.io/v1 186 | kind: APIService 187 | metadata: 188 | name: v1alpha1.identity.concierge.pinniped.dev 189 | labels: 190 | app: pinniped-concierge 191 | spec: 192 | version: v1alpha1 193 | group: identity.concierge.pinniped.dev 194 | groupPriorityMinimum: 9900 195 | versionPriority: 15 196 | service: 197 | name: pinniped-concierge-api 198 | namespace: pinniped-concierge 199 | port: 443 200 | --- 201 | apiVersion: config.concierge.pinniped.dev/v1alpha1 202 | kind: CredentialIssuer 203 | metadata: 204 | name: pinniped-concierge-config 205 | labels: 206 | app: pinniped-concierge 207 | spec: 208 | impersonationProxy: 209 | mode: auto 210 | service: 211 | type: LoadBalancer 212 | annotations: 213 | service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "4000" 214 | --- 215 | apiVersion: v1 216 | kind: Secret 217 | metadata: 218 | name: pinniped-concierge-impersonation-proxy 219 | namespace: pinniped-concierge 220 | labels: 221 | app: pinniped-concierge 222 | annotations: 223 | kapp.k14s.io/change-rule: upsert after upserting impersonation-proxy.concierge.pinniped.dev/serviceaccount 224 | kubernetes.io/service-account.name: pinniped-concierge-impersonation-proxy 225 | type: kubernetes.io/service-account-token 226 | --- 227 | apiVersion: rbac.authorization.k8s.io/v1 228 | kind: ClusterRole 229 | metadata: 230 | name: pinniped-concierge-aggregated-api-server 231 | labels: 232 | app: pinniped-concierge 233 | rules: 234 | - apiGroups: 235 | - "" 236 | resources: 237 | - namespaces 238 | verbs: 239 | - get 240 | - list 241 | - watch 242 | - apiGroups: 243 | - apiregistration.k8s.io 244 | resources: 245 | - apiservices 246 | verbs: 247 | - get 248 | - list 249 | - patch 250 | - update 251 | - watch 252 | - apiGroups: 253 | - admissionregistration.k8s.io 254 | resources: 255 | - validatingwebhookconfigurations 256 | - mutatingwebhookconfigurations 257 | verbs: 258 | - get 259 | - list 260 | - watch 261 | - apiGroups: 262 | - flowcontrol.apiserver.k8s.io 263 | resources: 264 | - flowschemas 265 | - prioritylevelconfigurations 266 | verbs: 267 | - get 268 | - list 269 | - watch 270 | - apiGroups: 271 | - security.openshift.io 272 | resources: 273 | - securitycontextconstraints 274 | verbs: 275 | - use 276 | resourceNames: 277 | - nonroot 278 | - apiGroups: 279 | - "" 280 | resources: 281 | - nodes 282 | verbs: 283 | - list 284 | - apiGroups: 285 | - config.concierge.pinniped.dev 286 | resources: 287 | - credentialissuers 288 | verbs: 289 | - get 290 | - list 291 | - watch 292 | - create 293 | - apiGroups: 294 | - config.concierge.pinniped.dev 295 | resources: 296 | - credentialissuers/status 297 | verbs: 298 | - get 299 | - patch 300 | - update 301 | - apiGroups: 302 | - authentication.concierge.pinniped.dev 303 | resources: 304 | - jwtauthenticators 305 | - webhookauthenticators 306 | verbs: 307 | - get 308 | - list 309 | - watch 310 | --- 311 | kind: ClusterRoleBinding 312 | apiVersion: rbac.authorization.k8s.io/v1 313 | metadata: 314 | name: pinniped-concierge-aggregated-api-server 315 | labels: 316 | app: pinniped-concierge 317 | subjects: 318 | - kind: ServiceAccount 319 | name: pinniped-concierge 320 | namespace: pinniped-concierge 321 | roleRef: 322 | kind: ClusterRole 323 | name: pinniped-concierge-aggregated-api-server 324 | apiGroup: rbac.authorization.k8s.io 325 | --- 326 | apiVersion: rbac.authorization.k8s.io/v1 327 | kind: ClusterRole 328 | metadata: 329 | name: pinniped-concierge-impersonation-proxy 330 | labels: 331 | app: pinniped-concierge 332 | rules: 333 | - apiGroups: 334 | - "" 335 | resources: 336 | - users 337 | - groups 338 | - serviceaccounts 339 | verbs: 340 | - impersonate 341 | - apiGroups: 342 | - authentication.k8s.io 343 | resources: 344 | - '*' 345 | verbs: 346 | - impersonate 347 | --- 348 | kind: ClusterRoleBinding 349 | apiVersion: rbac.authorization.k8s.io/v1 350 | metadata: 351 | name: pinniped-concierge-impersonation-proxy 352 | labels: 353 | app: pinniped-concierge 354 | subjects: 355 | - kind: ServiceAccount 356 | name: pinniped-concierge-impersonation-proxy 357 | namespace: pinniped-concierge 358 | roleRef: 359 | kind: ClusterRole 360 | name: pinniped-concierge-impersonation-proxy 361 | apiGroup: rbac.authorization.k8s.io 362 | --- 363 | apiVersion: rbac.authorization.k8s.io/v1 364 | kind: Role 365 | metadata: 366 | name: pinniped-concierge-kube-cert-agent 367 | namespace: pinniped-concierge 368 | labels: 369 | app: pinniped-concierge 370 | rules: 371 | - apiGroups: 372 | - policy 373 | resources: 374 | - podsecuritypolicies 375 | verbs: 376 | - use 377 | --- 378 | kind: RoleBinding 379 | apiVersion: rbac.authorization.k8s.io/v1 380 | metadata: 381 | name: pinniped-concierge-kube-cert-agent 382 | namespace: pinniped-concierge 383 | labels: 384 | app: pinniped-concierge 385 | subjects: 386 | - kind: ServiceAccount 387 | name: pinniped-concierge-kube-cert-agent 388 | namespace: pinniped-concierge 389 | roleRef: 390 | kind: Role 391 | name: pinniped-concierge-kube-cert-agent 392 | apiGroup: rbac.authorization.k8s.io 393 | --- 394 | apiVersion: rbac.authorization.k8s.io/v1 395 | kind: Role 396 | metadata: 397 | name: pinniped-concierge-aggregated-api-server 398 | namespace: pinniped-concierge 399 | labels: 400 | app: pinniped-concierge 401 | rules: 402 | - apiGroups: 403 | - "" 404 | resources: 405 | - services 406 | verbs: 407 | - create 408 | - get 409 | - list 410 | - patch 411 | - update 412 | - watch 413 | - delete 414 | - apiGroups: 415 | - "" 416 | resources: 417 | - secrets 418 | verbs: 419 | - create 420 | - get 421 | - list 422 | - patch 423 | - update 424 | - watch 425 | - delete 426 | - apiGroups: 427 | - "" 428 | resources: 429 | - pods 430 | verbs: 431 | - get 432 | - list 433 | - watch 434 | - apiGroups: 435 | - "" 436 | resources: 437 | - pods/exec 438 | verbs: 439 | - create 440 | - apiGroups: 441 | - "" 442 | resources: 443 | - pods 444 | verbs: 445 | - delete 446 | - apiGroups: 447 | - apps 448 | resources: 449 | - deployments 450 | verbs: 451 | - create 452 | - get 453 | - list 454 | - patch 455 | - update 456 | - watch 457 | - delete 458 | - apiGroups: 459 | - apps 460 | resources: 461 | - replicasets 462 | verbs: 463 | - get 464 | - apiGroups: 465 | - "" 466 | resources: 467 | - configmaps 468 | verbs: 469 | - list 470 | - get 471 | - watch 472 | - apiGroups: 473 | - coordination.k8s.io 474 | resources: 475 | - leases 476 | verbs: 477 | - create 478 | - get 479 | - update 480 | --- 481 | kind: RoleBinding 482 | apiVersion: rbac.authorization.k8s.io/v1 483 | metadata: 484 | name: pinniped-concierge-aggregated-api-server 485 | namespace: pinniped-concierge 486 | labels: 487 | app: pinniped-concierge 488 | subjects: 489 | - kind: ServiceAccount 490 | name: pinniped-concierge 491 | namespace: pinniped-concierge 492 | roleRef: 493 | kind: Role 494 | name: pinniped-concierge-aggregated-api-server 495 | apiGroup: rbac.authorization.k8s.io 496 | --- 497 | apiVersion: rbac.authorization.k8s.io/v1 498 | kind: Role 499 | metadata: 500 | name: pinniped-concierge-kube-system-pod-read 501 | namespace: kube-system 502 | labels: 503 | app: pinniped-concierge 504 | rules: 505 | - apiGroups: 506 | - "" 507 | resources: 508 | - pods 509 | verbs: 510 | - get 511 | - list 512 | - watch 513 | --- 514 | kind: RoleBinding 515 | apiVersion: rbac.authorization.k8s.io/v1 516 | metadata: 517 | name: pinniped-concierge-kube-system-pod-read 518 | namespace: kube-system 519 | labels: 520 | app: pinniped-concierge 521 | subjects: 522 | - kind: ServiceAccount 523 | name: pinniped-concierge 524 | namespace: pinniped-concierge 525 | roleRef: 526 | kind: Role 527 | name: pinniped-concierge-kube-system-pod-read 528 | apiGroup: rbac.authorization.k8s.io 529 | --- 530 | apiVersion: rbac.authorization.k8s.io/v1 531 | kind: ClusterRole 532 | metadata: 533 | name: pinniped-concierge-pre-authn-apis 534 | labels: 535 | app: pinniped-concierge 536 | rules: 537 | - apiGroups: 538 | - login.concierge.pinniped.dev 539 | resources: 540 | - tokencredentialrequests 541 | verbs: 542 | - create 543 | - list 544 | - apiGroups: 545 | - identity.concierge.pinniped.dev 546 | resources: 547 | - whoamirequests 548 | verbs: 549 | - create 550 | - list 551 | --- 552 | kind: ClusterRoleBinding 553 | apiVersion: rbac.authorization.k8s.io/v1 554 | metadata: 555 | name: pinniped-concierge-pre-authn-apis 556 | labels: 557 | app: pinniped-concierge 558 | subjects: 559 | - kind: Group 560 | name: system:authenticated 561 | apiGroup: rbac.authorization.k8s.io 562 | - kind: Group 563 | name: system:unauthenticated 564 | apiGroup: rbac.authorization.k8s.io 565 | roleRef: 566 | kind: ClusterRole 567 | name: pinniped-concierge-pre-authn-apis 568 | apiGroup: rbac.authorization.k8s.io 569 | --- 570 | kind: ClusterRoleBinding 571 | apiVersion: rbac.authorization.k8s.io/v1 572 | metadata: 573 | name: pinniped-concierge 574 | labels: 575 | app: pinniped-concierge 576 | subjects: 577 | - kind: ServiceAccount 578 | name: pinniped-concierge 579 | namespace: pinniped-concierge 580 | roleRef: 581 | kind: ClusterRole 582 | name: system:auth-delegator 583 | apiGroup: rbac.authorization.k8s.io 584 | --- 585 | kind: RoleBinding 586 | apiVersion: rbac.authorization.k8s.io/v1 587 | metadata: 588 | name: pinniped-concierge-extension-apiserver-authentication-reader 589 | namespace: kube-system 590 | labels: 591 | app: pinniped-concierge 592 | subjects: 593 | - kind: ServiceAccount 594 | name: pinniped-concierge 595 | namespace: pinniped-concierge 596 | roleRef: 597 | kind: Role 598 | name: extension-apiserver-authentication-reader 599 | apiGroup: rbac.authorization.k8s.io 600 | --- 601 | kind: Role 602 | apiVersion: rbac.authorization.k8s.io/v1 603 | metadata: 604 | name: pinniped-concierge-cluster-info-lister-watcher 605 | namespace: kube-public 606 | labels: 607 | app: pinniped-concierge 608 | rules: 609 | - apiGroups: 610 | - "" 611 | resources: 612 | - configmaps 613 | verbs: 614 | - list 615 | - watch 616 | --- 617 | kind: RoleBinding 618 | apiVersion: rbac.authorization.k8s.io/v1 619 | metadata: 620 | name: pinniped-concierge-cluster-info-lister-watcher 621 | namespace: kube-public 622 | labels: 623 | app: pinniped-concierge 624 | subjects: 625 | - kind: ServiceAccount 626 | name: pinniped-concierge 627 | namespace: pinniped-concierge 628 | roleRef: 629 | kind: Role 630 | name: pinniped-concierge-cluster-info-lister-watcher 631 | apiGroup: rbac.authorization.k8s.io 632 | -------------------------------------------------------------------------------- /src/pinniped/configuration.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/configuration.tf -------------------------------------------------------------------------------- /src/pinniped/data_source.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/data_source.tf -------------------------------------------------------------------------------- /src/pinniped/default_variant.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default_variant = "concierge-base" 3 | } 4 | -------------------------------------------------------------------------------- /src/pinniped/main.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/main._tf -------------------------------------------------------------------------------- /src/pinniped/supervisor-base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | commonAnnotations: 4 | catalog.kubestack.com/heritage: kubestack.com/catalog/pinniped 5 | catalog.kubestack.com/variant: base 6 | app.kubernetes.io/version: v0.15.0 7 | resources: 8 | - resources.yaml 9 | -------------------------------------------------------------------------------- /src/pinniped/variables.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/variables.tf -------------------------------------------------------------------------------- /src/pinniped/versions.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/versions.tf -------------------------------------------------------------------------------- /src/postgresql/_updater/metadata.yaml: -------------------------------------------------------------------------------- 1 | url: https://github.com/zalando/postgres-operator.git 2 | -------------------------------------------------------------------------------- /src/postgresql/_updater/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | source_path=$1 5 | target_path=$2 6 | tag=$3 7 | 8 | cp $source_path/manifests/postgres-operator.yaml $target_path/base/postgres-operator.yaml 9 | 10 | cp $source_path/manifests/operator-service-account-rbac.yaml $target_path/clusterwide/rbac.yaml 11 | 12 | # fix upstream by default exposing db externally 13 | # make base not clusterwide 14 | # and make provider indepented 15 | sed -e 's/enable_master_load_balancer: "true"/enable_master_load_balancer: "false"/g' \ 16 | -e '/watched_namespace/d' \ 17 | -e '/aws_region:/d' \ 18 | "${source_path}/manifests/configmap.yaml" \ 19 | > "${target_path}/base/configmap.yaml" 20 | 21 | # fix upstream net setting image correctly in provided manifest 22 | cd $target_path/base 23 | kustomize edit set image registry.opensource.zalan.do/acid/smoke-tested-postgres-operator=registry.opensource.zalan.do/acid/postgres-operator:$tag 24 | -------------------------------------------------------------------------------- /src/postgresql/base/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: postgres-operator 5 | data: 6 | # additional_owner_roles: "cron_admin" 7 | # additional_pod_capabilities: "SYS_NICE" 8 | # additional_secret_mount: "some-secret-name" 9 | # additional_secret_mount_path: "/some/dir" 10 | api_port: "8080" 11 | cluster_domain: cluster.local 12 | cluster_history_entries: "1000" 13 | cluster_labels: application:spilo 14 | cluster_name_label: cluster-name 15 | # connection_pooler_default_cpu_limit: "1" 16 | # connection_pooler_default_cpu_request: "500m" 17 | # connection_pooler_default_memory_limit: 100Mi 18 | # connection_pooler_default_memory_request: 100Mi 19 | connection_pooler_image: "registry.opensource.zalan.do/acid/pgbouncer:master-26" 20 | # connection_pooler_max_db_connections: 60 21 | # connection_pooler_mode: "transaction" 22 | # connection_pooler_number_of_instances: 2 23 | # connection_pooler_schema: "pooler" 24 | # connection_pooler_user: "pooler" 25 | crd_categories: "all" 26 | # custom_service_annotations: "keyx:valuez,keya:valuea" 27 | # custom_pod_annotations: "keya:valuea,keyb:valueb" 28 | db_hosted_zone: db.example.com 29 | debug_logging: "true" 30 | # default_cpu_limit: "1" 31 | # default_cpu_request: 100m 32 | # default_memory_limit: 500Mi 33 | # default_memory_request: 100Mi 34 | # delete_annotation_date_key: delete-date 35 | # delete_annotation_name_key: delete-clustername 36 | docker_image: ghcr.io/zalando/spilo-15:2.1-p9 37 | # downscaler_annotations: "deployment-time,downscaler/*" 38 | # enable_admin_role_for_users: "true" 39 | # enable_crd_registration: "true" 40 | # enable_cross_namespace_secret: "false" 41 | # enable_database_access: "true" 42 | enable_ebs_gp3_migration: "false" 43 | # enable_ebs_gp3_migration_max_size: "1000" 44 | # enable_init_containers: "true" 45 | # enable_lazy_spilo_upgrade: "false" 46 | enable_master_load_balancer: "false" 47 | enable_master_pooler_load_balancer: "false" 48 | enable_password_rotation: "false" 49 | # enable_patroni_failsafe_mode: "false" 50 | enable_pgversion_env_var: "true" 51 | # enable_pod_antiaffinity: "false" 52 | # enable_pod_disruption_budget: "true" 53 | # enable_postgres_team_crd: "false" 54 | # enable_postgres_team_crd_superusers: "false" 55 | enable_readiness_probe: "false" 56 | enable_replica_load_balancer: "false" 57 | enable_replica_pooler_load_balancer: "false" 58 | # enable_shm_volume: "true" 59 | # enable_sidecars: "true" 60 | enable_spilo_wal_path_compat: "true" 61 | enable_team_id_clustername_prefix: "false" 62 | enable_team_member_deprecation: "false" 63 | # enable_team_superuser: "false" 64 | enable_teams_api: "false" 65 | # etcd_host: "" 66 | external_traffic_policy: "Cluster" 67 | # gcp_credentials: "" 68 | # ignored_annotations: "" 69 | # infrastructure_roles_secret_name: "postgresql-infrastructure-roles" 70 | # infrastructure_roles_secrets: "secretname:monitoring-roles,userkey:user,passwordkey:password,rolekey:inrole" 71 | # ignore_instance_limits_annotation_key: "" 72 | # inherited_annotations: owned-by 73 | # inherited_labels: application,environment 74 | # kube_iam_role: "" 75 | # kubernetes_use_configmaps: "false" 76 | # log_s3_bucket: "" 77 | # logical_backup_azure_storage_account_name: "" 78 | # logical_backup_azure_storage_container: "" 79 | # logical_backup_azure_storage_account_key: "" 80 | # logical_backup_cpu_limit: "" 81 | # logical_backup_cpu_request: "" 82 | logical_backup_docker_image: "registry.opensource.zalan.do/acid/logical-backup:v1.9.0" 83 | # logical_backup_google_application_credentials: "" 84 | logical_backup_job_prefix: "logical-backup-" 85 | # logical_backup_memory_limit: "" 86 | # logical_backup_memory_request: "" 87 | logical_backup_provider: "s3" 88 | # logical_backup_s3_access_key_id: "" 89 | logical_backup_s3_bucket: "my-bucket-url" 90 | # logical_backup_s3_region: "" 91 | # logical_backup_s3_endpoint: "" 92 | # logical_backup_s3_secret_access_key: "" 93 | logical_backup_s3_sse: "AES256" 94 | # logical_backup_s3_retention_time: "" 95 | logical_backup_schedule: "30 00 * * *" 96 | major_version_upgrade_mode: "manual" 97 | # major_version_upgrade_team_allow_list: "" 98 | master_dns_name_format: "{cluster}.{namespace}.{hostedzone}" 99 | # master_legacy_dns_name_format: "{cluster}.{team}.{hostedzone}" 100 | # master_pod_move_timeout: 20m 101 | # max_instances: "-1" 102 | # min_instances: "-1" 103 | # max_cpu_request: "1" 104 | # max_memory_request: 4Gi 105 | # min_cpu_limit: 250m 106 | # min_memory_limit: 250Mi 107 | # minimal_major_version: "11" 108 | # node_readiness_label: "status:ready" 109 | # node_readiness_label_merge: "OR" 110 | # oauth_token_secret_name: postgresql-operator 111 | # pam_configuration: | 112 | # https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees 113 | # pam_role_name: zalandos 114 | patroni_api_check_interval: "1s" 115 | patroni_api_check_timeout: "5s" 116 | # password_rotation_interval: "90" 117 | # password_rotation_user_retention: "180" 118 | pdb_name_format: "postgres-{cluster}-pdb" 119 | # pod_antiaffinity_preferred_during_scheduling: "false" 120 | # pod_antiaffinity_topology_key: "kubernetes.io/hostname" 121 | pod_deletion_wait_timeout: 10m 122 | # pod_environment_configmap: "default/my-custom-config" 123 | # pod_environment_secret: "my-custom-secret" 124 | pod_label_wait_timeout: 10m 125 | pod_management_policy: "ordered_ready" 126 | # pod_priority_class_name: "postgres-pod-priority" 127 | pod_role_label: spilo-role 128 | # pod_service_account_definition: "" 129 | pod_service_account_name: "postgres-pod" 130 | # pod_service_account_role_binding_definition: "" 131 | pod_terminate_grace_period: 5m 132 | # postgres_superuser_teams: "postgres_superusers" 133 | # protected_role_names: "admin,cron_admin" 134 | ready_wait_interval: 3s 135 | ready_wait_timeout: 30s 136 | repair_period: 5m 137 | replica_dns_name_format: "{cluster}-repl.{namespace}.{hostedzone}" 138 | # replica_legacy_dns_name_format: "{cluster}-repl.{team}.{hostedzone}" 139 | replication_username: standby 140 | resource_check_interval: 3s 141 | resource_check_timeout: 10m 142 | resync_period: 30m 143 | ring_log_lines: "100" 144 | role_deletion_suffix: "_deleted" 145 | secret_name_template: "{username}.{cluster}.credentials.{tprkind}.{tprgroup}" 146 | share_pgsocket_with_sidecars: "false" 147 | # sidecar_docker_images: "" 148 | # set_memory_request_to_limit: "false" 149 | spilo_allow_privilege_escalation: "true" 150 | # spilo_runasuser: 101 151 | # spilo_runasgroup: 103 152 | # spilo_fsgroup: 103 153 | spilo_privileged: "false" 154 | storage_resize_mode: "pvc" 155 | super_username: postgres 156 | # target_major_version: "15" 157 | # team_admin_role: "admin" 158 | # team_api_role_configuration: "log_statement:all" 159 | # teams_api_url: http://fake-teams-api.default.svc.cluster.local 160 | # toleration: "key:db-only,operator:Exists,effect:NoSchedule" 161 | # wal_az_storage_account: "" 162 | # wal_gs_bucket: "" 163 | # wal_s3_bucket: "" 164 | workers: "8" 165 | -------------------------------------------------------------------------------- /src/postgresql/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: default 4 | commonAnnotations: 5 | app.kubernetes.io/version: v1.6.2 6 | catalog.kubestack.com/heritage: kubestack.com/catalog/postgresql 7 | catalog.kubestack.com/variant: base 8 | commonLabels: 9 | app.kubernetes.io/component: operator 10 | app.kubernetes.io/managed-by: kubestack 11 | app.kubernetes.io/name: postgresql 12 | resources: 13 | - postgres-operator.yaml 14 | - configmap.yaml 15 | images: 16 | - name: registry.opensource.zalan.do/acid/smoke-tested-postgres-operator 17 | newName: registry.opensource.zalan.do/acid/postgres-operator 18 | newTag: v1.9.0 19 | -------------------------------------------------------------------------------- /src/postgresql/base/postgres-operator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: postgres-operator 5 | labels: 6 | application: postgres-operator 7 | spec: 8 | replicas: 1 9 | strategy: 10 | type: "Recreate" 11 | selector: 12 | matchLabels: 13 | name: postgres-operator 14 | template: 15 | metadata: 16 | labels: 17 | name: postgres-operator 18 | spec: 19 | serviceAccountName: postgres-operator 20 | containers: 21 | - name: postgres-operator 22 | image: registry.opensource.zalan.do/acid/postgres-operator:v1.9.0 23 | imagePullPolicy: IfNotPresent 24 | resources: 25 | requests: 26 | cpu: 100m 27 | memory: 250Mi 28 | limits: 29 | cpu: 500m 30 | memory: 500Mi 31 | securityContext: 32 | runAsUser: 1000 33 | runAsNonRoot: true 34 | readOnlyRootFilesystem: true 35 | allowPrivilegeEscalation: false 36 | env: 37 | # provided additional ENV vars can overwrite individual config map entries 38 | - name: CONFIG_MAP_NAME 39 | value: "postgres-operator" 40 | # In order to use the CRD OperatorConfiguration instead, uncomment these lines and comment out the two lines above 41 | # - name: POSTGRES_OPERATOR_CONFIGURATION_OBJECT 42 | # value: postgresql-operator-default-configuration 43 | # Define an ID to isolate controllers from each other 44 | # - name: CONTROLLER_ID 45 | # value: "second-operator" 46 | -------------------------------------------------------------------------------- /src/postgresql/clusterwide/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | bases: 4 | - ../base/ 5 | namespace: operator-postgresql 6 | commonAnnotations: 7 | app.kubernetes.io/version: v1.6.2 8 | catalog.kubestack.com/heritage: kubestack.com/catalog/postgresql 9 | catalog.kubestack.com/variant: clusterwide 10 | commonLabels: 11 | app.kubernetes.io/component: operator 12 | app.kubernetes.io/managed-by: kubestack 13 | app.kubernetes.io/name: postgresql 14 | patchesStrategicMerge: 15 | - patch-deployment-env.yaml 16 | resources: 17 | - namespace.yaml 18 | - rbac.yaml 19 | -------------------------------------------------------------------------------- /src/postgresql/clusterwide/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: operator-postgresql 5 | -------------------------------------------------------------------------------- /src/postgresql/clusterwide/patch-deployment-env.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: postgres-operator 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name: postgres-operator 10 | env: 11 | - name: WATCHED_NAMESPACE 12 | value: '*' 13 | -------------------------------------------------------------------------------- /src/postgresql/clusterwide/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: postgres-operator 5 | namespace: default 6 | 7 | --- 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | name: postgres-operator 12 | rules: 13 | # all verbs allowed for custom operator resources 14 | - apiGroups: 15 | - acid.zalan.do 16 | resources: 17 | - postgresqls 18 | - postgresqls/status 19 | - operatorconfigurations 20 | verbs: 21 | - create 22 | - delete 23 | - deletecollection 24 | - get 25 | - list 26 | - patch 27 | - update 28 | - watch 29 | # operator only reads PostgresTeams 30 | - apiGroups: 31 | - acid.zalan.do 32 | resources: 33 | - postgresteams 34 | verbs: 35 | - get 36 | - list 37 | - watch 38 | # all verbs allowed for event streams (Zalando-internal feature) 39 | # - apiGroups: 40 | # - zalando.org 41 | # resources: 42 | # - fabriceventstreams 43 | # verbs: 44 | # - create 45 | # - delete 46 | # - deletecollection 47 | # - get 48 | # - list 49 | # - patch 50 | # - update 51 | # - watch 52 | # to create or get/update CRDs when starting up 53 | - apiGroups: 54 | - apiextensions.k8s.io 55 | resources: 56 | - customresourcedefinitions 57 | verbs: 58 | - create 59 | - get 60 | - patch 61 | - update 62 | # to read configuration from ConfigMaps 63 | - apiGroups: 64 | - "" 65 | resources: 66 | - configmaps 67 | verbs: 68 | - get 69 | # to send events to the CRs 70 | - apiGroups: 71 | - "" 72 | resources: 73 | - events 74 | verbs: 75 | - create 76 | - get 77 | - list 78 | - patch 79 | - update 80 | - watch 81 | # to manage endpoints which are also used by Patroni 82 | - apiGroups: 83 | - "" 84 | resources: 85 | - endpoints 86 | verbs: 87 | - create 88 | - delete 89 | - deletecollection 90 | - get 91 | - list 92 | - patch 93 | - update 94 | - watch 95 | # to CRUD secrets for database access 96 | - apiGroups: 97 | - "" 98 | resources: 99 | - secrets 100 | verbs: 101 | - create 102 | - delete 103 | - get 104 | - update 105 | # to check nodes for node readiness label 106 | - apiGroups: 107 | - "" 108 | resources: 109 | - nodes 110 | verbs: 111 | - get 112 | - list 113 | - watch 114 | # to read or delete existing PVCs. Creation via StatefulSet 115 | - apiGroups: 116 | - "" 117 | resources: 118 | - persistentvolumeclaims 119 | verbs: 120 | - delete 121 | - get 122 | - list 123 | - patch 124 | - update 125 | # to read existing PVs. Creation should be done via dynamic provisioning 126 | - apiGroups: 127 | - "" 128 | resources: 129 | - persistentvolumes 130 | verbs: 131 | - get 132 | - list 133 | - update # only for resizing AWS volumes 134 | # to watch Spilo pods and do rolling updates. Creation via StatefulSet 135 | - apiGroups: 136 | - "" 137 | resources: 138 | - pods 139 | verbs: 140 | - delete 141 | - get 142 | - list 143 | - patch 144 | - update 145 | - watch 146 | # to resize the filesystem in Spilo pods when increasing volume size 147 | - apiGroups: 148 | - "" 149 | resources: 150 | - pods/exec 151 | verbs: 152 | - create 153 | # to CRUD services to point to Postgres cluster instances 154 | - apiGroups: 155 | - "" 156 | resources: 157 | - services 158 | verbs: 159 | - create 160 | - delete 161 | - get 162 | - patch 163 | - update 164 | # to CRUD the StatefulSet which controls the Postgres cluster instances 165 | - apiGroups: 166 | - apps 167 | resources: 168 | - statefulsets 169 | - deployments 170 | verbs: 171 | - create 172 | - delete 173 | - get 174 | - list 175 | - patch 176 | # to CRUD cron jobs for logical backups 177 | - apiGroups: 178 | - batch 179 | resources: 180 | - cronjobs 181 | verbs: 182 | - create 183 | - delete 184 | - get 185 | - list 186 | - patch 187 | - update 188 | # to get namespaces operator resources can run in 189 | - apiGroups: 190 | - "" 191 | resources: 192 | - namespaces 193 | verbs: 194 | - get 195 | # to define PDBs. Update happens via delete/create 196 | - apiGroups: 197 | - policy 198 | resources: 199 | - poddisruptionbudgets 200 | verbs: 201 | - create 202 | - delete 203 | - get 204 | # to create ServiceAccounts in each namespace the operator watches 205 | - apiGroups: 206 | - "" 207 | resources: 208 | - serviceaccounts 209 | verbs: 210 | - get 211 | - create 212 | # to create role bindings to the postgres-pod service account 213 | - apiGroups: 214 | - rbac.authorization.k8s.io 215 | resources: 216 | - rolebindings 217 | verbs: 218 | - get 219 | - create 220 | # to grant privilege to run privileged pods (not needed by default) 221 | #- apiGroups: 222 | # - extensions 223 | # resources: 224 | # - podsecuritypolicies 225 | # resourceNames: 226 | # - privileged 227 | # verbs: 228 | # - use 229 | 230 | --- 231 | apiVersion: rbac.authorization.k8s.io/v1 232 | kind: ClusterRoleBinding 233 | metadata: 234 | name: postgres-operator 235 | roleRef: 236 | apiGroup: rbac.authorization.k8s.io 237 | kind: ClusterRole 238 | name: postgres-operator 239 | subjects: 240 | - kind: ServiceAccount 241 | name: postgres-operator 242 | namespace: default 243 | 244 | --- 245 | apiVersion: rbac.authorization.k8s.io/v1 246 | kind: ClusterRole 247 | metadata: 248 | name: postgres-pod 249 | rules: 250 | # Patroni needs to watch and manage endpoints 251 | - apiGroups: 252 | - "" 253 | resources: 254 | - endpoints 255 | verbs: 256 | - create 257 | - delete 258 | - deletecollection 259 | - get 260 | - list 261 | - patch 262 | - update 263 | - watch 264 | # Patroni needs to watch pods 265 | - apiGroups: 266 | - "" 267 | resources: 268 | - pods 269 | verbs: 270 | - get 271 | - list 272 | - patch 273 | - update 274 | - watch 275 | # to let Patroni create a headless service 276 | - apiGroups: 277 | - "" 278 | resources: 279 | - services 280 | verbs: 281 | - create 282 | # to grant privilege to run privileged pods (not needed by default) 283 | #- apiGroups: 284 | # - extensions 285 | # resources: 286 | # - podsecuritypolicies 287 | # resourceNames: 288 | # - privileged 289 | # verbs: 290 | # - use 291 | -------------------------------------------------------------------------------- /src/postgresql/configuration.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/configuration.tf -------------------------------------------------------------------------------- /src/postgresql/data_source.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/data_source.tf -------------------------------------------------------------------------------- /src/postgresql/default_variant.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default_variant = "clusterwide" 3 | } 4 | -------------------------------------------------------------------------------- /src/postgresql/main.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/main._tf -------------------------------------------------------------------------------- /src/postgresql/variables.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/variables.tf -------------------------------------------------------------------------------- /src/postgresql/versions.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/versions.tf -------------------------------------------------------------------------------- /src/prometheus/_updater/metadata.yaml: -------------------------------------------------------------------------------- 1 | url: https://github.com/coreos/prometheus-operator.git 2 | ref: main 3 | filter_tags: ^v(.+) 4 | -------------------------------------------------------------------------------- /src/prometheus/_updater/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | source_path=$1 5 | target_path=$2 6 | version=$3 7 | 8 | cp $source_path/bundle.yaml $target_path/base/bundle.yaml 9 | -------------------------------------------------------------------------------- /src/prometheus/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | commonAnnotations: 4 | catalog.kubestack.com/heritage: kubestack.com/catalog/prometheus 5 | catalog.kubestack.com/variant: base 6 | app.kubernetes.io/version: v0.47.1 7 | resources: 8 | - bundle.yaml 9 | -------------------------------------------------------------------------------- /src/prometheus/clusterwide/instance-cluster-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: prometheus-instance 5 | rules: 6 | - apiGroups: [""] 7 | resources: 8 | - nodes 9 | - services 10 | - endpoints 11 | - pods 12 | verbs: ["get", "list", "watch"] 13 | - apiGroups: [""] 14 | resources: 15 | - configmaps 16 | verbs: ["get"] 17 | -------------------------------------------------------------------------------- /src/prometheus/clusterwide/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | bases: 4 | - ../base/ 5 | namespace: operator-prometheus 6 | commonAnnotations: 7 | catalog.kubestack.com/heritage: kubestack.com/catalog/prometheus 8 | catalog.kubestack.com/variant: clusterwide 9 | app.kubernetes.io/version: v0.47.1 10 | resources: 11 | - namespace.yaml 12 | - instance-cluster-role.yaml 13 | -------------------------------------------------------------------------------- /src/prometheus/clusterwide/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: operator-prometheus 5 | -------------------------------------------------------------------------------- /src/prometheus/configuration.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/configuration.tf -------------------------------------------------------------------------------- /src/prometheus/data_source.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/data_source.tf -------------------------------------------------------------------------------- /src/prometheus/default_variant.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default_variant = "clusterwide" 3 | } 4 | -------------------------------------------------------------------------------- /src/prometheus/main.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/main._tf -------------------------------------------------------------------------------- /src/prometheus/variables.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/variables.tf -------------------------------------------------------------------------------- /src/prometheus/versions.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/versions.tf -------------------------------------------------------------------------------- /src/sealed-secrets/_updater/metadata.yaml: -------------------------------------------------------------------------------- 1 | url: https://github.com/bitnami-labs/sealed-secrets.git 2 | ref: main 3 | filter_tags: ^v([0-9.]+)$ 4 | -------------------------------------------------------------------------------- /src/sealed-secrets/_updater/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | source_path=$1 5 | target_path=$2 6 | version=$3 7 | 8 | curl -Lo $target_path/base/controller.yaml https://github.com/bitnami-labs/sealed-secrets/releases/download/$version/controller.yaml 9 | -------------------------------------------------------------------------------- /src/sealed-secrets/base/controller.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: sealedsecrets.bitnami.com 6 | spec: 7 | group: bitnami.com 8 | names: 9 | kind: SealedSecret 10 | listKind: SealedSecretList 11 | plural: sealedsecrets 12 | singular: sealedsecret 13 | scope: Namespaced 14 | versions: 15 | - name: v1alpha1 16 | schema: 17 | openAPIV3Schema: 18 | description: SealedSecret is the K8s representation of a "sealed Secret" - 19 | a regular k8s Secret that has been sealed (encrypted) using the controller's 20 | key. 21 | properties: 22 | apiVersion: 23 | description: 'APIVersion defines the versioned schema of this representation 24 | of an object. Servers should convert recognized schemas to the latest 25 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 26 | type: string 27 | kind: 28 | description: 'Kind is a string value representing the REST resource this 29 | object represents. Servers may infer this from the endpoint the client 30 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 31 | type: string 32 | metadata: 33 | type: object 34 | spec: 35 | description: SealedSecretSpec is the specification of a SealedSecret 36 | properties: 37 | data: 38 | description: Data is deprecated and will be removed eventually. Use 39 | per-value EncryptedData instead. 40 | format: byte 41 | type: string 42 | encryptedData: 43 | additionalProperties: 44 | type: string 45 | type: object 46 | x-kubernetes-preserve-unknown-fields: true 47 | template: 48 | description: Template defines the structure of the Secret that will 49 | be created from this sealed secret. 50 | properties: 51 | data: 52 | additionalProperties: 53 | type: string 54 | description: Keys that should be templated using decrypted data 55 | nullable: true 56 | type: object 57 | metadata: 58 | description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata' 59 | nullable: true 60 | type: object 61 | x-kubernetes-preserve-unknown-fields: true 62 | type: 63 | description: Used to facilitate programmatic handling of secret 64 | data. 65 | type: string 66 | type: object 67 | required: 68 | - encryptedData 69 | type: object 70 | status: 71 | description: SealedSecretStatus is the most recently observed status of 72 | the SealedSecret. 73 | properties: 74 | conditions: 75 | description: Represents the latest available observations of a sealed 76 | secret's current state. 77 | items: 78 | description: SealedSecretCondition describes the state of a sealed 79 | secret at a certain point. 80 | properties: 81 | lastTransitionTime: 82 | description: Last time the condition transitioned from one status 83 | to another. 84 | format: date-time 85 | type: string 86 | lastUpdateTime: 87 | description: The last time this condition was updated. 88 | format: date-time 89 | type: string 90 | message: 91 | description: A human readable message indicating details about 92 | the transition. 93 | type: string 94 | reason: 95 | description: The reason for the condition's last transition. 96 | type: string 97 | status: 98 | description: 'Status of the condition for a sealed secret. Valid 99 | values for "Synced": "True", "False", or "Unknown".' 100 | type: string 101 | type: 102 | description: 'Type of condition for a sealed secret. Valid value: 103 | "Synced"' 104 | type: string 105 | required: 106 | - status 107 | - type 108 | type: object 109 | type: array 110 | observedGeneration: 111 | description: ObservedGeneration reflects the generation most recently 112 | observed by the sealed-secrets controller. 113 | format: int64 114 | type: integer 115 | type: object 116 | required: 117 | - spec 118 | type: object 119 | served: true 120 | storage: true 121 | subresources: 122 | status: {} 123 | --- 124 | apiVersion: v1 125 | kind: Service 126 | metadata: 127 | annotations: {} 128 | labels: 129 | name: sealed-secrets-controller 130 | name: sealed-secrets-controller 131 | namespace: kube-system 132 | spec: 133 | ports: 134 | - port: 8080 135 | targetPort: 8080 136 | selector: 137 | name: sealed-secrets-controller 138 | type: ClusterIP 139 | --- 140 | apiVersion: rbac.authorization.k8s.io/v1 141 | kind: RoleBinding 142 | metadata: 143 | annotations: {} 144 | labels: 145 | name: sealed-secrets-service-proxier 146 | name: sealed-secrets-service-proxier 147 | namespace: kube-system 148 | roleRef: 149 | apiGroup: rbac.authorization.k8s.io 150 | kind: Role 151 | name: sealed-secrets-service-proxier 152 | subjects: 153 | - apiGroup: rbac.authorization.k8s.io 154 | kind: Group 155 | name: system:authenticated 156 | --- 157 | apiVersion: rbac.authorization.k8s.io/v1 158 | kind: Role 159 | metadata: 160 | annotations: {} 161 | labels: 162 | name: sealed-secrets-service-proxier 163 | name: sealed-secrets-service-proxier 164 | namespace: kube-system 165 | rules: 166 | - apiGroups: 167 | - "" 168 | resourceNames: 169 | - sealed-secrets-controller 170 | resources: 171 | - services 172 | verbs: 173 | - get 174 | - apiGroups: 175 | - "" 176 | resourceNames: 177 | - 'http:sealed-secrets-controller:' 178 | - http:sealed-secrets-controller:http 179 | - sealed-secrets-controller 180 | resources: 181 | - services/proxy 182 | verbs: 183 | - create 184 | - get 185 | --- 186 | apiVersion: rbac.authorization.k8s.io/v1 187 | kind: Role 188 | metadata: 189 | annotations: {} 190 | labels: 191 | name: sealed-secrets-key-admin 192 | name: sealed-secrets-key-admin 193 | namespace: kube-system 194 | rules: 195 | - apiGroups: 196 | - "" 197 | resources: 198 | - secrets 199 | verbs: 200 | - create 201 | - list 202 | --- 203 | apiVersion: v1 204 | kind: ServiceAccount 205 | metadata: 206 | annotations: {} 207 | labels: 208 | name: sealed-secrets-controller 209 | name: sealed-secrets-controller 210 | namespace: kube-system 211 | --- 212 | apiVersion: apps/v1 213 | kind: Deployment 214 | metadata: 215 | annotations: {} 216 | labels: 217 | name: sealed-secrets-controller 218 | name: sealed-secrets-controller 219 | namespace: kube-system 220 | spec: 221 | minReadySeconds: 30 222 | replicas: 1 223 | revisionHistoryLimit: 10 224 | selector: 225 | matchLabels: 226 | name: sealed-secrets-controller 227 | strategy: 228 | rollingUpdate: 229 | maxSurge: 25% 230 | maxUnavailable: 25% 231 | type: RollingUpdate 232 | template: 233 | metadata: 234 | annotations: {} 235 | labels: 236 | name: sealed-secrets-controller 237 | spec: 238 | containers: 239 | - args: [] 240 | command: 241 | - controller 242 | env: [] 243 | image: docker.io/bitnami/sealed-secrets-controller:v0.20.2 244 | imagePullPolicy: IfNotPresent 245 | livenessProbe: 246 | httpGet: 247 | path: /healthz 248 | port: http 249 | name: sealed-secrets-controller 250 | ports: 251 | - containerPort: 8080 252 | name: http 253 | readinessProbe: 254 | httpGet: 255 | path: /healthz 256 | port: http 257 | securityContext: 258 | readOnlyRootFilesystem: true 259 | runAsNonRoot: true 260 | runAsUser: 1001 261 | stdin: false 262 | tty: false 263 | volumeMounts: 264 | - mountPath: /tmp 265 | name: tmp 266 | imagePullSecrets: [] 267 | initContainers: [] 268 | securityContext: 269 | fsGroup: 65534 270 | serviceAccountName: sealed-secrets-controller 271 | terminationGracePeriodSeconds: 30 272 | volumes: 273 | - emptyDir: {} 274 | name: tmp 275 | --- 276 | apiVersion: rbac.authorization.k8s.io/v1 277 | kind: RoleBinding 278 | metadata: 279 | annotations: {} 280 | labels: 281 | name: sealed-secrets-controller 282 | name: sealed-secrets-controller 283 | namespace: kube-system 284 | roleRef: 285 | apiGroup: rbac.authorization.k8s.io 286 | kind: Role 287 | name: sealed-secrets-key-admin 288 | subjects: 289 | - kind: ServiceAccount 290 | name: sealed-secrets-controller 291 | namespace: kube-system 292 | --- 293 | apiVersion: rbac.authorization.k8s.io/v1 294 | kind: ClusterRoleBinding 295 | metadata: 296 | annotations: {} 297 | labels: 298 | name: sealed-secrets-controller 299 | name: sealed-secrets-controller 300 | roleRef: 301 | apiGroup: rbac.authorization.k8s.io 302 | kind: ClusterRole 303 | name: secrets-unsealer 304 | subjects: 305 | - kind: ServiceAccount 306 | name: sealed-secrets-controller 307 | namespace: kube-system 308 | --- 309 | apiVersion: rbac.authorization.k8s.io/v1 310 | kind: ClusterRole 311 | metadata: 312 | annotations: {} 313 | labels: 314 | name: secrets-unsealer 315 | name: secrets-unsealer 316 | rules: 317 | - apiGroups: 318 | - bitnami.com 319 | resources: 320 | - sealedsecrets 321 | verbs: 322 | - get 323 | - list 324 | - watch 325 | - apiGroups: 326 | - bitnami.com 327 | resources: 328 | - sealedsecrets/status 329 | verbs: 330 | - update 331 | - apiGroups: 332 | - "" 333 | resources: 334 | - secrets 335 | verbs: 336 | - get 337 | - list 338 | - create 339 | - update 340 | - delete 341 | - watch 342 | - apiGroups: 343 | - "" 344 | resources: 345 | - events 346 | verbs: 347 | - create 348 | - patch 349 | - apiGroups: 350 | - "" 351 | resources: 352 | - namespaces 353 | verbs: 354 | - get 355 | -------------------------------------------------------------------------------- /src/sealed-secrets/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | commonAnnotations: 4 | app.kubernetes.io/version: v0.15.0 5 | catalog.kubestack.com/heritage: kubestack.com/catalog/sealed-secrets 6 | catalog.kubestack.com/variant: base 7 | commonLabels: 8 | app.kubernetes.io/component: controller 9 | app.kubernetes.io/managed-by: kubestack 10 | app.kubernetes.io/name: sealed-secrets 11 | resources: 12 | - controller.yaml 13 | -------------------------------------------------------------------------------- /src/sealed-secrets/configuration.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/configuration.tf -------------------------------------------------------------------------------- /src/sealed-secrets/data_source.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/data_source.tf -------------------------------------------------------------------------------- /src/sealed-secrets/default_variant.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default_variant = "base" 3 | } 4 | -------------------------------------------------------------------------------- /src/sealed-secrets/main.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/main._tf -------------------------------------------------------------------------------- /src/sealed-secrets/variables.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/variables.tf -------------------------------------------------------------------------------- /src/sealed-secrets/versions.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/versions.tf -------------------------------------------------------------------------------- /src/tektoncd/_updater/metadata.yaml: -------------------------------------------------------------------------------- 1 | url: https://github.com/tektoncd/pipeline.git 2 | ref: main 3 | -------------------------------------------------------------------------------- /src/tektoncd/_updater/run.sh: -------------------------------------------------------------------------------- 1 | # https://storage.googleapis.com/tekton-releases/previous/v0.4.0/release.yaml 2 | 3 | #!/bin/sh 4 | 5 | set -e 6 | source_path=$1 7 | target_path=$2 8 | version=$3 9 | 10 | curl -Lo $target_path/base/release.yaml https://github.com/tektoncd/pipeline/releases/download/$version/release.yaml 11 | -------------------------------------------------------------------------------- /src/tektoncd/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | commonAnnotations: 4 | app.kubernetes.io/version: v0.23.0 5 | catalog.kubestack.com/heritage: kubestack.com/catalog/tektoncd 6 | catalog.kubestack.com/variant: base 7 | commonLabels: 8 | app.kubernetes.io/component: controller 9 | app.kubernetes.io/managed-by: kubestack 10 | app.kubernetes.io/name: tektoncd 11 | resources: 12 | - release.yaml 13 | -------------------------------------------------------------------------------- /src/tektoncd/configuration.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/configuration.tf -------------------------------------------------------------------------------- /src/tektoncd/data_source.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/data_source.tf -------------------------------------------------------------------------------- /src/tektoncd/default_variant.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default_variant = "base" 3 | } 4 | -------------------------------------------------------------------------------- /src/tektoncd/main.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/main._tf -------------------------------------------------------------------------------- /src/tektoncd/variables.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/variables.tf -------------------------------------------------------------------------------- /src/tektoncd/versions.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/versions.tf -------------------------------------------------------------------------------- /src/test/base/Kustomization: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | namespace: test 5 | 6 | resources: 7 | - namespace.yaml 8 | - configmap.yaml 9 | - deployment.yaml 10 | 11 | secretGenerator: 12 | - name: test 13 | literals: 14 | - "TEST=TEST" 15 | -------------------------------------------------------------------------------- /src/test/base/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | creationTimestamp: null 5 | name: test 6 | -------------------------------------------------------------------------------- /src/test/base/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app: test 7 | name: test 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: test 13 | strategy: {} 14 | template: 15 | metadata: 16 | creationTimestamp: null 17 | labels: 18 | app: test 19 | spec: 20 | containers: 21 | - image: busybox 22 | name: busybox 23 | command: 24 | - sleep 25 | - "60" 26 | resources: {} 27 | status: {} 28 | -------------------------------------------------------------------------------- /src/test/base/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: test -------------------------------------------------------------------------------- /src/test/configuration.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/configuration.tf -------------------------------------------------------------------------------- /src/test/data_source.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/data_source.tf -------------------------------------------------------------------------------- /src/test/default_variant.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default_variant = "base" 3 | } 4 | -------------------------------------------------------------------------------- /src/test/main.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/main._tf -------------------------------------------------------------------------------- /src/test/overlay/Kustomization: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | namespace: test-overlay 5 | 6 | resources: 7 | - ../base 8 | 9 | patches: 10 | - path: patch-namespace.yaml 11 | target: 12 | kind: Namespace 13 | name: test 14 | version: v1 15 | -------------------------------------------------------------------------------- /src/test/overlay/patch-namespace.yaml: -------------------------------------------------------------------------------- 1 | - op: replace 2 | path: /metadata/name 3 | value: test-overlay 4 | -------------------------------------------------------------------------------- /src/test/variables.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/variables.tf -------------------------------------------------------------------------------- /src/test/versions.tf: -------------------------------------------------------------------------------- 1 | ../_terraform_module/versions.tf -------------------------------------------------------------------------------- /test/k3d/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM kubestack/framework:v0.19.1-beta.0-kind 2 | 3 | COPY Pipfile Pipfile.lock /opt/ 4 | WORKDIR /opt 5 | 6 | RUN pip install --no-cache-dir pipenv &&\ 7 | PIPENV_VENV_IN_PROJECT=true pipenv install 8 | 9 | COPY main.tf.tpl main.py /opt/test/ 10 | 11 | ENV PATH=/opt/.venv/bin:$PATH \ 12 | KUBECONFIG=/opt/test/.kubeconfig \ 13 | KUBECONFIG_PATH=/opt/test/.kubeconfig 14 | 15 | WORKDIR /opt/test 16 | ENTRYPOINT ["python", "main.py"] 17 | -------------------------------------------------------------------------------- /test/k3d/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | kubernetes = "*" 10 | jinja2 = "*" 11 | 12 | [requires] 13 | python_version = "3" 14 | -------------------------------------------------------------------------------- /test/k3d/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import logging 4 | import sys 5 | import time 6 | 7 | from os.path import isdir, join 8 | from subprocess import Popen 9 | from tempfile import TemporaryDirectory 10 | from shutil import unpack_archive 11 | 12 | from kubernetes import client, config 13 | from jinja2 import Environment, FileSystemLoader 14 | 15 | DISTDIR = "/_dist" 16 | TIMEOUT = 600 # 10 minutes in seconds 17 | 18 | 19 | def run_cmd(name, path, cmd, timeout): 20 | p = Popen(cmd, cwd=path) 21 | p.wait(timeout) 22 | assert p.returncode == 0 23 | 24 | 25 | def wait_retries(name, timeout): 26 | config.load_kube_config() 27 | 28 | v1 = client.CoreV1Api() 29 | 30 | count = 0 31 | start = time.time() 32 | failed_pods = [] 33 | 34 | while True: 35 | # we give up 36 | if (time.time() - start) >= timeout: 37 | break 38 | 39 | failed_pods = [] 40 | 41 | ret = v1.list_pod_for_all_namespaces(watch=False) 42 | 43 | for p in ret.items: 44 | metann = f"{p.metadata.namespace}/{p.metadata.name}" 45 | 46 | # continue if there are no conditions yet 47 | if not p.status.conditions: 48 | continue 49 | 50 | is_ready = False 51 | for c in p.status.conditions: 52 | if c.type != "Ready": 53 | continue 54 | 55 | if c.status == "True" or (c.status == "False" and c.reason == "PodCompleted"): 56 | is_ready = True 57 | 58 | if not is_ready: 59 | failed_pods.append(metann) 60 | 61 | # we're done here 62 | if len(failed_pods) == 0: 63 | break 64 | 65 | # we're not done 66 | # sleep a little, then try again 67 | count += 1 68 | time.sleep(min(count * 2, 30)) 69 | 70 | # output debug info 71 | if len(failed_pods) > 0: 72 | ret = v1.list_pod_for_all_namespaces(watch=False) 73 | for p in ret.items: 74 | metann = f"{p.metadata.namespace}/{p.metadata.name}" 75 | podstatus = f"{p.status.phase:<11} {metann}" 76 | logging.error(podstatus) 77 | 78 | logging.error(f"timed out waiting for: {failed_pods}") 79 | 80 | assert len(failed_pods) == 0 81 | 82 | 83 | def run_steps(name, path): 84 | steps = { 85 | "init": {"type": "run_cmd", 86 | "path": path, 87 | "cmd": ["terraform", "init"]}, 88 | "apply": {"type": "run_cmd", 89 | "path": path, 90 | "cmd": ["terraform", 91 | "apply", 92 | "--auto-approve"]}, 93 | "wait": {"type": "wait_retries"}, 94 | "destroy": {"type": "run_cmd", 95 | "path": path, 96 | "cmd": ["terraform", 97 | "destroy", 98 | "--auto-approve"]} 99 | } 100 | 101 | for step in steps.values(): 102 | if step["type"] == "run_cmd": 103 | run_cmd(name, step["path"], step["cmd"], TIMEOUT) 104 | if step["type"] == "wait_retries": 105 | wait_retries(name, TIMEOUT) 106 | 107 | 108 | if __name__ == '__main__': 109 | if len(sys.argv) < 3: 110 | logging.fatal(f"missing 2 args: name and variant") 111 | sys.exit(1) 112 | 113 | name = sys.argv[1] 114 | variant = sys.argv[2] 115 | 116 | with TemporaryDirectory() as root: 117 | mut = join(root, "mut") 118 | archive = join(DISTDIR, name) 119 | unpack_archive(archive, mut, "zip") 120 | 121 | variant_path = join(mut, variant) 122 | if not isdir(variant_path): 123 | logging.fatal(f"path not found: {variant_path}") 124 | sys.exit(1) 125 | 126 | # write main.tf into root_module 127 | jinja = Environment(loader=FileSystemLoader(".")) 128 | template = jinja.get_template('main.tf.tpl') 129 | data = template.render( 130 | {'entry_module': mut, 'variant': variant}) 131 | 132 | with open(f'{root}/main.tf', 'w') as f: 133 | f.write(data) 134 | # always include newline at end of file 135 | f.write('\n') 136 | 137 | run_steps(f"{name} - {variant}", root) 138 | -------------------------------------------------------------------------------- /test/k3d/main.tf.tpl: -------------------------------------------------------------------------------- 1 | module "test" { 2 | source = "{{entry_module}}" 3 | 4 | configuration = { 5 | apps = { 6 | variant = "{{variant}}" 7 | } 8 | 9 | ops = {} 10 | 11 | default = {} 12 | } 13 | } 14 | --------------------------------------------------------------------------------