├── .go-version ├── tools ├── DEEPCOPY_GEN_VERSION ├── GOLANGCI_LINT_VERSION ├── GORELEASER_VERSION ├── parse-tags.sh ├── boilerplate.go.txt └── make │ ├── generate.mk │ ├── release.mk │ ├── clean.mk │ ├── lint.mk │ ├── test.mk │ ├── build.mk │ └── container.mk ├── .shellcheckrc ├── pkg ├── cmd │ ├── interpolate │ │ └── testdata │ │ │ ├── folder │ │ │ ├── ignored │ │ │ ├── inner │ │ │ │ └── inner.yaml │ │ │ ├── no-interpolation.yaml │ │ │ └── other-file.yml │ │ │ ├── results-contained │ │ │ ├── inner.yaml │ │ │ ├── no-interpolation.yaml │ │ │ └── other-file.yml │ │ │ ├── missing-env.yaml │ │ │ ├── results │ │ │ ├── no-interpolation.yaml │ │ │ ├── other-file.yml │ │ │ └── file.yaml │ │ │ ├── file.yaml │ │ │ ├── stdin │ │ │ └── output.yaml │ │ │ └── results-missing-path │ │ │ └── file.yaml │ ├── kustomize │ │ ├── testdata │ │ │ └── kustomization.yaml │ │ ├── kustomize.go │ │ └── kustomize_test.go │ ├── deploy │ │ ├── testdata │ │ │ ├── resources │ │ │ │ ├── configmap.yaml │ │ │ │ ├── secret.yaml │ │ │ │ ├── external-secret.yaml │ │ │ │ ├── store.yaml │ │ │ │ ├── cronjob.yml │ │ │ │ └── deployment.yaml │ │ │ ├── error-resources │ │ │ │ ├── configmap.yaml │ │ │ │ ├── secret.yaml │ │ │ │ ├── cronjob.yml │ │ │ │ └── deployment.yaml │ │ │ └── expectations │ │ │ │ ├── configmap.yaml │ │ │ │ ├── store.yaml │ │ │ │ ├── external-secret.yaml │ │ │ │ ├── cronjob.yaml │ │ │ │ ├── job.yaml │ │ │ │ └── deployment.yaml │ │ └── convert_inventory.go │ ├── generate │ │ ├── testdata │ │ │ └── configuration.yaml │ │ └── types.go │ ├── doc.go │ ├── root_test.go │ ├── hydrate │ │ ├── kustomizationFile.go │ │ ├── hydrate_test.go │ │ └── hydrate.go │ └── root.go ├── extensions │ ├── testdata │ │ ├── custom-pollers │ │ │ ├── secstore-no-status.yaml │ │ │ ├── extsec-no-status.yaml │ │ │ ├── secstore-ready-true.yaml │ │ │ ├── secstore-ready-false.yaml │ │ │ ├── extsec-ready-false.yaml │ │ │ ├── extsec-ready-true.yaml │ │ │ └── extsec-terminating.yaml │ │ ├── filter │ │ │ ├── secret.yaml │ │ │ ├── configmap.yaml │ │ │ ├── filtered.yaml │ │ │ ├── unfiltered.yaml │ │ │ └── deployment.yaml │ │ ├── externalsecret-mutator │ │ │ ├── configmap.yaml │ │ │ ├── wrong-resource.yaml │ │ │ ├── store.yaml │ │ │ ├── external-secret-secret-name.yaml │ │ │ ├── pod.yaml │ │ │ ├── daemonset.yaml │ │ │ ├── sts.yaml │ │ │ ├── expected-sts.yaml │ │ │ ├── deployment.yaml │ │ │ ├── expected-daemonset.yaml │ │ │ ├── expected-pod.yaml │ │ │ ├── expected-deployment.yaml │ │ │ ├── external-secret.yaml │ │ │ └── expected-external-secret.yaml │ │ ├── dependency-mutator │ │ │ ├── cm-other-namespace.yaml │ │ │ ├── configmap.yaml │ │ │ ├── secret.yaml │ │ │ ├── wrong-resource.yaml │ │ │ ├── daemonset.yaml │ │ │ ├── pod.yaml │ │ │ ├── expected-daemonset.yaml │ │ │ ├── sts.yaml │ │ │ ├── expected-sts.yaml │ │ │ ├── expected-pod.yaml │ │ │ ├── deployment.yaml │ │ │ └── expected-deployment.yaml │ │ └── deploy-mutator │ │ │ ├── wrong-resource.yaml │ │ │ ├── error-remote.yaml │ │ │ ├── pod.yaml │ │ │ ├── deployment-smart-remote.yaml │ │ │ ├── expected-pod.yaml │ │ │ ├── remote-status.yaml │ │ │ ├── expected-deployment-smart-remote.yaml │ │ │ ├── sts.yaml │ │ │ ├── expected-sts.yaml │ │ │ ├── daemonset.yaml │ │ │ ├── expected-daemonset.yaml │ │ │ ├── deployment.yaml │ │ │ ├── wrong-image.yaml │ │ │ ├── deployment-smart.yaml │ │ │ ├── expected-deployment.yaml │ │ │ └── expected-deployment-smart.yaml │ ├── deployoncefilter.go │ ├── deployoncefilter_test.go │ ├── externalsecretpoller.go │ ├── externalsecretpoller_test.go │ └── utils.go └── apis │ └── mlp.mia-platform.eu │ └── v1 │ ├── doc.go │ ├── types.go │ └── zz_generated.deepcopy.go ├── .gitleaksignore ├── tests └── e2e │ ├── testdata │ ├── kind.yaml │ ├── smart-deploy │ │ ├── stage1 │ │ │ ├── test.secret.yml │ │ │ ├── test.configmap.yml │ │ │ ├── test.service.yml │ │ │ └── test.deployment.yaml │ │ └── stage2 │ │ │ ├── test.configmap.yml │ │ │ ├── test.service.yml │ │ │ └── test.deployment.yaml │ └── apply-resources │ │ ├── literal.configmap.yaml │ │ ├── opaque.secret.yaml │ │ ├── docker.secret.yaml │ │ ├── test.cronjob.yaml │ │ └── test.deployment.yaml │ └── main_test.go ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── new-feature.yml │ ├── documentation.yml │ └── bug_report.yml ├── PULL_REQUEST_TEMPLATE.md ├── workflows │ ├── dependency-review.yaml │ └── codeql.yaml └── dependabot.yml ├── Dockerfile ├── .editorconfig ├── .markdownlint.yaml ├── examples └── example-cm-secret-config.yaml ├── docs ├── 40_hydrate.md ├── 50_interpolate.md ├── 10_overview.md ├── 30_generate.md └── 20_setup.md ├── main.go ├── .pre-commit-config.yaml ├── .devcontainer └── devcontainer.json ├── .goreleaser.yaml ├── README.md ├── .gitignore ├── CONTRIBUTING.md ├── .golangci.yaml ├── go.mod ├── Makefile └── CODE_OF_CONDUCT.md /.go-version: -------------------------------------------------------------------------------- 1 | 1.25.4 2 | -------------------------------------------------------------------------------- /tools/DEEPCOPY_GEN_VERSION: -------------------------------------------------------------------------------- 1 | v0.34.2 2 | -------------------------------------------------------------------------------- /tools/GOLANGCI_LINT_VERSION: -------------------------------------------------------------------------------- 1 | v2.6.2 2 | -------------------------------------------------------------------------------- /tools/GORELEASER_VERSION: -------------------------------------------------------------------------------- 1 | v2.13.0 2 | -------------------------------------------------------------------------------- /.shellcheckrc: -------------------------------------------------------------------------------- 1 | external-sources=true 2 | -------------------------------------------------------------------------------- /pkg/cmd/interpolate/testdata/folder/ignored: -------------------------------------------------------------------------------- 1 | {{TEST_ENV}} 2 | -------------------------------------------------------------------------------- /.gitleaksignore: -------------------------------------------------------------------------------- 1 | pkg/interpolate/interpolate_test.go:private-key:201 2 | -------------------------------------------------------------------------------- /pkg/cmd/kustomize/testdata/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: [] 4 | -------------------------------------------------------------------------------- /pkg/cmd/interpolate/testdata/folder/inner/inner.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: inner 5 | data: 6 | key: value 7 | -------------------------------------------------------------------------------- /pkg/cmd/interpolate/testdata/results-contained/inner.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: inner 5 | data: 6 | key: value 7 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/custom-pollers/secstore-no-status.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: external-secrets.io/v1beta1 2 | kind: SecretStore 3 | metadata: 4 | name: store 5 | -------------------------------------------------------------------------------- /tests/e2e/testdata/kind.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | nodes: 4 | - role: control-plane 5 | - role: worker 6 | - role: worker 7 | -------------------------------------------------------------------------------- /tests/e2e/testdata/smart-deploy/stage1/test.secret.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: test 5 | type: Opaque 6 | data: 7 | key: dmFsdWU= 8 | -------------------------------------------------------------------------------- /pkg/cmd/interpolate/testdata/missing-env.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: example 5 | type: Opaque 6 | data: 7 | key: {{MISSING_ENV}} 8 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/custom-pollers/extsec-no-status.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: external-secrets.io/v1beta1 2 | kind: ExternalSecret 3 | metadata: 4 | name: external-secret 5 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/resources/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: example 5 | data: 6 | key: value 7 | otherKey: otherValue 8 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # All files 2 | * @mia-platform/sig-cli 3 | # Actions must also be checked by security 4 | .github/workflows @mia-platform/sig-cli @mia-platform/sig-security 5 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/error-resources/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: example 5 | data: 6 | key: value 7 | otherKey: otherValue 8 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/filter/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: example 5 | annotations: 6 | mia-platform.eu/deploy: always 7 | type: Opaque 8 | -------------------------------------------------------------------------------- /tests/e2e/testdata/apply-resources/literal.configmap.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | data: 4 | key1: value 5 | key2: value2 6 | kind: ConfigMap 7 | metadata: 8 | name: literal 9 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/error-resources/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: example 5 | annotations: 6 | mia-platform.eu/deploy: once 7 | type: Opaque 8 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/externalsecret-mutator/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: example 5 | namespace: externalsecret-test 6 | data: 7 | key: value 8 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/filter/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: example 5 | labels: 6 | mia-platform.eu/deploy: once 7 | data: 8 | key: value 9 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/dependency-mutator/cm-other-namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: example 5 | namespace: other-ns 6 | data: 7 | config: othervalue 8 | -------------------------------------------------------------------------------- /tests/e2e/testdata/smart-deploy/stage1/test.configmap.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | data: 4 | request-size.conf: client_max_body_size 100m; 5 | metadata: 6 | name: api-gateway-server 7 | -------------------------------------------------------------------------------- /tests/e2e/testdata/smart-deploy/stage2/test.configmap.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | data: 4 | request-size.conf: client_max_body_size 500m; 5 | metadata: 6 | name: api-gateway-server 7 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/filter/filtered.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: filtered 5 | namespace: test 6 | annotations: 7 | mia-platform.eu/deploy: once 8 | type: Opaque 9 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/filter/unfiltered.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: unfiltered 5 | namespace: test 6 | annotations: 7 | mia-platform.eu/deploy: once 8 | type: Opaque 9 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/resources/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: example 5 | namespace: mlp-deploy-test 6 | annotations: 7 | mia-platform.eu/deploy: once 8 | type: Opaque 9 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/dependency-mutator/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: example 5 | namespace: test 6 | data: 7 | config: value 8 | binaryData: 9 | bconfig: //0= 10 | -------------------------------------------------------------------------------- /pkg/cmd/generate/testdata/configuration.yaml: -------------------------------------------------------------------------------- 1 | config-maps: 2 | - name: "literal" 3 | data: 4 | - from: "literal" 5 | key: key 6 | value: value 7 | - from: "literal" 8 | key: otherKey 9 | value: value 10 | -------------------------------------------------------------------------------- /tests/e2e/testdata/apply-resources/opaque.secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | type: Opaque 4 | metadata: 5 | annotations: 6 | mia-platform.eu/deploy: always 7 | name: opaque 8 | data: 9 | key: dmFsdWU= 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Mia-Platform Community Support 4 | url: https://github.com/mia-platform/community/discussions 5 | about: Please ask and answer questions here. 6 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/expectations/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: example 5 | namespace: mlp-deploy-test 6 | annotations: {} 7 | data: 8 | key: value 9 | otherKey: otherValue 10 | -------------------------------------------------------------------------------- /pkg/cmd/interpolate/testdata/folder/no-interpolation.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: example 5 | spec: 6 | selector: 7 | app: example 8 | ports: 9 | - port: 80 10 | targetPort: 8080 11 | -------------------------------------------------------------------------------- /pkg/cmd/interpolate/testdata/results/no-interpolation.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: example 5 | spec: 6 | selector: 7 | app: example 8 | ports: 9 | - port: 80 10 | targetPort: 8080 11 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/wrong-resource.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: example 5 | spec: 6 | selector: 7 | app: example 8 | ports: 9 | - port: 80 10 | targetPort: 8080 11 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/dependency-mutator/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: example 5 | namespace: test 6 | type: Opaque 7 | data: 8 | data: dmFsdWU= 9 | stringData: 10 | otherData: value 11 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/dependency-mutator/wrong-resource.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: example 5 | spec: 6 | selector: 7 | app: example 8 | ports: 9 | - port: 80 10 | targetPort: 8080 11 | -------------------------------------------------------------------------------- /pkg/cmd/interpolate/testdata/results-contained/no-interpolation.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: example 5 | spec: 6 | selector: 7 | app: example 8 | ports: 9 | - port: 80 10 | targetPort: 8080 11 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/externalsecret-mutator/wrong-resource.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: example 5 | spec: 6 | selector: 7 | app: example 8 | ports: 9 | - port: 80 10 | targetPort: 8080 11 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/custom-pollers/secstore-ready-true.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: external-secrets.io/v1beta1 2 | kind: SecretStore 3 | metadata: 4 | name: store 5 | status: 6 | conditions: 7 | - type: Ready 8 | status: "True" 9 | message: "custom message" 10 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/custom-pollers/secstore-ready-false.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: external-secrets.io/v1beta1 2 | kind: SecretStore 3 | metadata: 4 | name: store 5 | status: 6 | conditions: 7 | - type: Ready 8 | status: "False" 9 | message: "custom message" 10 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/custom-pollers/extsec-ready-false.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: external-secrets.io/v1beta1 2 | kind: ExternalSecret 3 | metadata: 4 | name: external-secret 5 | status: 6 | conditions: 7 | - type: Ready 8 | status: "False" 9 | message: "custom message" 10 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/custom-pollers/extsec-ready-true.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: external-secrets.io/v1beta1 2 | kind: ExternalSecret 3 | metadata: 4 | name: external-secret 5 | status: 6 | conditions: 7 | - type: Ready 8 | status: "True" 9 | message: "custom message" 10 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/custom-pollers/extsec-terminating.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: external-secrets.io/v1beta1 2 | kind: ExternalSecret 3 | metadata: 4 | name: external-secret 5 | status: 6 | conditions: 7 | - type: Deleted 8 | status: "True" 9 | message: "custom message" 10 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/error-remote.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: error 5 | labels: 6 | name: error 7 | spec: 8 | containers: 9 | - name: error 10 | image: busybox:v1.0.0 11 | resources: 12 | limits: 13 | memory: "128Mi" 14 | cpu: "500m" 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM docker.io/library/alpine:3.23.2@sha256:865b95f46d98cf867a156fe4a135ad3fe50d2056aa3f25ed31662dff6da4eb62 3 | 4 | ARG TARGETPLATFORM 5 | ARG CMD_NAME 6 | ENV COMMAND_NAME=${CMD_NAME} 7 | 8 | COPY ${TARGETPLATFORM}/${CMD_NAME} /usr/local/bin/ 9 | 10 | CMD ["/bin/sh", "-c", "${COMMAND_NAME}"] 11 | -------------------------------------------------------------------------------- /tools/parse-tags.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | set -o pipefail 4 | set -o errexit 5 | set -o nounset 6 | 7 | TAG_TO_PARSE="${1}" 8 | 9 | if [[ ${TAG_TO_PARSE} =~ ^v[0-9]+\.[0-9]\.[0-9]+$ ]]; then 10 | awk -F'.' '{ print $1, $1 "." $2, $1 "." $2 "." $3 }' <<< "${TAG_TO_PARSE//v/}" 11 | exit 0 12 | fi 13 | 14 | echo "${TAG_TO_PARSE}" 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = tab 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | max_line_length = 120 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.{yaml,yml}] 16 | indent_style = space 17 | tab_width = 2 18 | -------------------------------------------------------------------------------- /pkg/cmd/interpolate/testdata/folder/other-file.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: example 5 | data: 6 | key: "{{JSON_SINGLELINE_ENV}}" 7 | key2: {{MULTILINE_STRING}} 8 | key3: {{STRING_ESCAPED_ENV}} 9 | key4: "{{DOLLAR_ENV}}otherstring" 10 | key5: "otherstring{{MLP_SIMPLE_ENV}}" 11 | key6: {{DOLLAR_ENV}} 12 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | default: true 2 | MD003: 3 | style: atx 4 | MD010: false 5 | MD013: 6 | line_length: 120 7 | heading_line_length: 80 8 | code_blocks: false 9 | tables: false 10 | headings: true 11 | strict: false 12 | stern: false 13 | MD024: 14 | siblings_only: true 15 | MD029: 16 | style: one 17 | MD033: 18 | allowed_elements: 19 | - center 20 | - picture 21 | - source 22 | - img 23 | MD046: 24 | style: fenced 25 | -------------------------------------------------------------------------------- /tests/e2e/testdata/apply-resources/docker.secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | data: 4 | .dockerconfigjson: eyJhdXRocyI6eyJleGFtcGxlLmNvbSI6eyJ1c2VybmFtZSI6InVzZXJuYW1lIiwicGFzc3dvcmQiOiJwYXNzd29yZCIsImVtYWlsIjoiZW1haWxAZXhhbXBsZS5jb20iLCJhdXRoIjoiZFhObGNtNWhiV1U2Y0dGemMzZHZjbVE9In19fQ== 5 | metadata: 6 | annotations: 7 | mia-platform.eu/deploy: once 8 | creationTimestamp: null 9 | name: docker 10 | type: kubernetes.io/dockerconfigjson 11 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/resources/external-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: external-secrets.io/v1beta1 2 | kind: ExternalSecret 3 | metadata: 4 | name: external-secret 5 | namespace: mlp-deploy-test 6 | spec: 7 | refreshInterval: 1h 8 | secretStoreRef: 9 | name: secret-store 10 | target: 11 | creationPolicy: Owner 12 | data: 13 | - secretKey: secret-key 14 | remoteRef: 15 | key: provider-key 16 | version: provider-key-version 17 | property: provider-key-property 18 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/filter/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: example 5 | annotations: 6 | mia-platform.eu/deploy: once 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: example 11 | template: 12 | metadata: 13 | labels: 14 | app: example 15 | spec: 16 | containers: 17 | - name: example 18 | image: busybox 19 | resources: 20 | limits: 21 | memory: "128Mi" 22 | cpu: "500m" 23 | -------------------------------------------------------------------------------- /tests/e2e/testdata/apply-resources/test.cronjob.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: CronJob 3 | metadata: 4 | name: test 5 | annotations: 6 | mia-platform.eu/autocreate: 'true' 7 | spec: 8 | schedule: "*/5 * * * *" 9 | jobTemplate: 10 | spec: 11 | template: 12 | spec: 13 | containers: 14 | - name: hello 15 | image: busybox 16 | args: 17 | - /bin/sh 18 | - -c 19 | - date; sleep 5 20 | restartPolicy: OnFailure 21 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/resources/store.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: external-secrets.io/v1beta1 2 | kind: SecretStore 3 | metadata: 4 | name: secret-store 5 | namespace: mlp-deploy-test 6 | spec: 7 | provider: 8 | aws: 9 | service: SecretsManager 10 | region: us-east-1 11 | auth: 12 | secretRef: 13 | accessKeyIDSecretRef: 14 | name: awssm-secret 15 | key: access-key 16 | secretAccessKeySecretRef: 17 | name: awssm-secret 18 | key: secret-access-key 19 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/externalsecret-mutator/store.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: external-secrets.io/v1beta1 2 | kind: SecretStore 3 | metadata: 4 | name: secret-store 5 | namespace: externalsecret-test 6 | spec: 7 | provider: 8 | aws: 9 | service: SecretsManager 10 | region: us-east-1 11 | auth: 12 | secretRef: 13 | accessKeyIDSecretRef: 14 | name: awssm-secret 15 | key: access-key 16 | secretAccessKeySecretRef: 17 | name: awssm-secret 18 | key: secret-access-key 19 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/expectations/store.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: external-secrets.io/v1beta1 2 | kind: SecretStore 3 | metadata: 4 | name: secret-store 5 | namespace: mlp-deploy-test 6 | annotations: {} 7 | spec: 8 | provider: 9 | aws: 10 | service: SecretsManager 11 | region: us-east-1 12 | auth: 13 | secretRef: 14 | accessKeyIDSecretRef: 15 | name: awssm-secret 16 | key: access-key 17 | secretAccessKeySecretRef: 18 | name: awssm-secret 19 | key: secret-access-key 20 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/externalsecret-mutator/external-secret-secret-name.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: external-secrets.io/v1beta1 2 | kind: ExternalSecret 3 | metadata: 4 | name: external-secret2 5 | namespace: externalsecret-test 6 | spec: 7 | refreshInterval: 1h 8 | secretStoreRef: 9 | name: secret-store 10 | kind: SecretStore 11 | target: 12 | name: custom-secret-name 13 | creationPolicy: Owner 14 | data: 15 | - secretKey: secret-key 16 | remoteRef: 17 | key: provider-key 18 | version: provider-key-version 19 | property: provider-key-property 20 | -------------------------------------------------------------------------------- /tests/e2e/testdata/apply-resources/test.deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: test 5 | labels: 6 | app: test 7 | annotations: 8 | key: value 9 | spec: 10 | replicas: 2 11 | selector: 12 | matchLabels: 13 | app: test 14 | strategy: {} 15 | template: 16 | metadata: 17 | labels: 18 | app: test 19 | spec: 20 | containers: 21 | - image: nginx 22 | name: nginx 23 | resources: {} 24 | volumes: 25 | - name: configmap 26 | configMap: 27 | name: literal 28 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/error-resources/cronjob.yml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: CronJob 3 | metadata: 4 | name: example 5 | spec: 6 | schedule: "*/5 * * * *" 7 | jobTemplate: 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: example 13 | image: busybox 14 | args: 15 | - /bin/sh 16 | - -c 17 | - date; sleep 120 18 | env: 19 | - name: ENV 20 | valueFrom: 21 | configMapKeyRef: 22 | key: key 23 | name: example 24 | restartPolicy: OnFailure 25 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/dependency-mutator/daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | name: example 10 | template: 11 | metadata: 12 | annotations: 13 | existing: annotation 14 | labels: 15 | name: example 16 | spec: 17 | containers: 18 | - name: example 19 | image: busybox 20 | volumes: 21 | - name: varlog 22 | hostPath: 23 | path: /var/log 24 | - name: example 25 | secret: 26 | secretName: example 27 | optional: true 28 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/error-resources/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: example 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: example 9 | template: 10 | metadata: 11 | annotations: 12 | mia-platform.eu/dependencies-checksum: predefined-value 13 | labels: 14 | app: example 15 | spec: 16 | containers: 17 | - name: example 18 | image: nginx:latest 19 | resources: 20 | limits: 21 | memory: "128Mi" 22 | cpu: "500m" 23 | volumes: 24 | - name: example 25 | configMap: 26 | name: example 27 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/dependency-mutator/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: example 5 | namespace: test 6 | labels: 7 | name: example 8 | spec: 9 | initContainers: 10 | - name: init 11 | image: busybox 12 | env: 13 | - name: ENV 14 | valueFrom: 15 | secretKeyRef: 16 | key: otherData 17 | name: example 18 | - name: ENV2 19 | valueFrom: 20 | configMapKeyRef: 21 | key: example 22 | name: missing 23 | containers: 24 | - name: example 25 | image: busybox 26 | resources: 27 | limits: 28 | memory: "128Mi" 29 | cpu: "500m" 30 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: example 5 | namespace: test 6 | labels: 7 | name: example 8 | spec: 9 | initContainers: 10 | - name: init 11 | image: busybox:v1.0.0 12 | env: 13 | - name: ENV 14 | valueFrom: 15 | secretKeyRef: 16 | key: otherkey 17 | name: example 18 | - name: ENV2 19 | valueFrom: 20 | configMapKeyRef: 21 | key: example 22 | name: missing 23 | containers: 24 | - name: example 25 | image: busybox 26 | resources: 27 | limits: 28 | memory: "128Mi" 29 | cpu: "500m" 30 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/deployment-smart-remote.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | template: 11 | metadata: 12 | labels: 13 | app: example 14 | spec: 15 | containers: 16 | - name: example 17 | image: busybox:1.35.0@sha256:5be7104a4306abe768359a5379e6050ef69a29e9a5f99fcf7f46d5f7e9ba29a2 18 | resources: 19 | limits: 20 | memory: "128Mi" 21 | cpu: "500m" 22 | volumes: 23 | - name: volume 24 | configMap: 25 | name: example 26 | -------------------------------------------------------------------------------- /tools/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/resources/cronjob.yml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: CronJob 3 | metadata: 4 | name: example 5 | annotations: 6 | mia-platform.eu/autocreate: 'true' 7 | spec: 8 | schedule: "*/5 * * * *" 9 | jobTemplate: 10 | spec: 11 | template: 12 | spec: 13 | containers: 14 | - name: example 15 | image: busybox 16 | args: 17 | - /bin/sh 18 | - -c 19 | - date; sleep 120 20 | env: 21 | - name: ENV 22 | valueFrom: 23 | configMapKeyRef: 24 | key: key 25 | name: example 26 | restartPolicy: OnFailure 27 | -------------------------------------------------------------------------------- /tests/e2e/testdata/smart-deploy/stage1/test.service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | mia-platform.eu/version: 8.4.0 6 | labels: 7 | app: test 8 | app.kubernetes.io/component: custom 9 | app.kubernetes.io/managed-by: mia-platform 10 | app.kubernetes.io/name: test 11 | app.kubernetes.io/part-of: test-mlp-kustomize-2 12 | app.kubernetes.io/version: latest 13 | mia-platform.eu/stage: 'DEV' 14 | mia-platform.eu/tenant: kustomize-tenant 15 | name: test 16 | spec: 17 | ports: 18 | - name: http 19 | nodePort: null 20 | port: 80 21 | protocol: TCP 22 | targetPort: 3000 23 | selector: 24 | app: test 25 | type: ClusterIP 26 | -------------------------------------------------------------------------------- /tests/e2e/testdata/smart-deploy/stage2/test.service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | mia-platform.eu/version: 8.4.0 6 | labels: 7 | app: test 8 | app.kubernetes.io/component: custom 9 | app.kubernetes.io/managed-by: mia-platform 10 | app.kubernetes.io/name: test 11 | app.kubernetes.io/part-of: test-mlp-kustomize-2 12 | app.kubernetes.io/version: latest 13 | mia-platform.eu/stage: 'DEV' 14 | mia-platform.eu/tenant: kustomize-tenant 15 | name: test 16 | spec: 17 | ports: 18 | - name: http 19 | nodePort: null 20 | port: 80 21 | protocol: TCP 22 | targetPort: 3000 23 | selector: 24 | app: test 25 | type: ClusterIP 26 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/expectations/external-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: external-secrets.io/v1beta1 2 | kind: ExternalSecret 3 | metadata: 4 | name: external-secret 5 | namespace: mlp-deploy-test 6 | annotations: 7 | config.kubernetes.io/depends-on: external-secrets.io/namespaces/mlp-deploy-test/SecretStore/secret-store 8 | mia-platform.eu/deploy-checksum: a2d1ace0489d09c0ca26a1ab8a8bc9b11e4365cb4f904c434565a59119f3eb15 9 | spec: 10 | refreshInterval: 1h 11 | secretStoreRef: 12 | name: secret-store 13 | target: 14 | creationPolicy: Owner 15 | data: 16 | - secretKey: secret-key 17 | remoteRef: 18 | key: provider-key 19 | version: provider-key-version 20 | property: provider-key-property 21 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/externalsecret-mutator/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: example 5 | namespace: externalsecret-test 6 | labels: 7 | name: example 8 | spec: 9 | initContainers: 10 | - name: init 11 | image: busybox 12 | env: 13 | - name: ENV 14 | valueFrom: 15 | secretKeyRef: 16 | key: otherkey 17 | name: external-secret 18 | containers: 19 | - name: example 20 | image: busybox 21 | resources: 22 | limits: 23 | memory: "128Mi" 24 | cpu: "500m" 25 | volumes: 26 | - name: volume 27 | secret: 28 | secretName: external-secret 29 | - name: volume 30 | secret: 31 | secretName: custom-secret-name 32 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/expectations/cronjob.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: CronJob 3 | metadata: 4 | name: example 5 | namespace: mlp-deploy-test 6 | annotations: 7 | mia-platform.eu/autocreate: 'true' 8 | spec: 9 | jobTemplate: 10 | spec: 11 | template: 12 | spec: 13 | containers: 14 | - args: 15 | - /bin/sh 16 | - '-c' 17 | - date; sleep 120 18 | env: 19 | - name: ENV 20 | valueFrom: 21 | configMapKeyRef: 22 | key: key 23 | name: example 24 | image: busybox 25 | name: example 26 | restartPolicy: OnFailure 27 | schedule: '*/5 * * * *' 28 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/expectations/job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: example 5 | namespace: mlp-deploy-test 6 | annotations: 7 | cronjob.kubernetes.io/instantiate: manual 8 | creationTimestamp: null 9 | spec: 10 | template: 11 | metadata: 12 | creationTimestamp: null 13 | spec: 14 | containers: 15 | - args: 16 | - /bin/sh 17 | - '-c' 18 | - date; sleep 120 19 | env: 20 | - name: ENV 21 | valueFrom: 22 | configMapKeyRef: 23 | key: key 24 | name: example 25 | image: busybox 26 | name: example 27 | resources: {} 28 | restartPolicy: OnFailure 29 | status: {} 30 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/expected-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: example 5 | namespace: test 6 | labels: 7 | name: example 8 | annotations: 9 | mia-platform.eu/deploy-checksum: test-identifier 10 | spec: 11 | initContainers: 12 | - name: init 13 | image: busybox:v1.0.0 14 | env: 15 | - name: ENV 16 | valueFrom: 17 | secretKeyRef: 18 | key: otherkey 19 | name: example 20 | - name: ENV2 21 | valueFrom: 22 | configMapKeyRef: 23 | key: example 24 | name: missing 25 | containers: 26 | - name: example 27 | image: busybox 28 | resources: 29 | limits: 30 | memory: "128Mi" 31 | cpu: "500m" 32 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/remote-status.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | template: 11 | metadata: 12 | annotations: 13 | mia-platform.eu/deploy-checksum: remote-identifier 14 | labels: 15 | app: example 16 | spec: 17 | containers: 18 | - name: example 19 | image: busybox:1.35.0@sha256:5be7104a4306abe768359a5379e6050ef69a29e9a5f99fcf7f46d5f7e9ba29a2 20 | resources: 21 | limits: 22 | memory: "128Mi" 23 | cpu: "500m" 24 | volumes: 25 | - name: volume 26 | configMap: 27 | name: example 28 | -------------------------------------------------------------------------------- /examples/example-cm-secret-config.yaml: -------------------------------------------------------------------------------- 1 | config-map: 2 | - name: "pippo" 3 | data: 4 | - from: "literal|file" 5 | file: ./path 6 | key: key 7 | value: value 8 | - from: literal 9 | key: key1 10 | value: value1 11 | - from: literal 12 | key: key2 13 | value: {{PIPPO}} 14 | 15 | secrets: 16 | - name: "pippo" 17 | when: "always|once" 18 | tls: 19 | cert: path 20 | key: path 21 | docker: 22 | username: {{DOCKER_USERNAME}} 23 | password: {{DOCKER_USERNAME}} 24 | email: {{DOCKER_USERNAME}} 25 | server: {{DOCKER_USERNAME}} 26 | data: 27 | - from: "literal|file" 28 | file: ./path 29 | key: key 30 | value: {{MLP}} 31 | -------------------------------------------------------------------------------- /pkg/cmd/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | // Package cmd provides all the functions describing mlp's commands 17 | package cmd 18 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/externalsecret-mutator/daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: example 5 | namespace: externalsecret-test 6 | spec: 7 | selector: 8 | matchLabels: 9 | name: example 10 | template: 11 | metadata: 12 | annotations: 13 | existing: annotation 14 | labels: 15 | name: example 16 | spec: 17 | initContainers: 18 | - name: example 19 | image: busybox 20 | containers: 21 | - name: example 22 | image: busybox:v1.0.0 23 | volumes: 24 | - name: varlog 25 | hostPath: 26 | path: /var/log 27 | - name: example 28 | secret: 29 | secretName: custom-secret-name 30 | optional: true 31 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/expected-deployment-smart-remote.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | template: 11 | metadata: 12 | annotations: 13 | mia-platform.eu/deploy-checksum: remote-identifier 14 | labels: 15 | app: example 16 | spec: 17 | containers: 18 | - name: example 19 | image: busybox:1.35.0@sha256:5be7104a4306abe768359a5379e6050ef69a29e9a5f99fcf7f46d5f7e9ba29a2 20 | resources: 21 | limits: 22 | memory: "128Mi" 23 | cpu: "500m" 24 | volumes: 25 | - name: volume 26 | configMap: 27 | name: example 28 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/dependency-mutator/expected-daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | name: example 10 | template: 11 | metadata: 12 | annotations: 13 | existing: annotation 14 | mia-platform.eu/dependencies-checksum: 0a7f9b4a49ce906ecfaba03aa45ef6512ecb33639d434107b43473b16d0c1afb 15 | labels: 16 | name: example 17 | spec: 18 | containers: 19 | - name: example 20 | image: busybox 21 | volumes: 22 | - name: varlog 23 | hostPath: 24 | path: /var/log 25 | - name: example 26 | secret: 27 | secretName: example 28 | optional: true 29 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/sts.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | serviceName: example 11 | replicas: 2 12 | template: 13 | metadata: 14 | annotations: 15 | existing: annotation 16 | labels: 17 | app: example 18 | spec: 19 | containers: 20 | - name: example 21 | image: busybox 22 | volumes: 23 | - name: volume 24 | secret: 25 | secretName: missing 26 | volumeClaimTemplates: 27 | - metadata: 28 | name: www 29 | spec: 30 | accessModes: 31 | - ReadWriteOnce 32 | resources: 33 | requests: 34 | storage: 1Gi 35 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/dependency-mutator/sts.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | serviceName: example 11 | replicas: 2 12 | template: 13 | metadata: 14 | annotations: 15 | existing: annotation 16 | labels: 17 | app: example 18 | spec: 19 | containers: 20 | - name: example 21 | image: busybox 22 | volumes: 23 | - name: volume 24 | secret: 25 | secretName: missing 26 | volumeClaimTemplates: 27 | - metadata: 28 | name: www 29 | spec: 30 | accessModes: 31 | - ReadWriteOnce 32 | resources: 33 | requests: 34 | storage: 1Gi 35 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/expected-sts.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | serviceName: example 11 | replicas: 2 12 | template: 13 | metadata: 14 | annotations: 15 | existing: annotation 16 | labels: 17 | app: example 18 | spec: 19 | containers: 20 | - name: example 21 | image: busybox 22 | volumes: 23 | - name: volume 24 | secret: 25 | secretName: missing 26 | volumeClaimTemplates: 27 | - metadata: 28 | name: www 29 | spec: 30 | accessModes: 31 | - ReadWriteOnce 32 | resources: 33 | requests: 34 | storage: 1Gi 35 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/dependency-mutator/expected-sts.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | serviceName: example 11 | replicas: 2 12 | template: 13 | metadata: 14 | annotations: 15 | existing: annotation 16 | labels: 17 | app: example 18 | spec: 19 | containers: 20 | - name: example 21 | image: busybox 22 | volumes: 23 | - name: volume 24 | secret: 25 | secretName: missing 26 | volumeClaimTemplates: 27 | - metadata: 28 | name: www 29 | spec: 30 | accessModes: 31 | - ReadWriteOnce 32 | resources: 33 | requests: 34 | storage: 1Gi 35 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/dependency-mutator/expected-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: example 5 | namespace: test 6 | labels: 7 | name: example 8 | annotations: 9 | mia-platform.eu/dependencies-checksum: ec4cee1e7cd6503727710ff3f36d85576177196688ea2871efae51f9de3c65ea 10 | spec: 11 | initContainers: 12 | - name: init 13 | image: busybox 14 | env: 15 | - name: ENV 16 | valueFrom: 17 | secretKeyRef: 18 | key: otherData 19 | name: example 20 | - name: ENV2 21 | valueFrom: 22 | configMapKeyRef: 23 | key: example 24 | name: missing 25 | containers: 26 | - name: example 27 | image: busybox 28 | resources: 29 | limits: 30 | memory: "128Mi" 31 | cpu: "500m" 32 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | name: example 10 | template: 11 | metadata: 12 | annotations: 13 | existing: annotation 14 | labels: 15 | name: example 16 | spec: 17 | initContainers: 18 | - name: example 19 | image: busybox@sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7 20 | containers: 21 | - name: example 22 | image: busybox:v1.0.0 23 | volumes: 24 | - name: varlog 25 | hostPath: 26 | path: /var/log 27 | - name: example 28 | secret: 29 | secretName: example 30 | optional: true 31 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/externalsecret-mutator/sts.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: example 5 | namespace: externalsecret-test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | serviceName: example 11 | replicas: 2 12 | template: 13 | metadata: 14 | annotations: 15 | existing: annotation 16 | labels: 17 | app: example 18 | spec: 19 | containers: 20 | - name: example 21 | image: busybox 22 | volumes: 23 | - name: volume 24 | secret: 25 | secretName: missing 26 | volumeClaimTemplates: 27 | - metadata: 28 | name: www 29 | spec: 30 | accessModes: 31 | - ReadWriteOnce 32 | resources: 33 | requests: 34 | storage: 1Gi 35 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/expected-daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | name: example 10 | template: 11 | metadata: 12 | annotations: 13 | existing: annotation 14 | labels: 15 | name: example 16 | spec: 17 | initContainers: 18 | - name: example 19 | image: busybox@sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7 20 | containers: 21 | - name: example 22 | image: busybox:v1.0.0 23 | volumes: 24 | - name: varlog 25 | hostPath: 26 | path: /var/log 27 | - name: example 28 | secret: 29 | secretName: example 30 | optional: true 31 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/externalsecret-mutator/expected-sts.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: example 5 | namespace: externalsecret-test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | serviceName: example 11 | replicas: 2 12 | template: 13 | metadata: 14 | annotations: 15 | existing: annotation 16 | labels: 17 | app: example 18 | spec: 19 | containers: 20 | - name: example 21 | image: busybox 22 | volumes: 23 | - name: volume 24 | secret: 25 | secretName: missing 26 | volumeClaimTemplates: 27 | - metadata: 28 | name: www 29 | spec: 30 | accessModes: 31 | - ReadWriteOnce 32 | resources: 33 | requests: 34 | storage: 1Gi 35 | -------------------------------------------------------------------------------- /tests/e2e/testdata/smart-deploy/stage2/test.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: nginx 22 | name: nginx 23 | resources: {} 24 | volumeMounts: 25 | - mountPath: /etc/nginx/conf.d 26 | name: api-gateway-server 27 | readOnly: true 28 | volumes: 29 | - configMap: 30 | defaultMode: 420 31 | name: api-gateway-server 32 | name: api-gateway-server 33 | status: {} 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | ## What this PR is for? 9 | 10 | 13 | 14 | **Which issue(s) this PR fixes:** 15 | 19 | - Fixes # 20 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/resources/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: example 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: example 9 | template: 10 | metadata: 11 | annotations: 12 | mia-platform.eu/dependencies-checksum: predefined-value 13 | labels: 14 | app: example 15 | spec: 16 | containers: 17 | - name: example 18 | image: nginx:latest 19 | resources: 20 | limits: 21 | memory: "128Mi" 22 | cpu: "500m" 23 | env: 24 | - name: ENV 25 | valueFrom: 26 | secretKeyRef: 27 | key: secret-key 28 | name: external-secret 29 | volumes: 30 | - name: example 31 | configMap: 32 | name: example 33 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/dependency-mutator/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | template: 11 | metadata: 12 | labels: 13 | app: example 14 | spec: 15 | initContainers: 16 | - name: example 17 | image: busybox 18 | env: 19 | - name: ENV 20 | valueFrom: 21 | secretKeyRef: 22 | key: data 23 | name: example 24 | containers: 25 | - name: example 26 | image: busybox 27 | resources: 28 | limits: 29 | memory: "128Mi" 30 | cpu: "500m" 31 | volumes: 32 | - name: volume 33 | configMap: 34 | name: example 35 | -------------------------------------------------------------------------------- /pkg/apis/mlp.mia-platform.eu/v1/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | // Package v1 implements the v1 apiVersion of mlp generate configuration 17 | // 18 | // +k8s:deepcopy-gen=package 19 | package v1 20 | -------------------------------------------------------------------------------- /tools/make/generate.mk: -------------------------------------------------------------------------------- 1 | # Copyright Mia srl 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ##@ Deepcopy Goals 17 | 18 | .PHONY: generate-deps 19 | generate-deps: 20 | 21 | .PHONY: generate 22 | generate: generate-deps 23 | go generate -x -ldflags "$(GO_LDFLAGS)" ./... 24 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | template: 11 | metadata: 12 | labels: 13 | app: example 14 | spec: 15 | initContainers: 16 | - name: example 17 | image: busybox:v1.0.0 18 | env: 19 | - name: ENV 20 | valueFrom: 21 | secretKeyRef: 22 | key: key 23 | name: example 24 | key: key 25 | containers: 26 | - name: example 27 | image: busybox 28 | resources: 29 | limits: 30 | memory: "128Mi" 31 | cpu: "500m" 32 | volumes: 33 | - name: volume 34 | configMap: 35 | name: example 36 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/externalsecret-mutator/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: example 5 | namespace: externalsecret-test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | template: 11 | metadata: 12 | labels: 13 | app: example 14 | spec: 15 | initContainers: 16 | - name: example 17 | image: busybox 18 | env: 19 | - name: ENV 20 | valueFrom: 21 | secretKeyRef: 22 | key: data 23 | name: external-secret 24 | containers: 25 | - name: example 26 | image: busybox 27 | resources: 28 | limits: 29 | memory: "128Mi" 30 | cpu: "500m" 31 | volumes: 32 | - name: volume 33 | configMap: 34 | name: example 35 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/wrong-image.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | serviceName: example 11 | replicas: 2 12 | template: 13 | metadata: 14 | annotations: 15 | existing: annotation 16 | labels: 17 | app: example 18 | spec: 19 | containers: 20 | - name: example 21 | image: busybox:sha256:5be7104a4306abe768359a5379e6050ef69a29e9a5f99fcf7f46d5f7e9ba29a2 22 | volumes: 23 | - name: volume 24 | secret: 25 | secretName: missing 26 | volumeClaimTemplates: 27 | - metadata: 28 | name: www 29 | spec: 30 | accessModes: 31 | - ReadWriteOnce 32 | resources: 33 | requests: 34 | storage: 1Gi 35 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yaml: -------------------------------------------------------------------------------- 1 | name: Dependency Review 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - "**/*.md" 8 | - docs/** 9 | - examples/** 10 | 11 | permissions: {} 12 | 13 | jobs: 14 | dependency-review: 15 | name: Dependencies Review 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | pull-requests: write 20 | steps: 21 | - name: Checkout Repository 22 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 23 | with: 24 | show-progress: false 25 | - name: Dependency Review 26 | uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 27 | with: 28 | fail-on-severity: high 29 | fail-on-scopes: development,runtime,unknown 30 | comment-summary-in-pr: on-failure 31 | -------------------------------------------------------------------------------- /pkg/cmd/interpolate/testdata/results/other-file.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: example 5 | data: 6 | key: "{\"type\":\"type\",\"project_id\":\"id\",\"private_key_id\":\"key\",\"private_key\":\"-----BEGIN CERTIFICATE-----\nXXXXXXXXXXXXXXXXXXXXXXXXX\nYYYYYYYYYYYYYYY/4YYYYYYYYY\n-----END CERTIFICATE-----\n\",\"client_email\":\"email@example.com\",\"client_id\":\"client-id\",\"auth_uri\":\"https://example.com/auth\",\"token_uri\":\"https://example.com/token\",\"auth_provider_x509_cert_url\":\"https://example.com/certs\",\"client_x509_cert_url\":\"https://example.com/certs/fooo%40bar\"}" 7 | key2: -----BEGIN CERTIFICATE-----\nXXXXXXXXXXXXXXXXXXXXXXXXX\nYYYYYYYYYYYYYYY/4YYYYYYYYY\nZZZZZZZZZZZZZZZZZZZZZZZZZZ\n-----END CERTIFICATE----- 8 | key3: env\\first\line 9 | key4: "$contains$dollars$otherstring" 10 | key5: "otherstringtest" 11 | key6: $contains$dollars$ 12 | -------------------------------------------------------------------------------- /tests/e2e/testdata/smart-deploy/stage1/test.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 | annotations: 20 | mia-platform.eu/deploy-checksum: "" 21 | spec: 22 | containers: 23 | - image: nginx 24 | name: nginx 25 | resources: {} 26 | volumeMounts: 27 | - mountPath: /etc/nginx/conf.d 28 | name: api-gateway-server 29 | readOnly: true 30 | volumes: 31 | - configMap: 32 | defaultMode: 420 33 | name: api-gateway-server 34 | name: api-gateway-server 35 | status: {} 36 | -------------------------------------------------------------------------------- /pkg/cmd/interpolate/testdata/results-contained/other-file.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: example 5 | data: 6 | key: "{\"type\":\"type\",\"project_id\":\"id\",\"private_key_id\":\"key\",\"private_key\":\"-----BEGIN CERTIFICATE-----\nXXXXXXXXXXXXXXXXXXXXXXXXX\nYYYYYYYYYYYYYYY/4YYYYYYYYY\n-----END CERTIFICATE-----\n\",\"client_email\":\"email@example.com\",\"client_id\":\"client-id\",\"auth_uri\":\"https://example.com/auth\",\"token_uri\":\"https://example.com/token\",\"auth_provider_x509_cert_url\":\"https://example.com/certs\",\"client_x509_cert_url\":\"https://example.com/certs/fooo%40bar\"}" 7 | key2: -----BEGIN CERTIFICATE-----\nXXXXXXXXXXXXXXXXXXXXXXXXX\nYYYYYYYYYYYYYYY/4YYYYYYYYY\nZZZZZZZZZZZZZZZZZZZZZZZZZZ\n-----END CERTIFICATE----- 8 | key3: env\\first\line 9 | key4: "$contains$dollars$otherstring" 10 | key5: "otherstringtest" 11 | key6: $contains$dollars$ 12 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/externalsecret-mutator/expected-daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: example 5 | namespace: externalsecret-test 6 | annotations: 7 | config.kubernetes.io/depends-on: external-secrets.io/namespaces/externalsecret-test/ExternalSecret/external-secret2 8 | spec: 9 | selector: 10 | matchLabels: 11 | name: example 12 | template: 13 | metadata: 14 | annotations: 15 | existing: annotation 16 | labels: 17 | name: example 18 | spec: 19 | initContainers: 20 | - name: example 21 | image: busybox 22 | containers: 23 | - name: example 24 | image: busybox:v1.0.0 25 | volumes: 26 | - name: varlog 27 | hostPath: 28 | path: /var/log 29 | - name: example 30 | secret: 31 | secretName: custom-secret-name 32 | optional: true 33 | -------------------------------------------------------------------------------- /docs/40_hydrate.md: -------------------------------------------------------------------------------- 1 | # Hydration Logic 2 | 3 | The `hydrate` subcommand is an helper to fill kustomization configuration files with resources and patches. 4 | 5 | For doing so without launching multiple commands with different parameters, and leaving the user being able to add 6 | specific patches that needs custom targeting will only add files that conform to specific regex and we will skip files 7 | that are already present in the relative section in the file. 8 | 9 | The first regex will match any file that ends with `.patch.yaml` or `.patch.yml` and the files `patch.yaml` 10 | and `patch.yml`. These files will be assumed that they contains a strategic-merge-style patch and they will be 11 | added to the `patches` section of the kustomize file. 12 | 13 | The other regex will match every file that has the `yaml` or `yml` extensions that has not been matched in the previous 14 | regex. 15 | These files will be added to the `resources` section. 16 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/deployment-smart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | template: 11 | metadata: 12 | labels: 13 | app: example 14 | spec: 15 | initContainers: 16 | - name: example 17 | image: busybox:stable 18 | env: 19 | - name: ENV 20 | valueFrom: 21 | secretKeyRef: 22 | key: key 23 | name: example 24 | key: key 25 | containers: 26 | - name: example 27 | image: busybox:1.35.0@sha256:5be7104a4306abe768359a5379e6050ef69a29e9a5f99fcf7f46d5f7e9ba29a2 28 | resources: 29 | limits: 30 | memory: "128Mi" 31 | cpu: "500m" 32 | volumes: 33 | - name: volume 34 | configMap: 35 | name: example 36 | -------------------------------------------------------------------------------- /pkg/cmd/interpolate/testdata/file.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: test 5 | namespace: {{SIMPLE_ENV}} 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: test 10 | template: 11 | replicas: {{NUMBER_ENV}} 12 | metadata: 13 | labels: 14 | app: test 15 | spec: 16 | containers: 17 | - name: test 18 | image: nginx:latest 19 | env: 20 | - name: env1 21 | value: '{{MULTILINE_STRING_ESCAPED_ENV}}' 22 | - name: env2 23 | value: '{{SPECIAL_JSON_ENV}}' 24 | - name: env3 25 | value: "{{JSON_MULTILINE_ENV}}" 26 | - name: env4 27 | value: "abc{{JSON_ESCAPED_ENV}}def" 28 | - name: env5 29 | value: '{{HTML}}' 30 | - name: env6 31 | value: "{{NUMBER_ENV}}" 32 | resources: 33 | limits: 34 | memory: "128Mi" 35 | cpu: "500m" 36 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/expected-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | template: 11 | metadata: 12 | annotations: 13 | mia-platform.eu/deploy-checksum: test-identifier 14 | labels: 15 | app: example 16 | spec: 17 | initContainers: 18 | - name: example 19 | image: busybox:v1.0.0 20 | env: 21 | - name: ENV 22 | valueFrom: 23 | secretKeyRef: 24 | key: key 25 | name: example 26 | key: key 27 | containers: 28 | - name: example 29 | image: busybox 30 | resources: 31 | limits: 32 | memory: "128Mi" 33 | cpu: "500m" 34 | volumes: 35 | - name: volume 36 | configMap: 37 | name: example 38 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/externalsecret-mutator/expected-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: example 5 | namespace: externalsecret-test 6 | annotations: 7 | config.kubernetes.io/depends-on: external-secrets.io/namespaces/externalsecret-test/ExternalSecret/external-secret,external-secrets.io/namespaces/externalsecret-test/ExternalSecret/external-secret2 8 | labels: 9 | name: example 10 | spec: 11 | initContainers: 12 | - name: init 13 | image: busybox 14 | env: 15 | - name: ENV 16 | valueFrom: 17 | secretKeyRef: 18 | key: otherkey 19 | name: external-secret 20 | containers: 21 | - name: example 22 | image: busybox 23 | resources: 24 | limits: 25 | memory: "128Mi" 26 | cpu: "500m" 27 | volumes: 28 | - name: volume 29 | secret: 30 | secretName: external-secret 31 | - name: volume 32 | secret: 33 | secretName: custom-secret-name 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new-feature.yml: -------------------------------------------------------------------------------- 1 | name: New Feature 2 | description: Describe a new feature that you want implemented 3 | title: "[Feature]: " 4 | labels: 5 | - enhancement 6 | - needs triage 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: "## Thank you for contributing to our project!" 11 | - type: markdown 12 | attributes: 13 | value: Thanks for taking the time to fill out this feature request. 14 | - id: new-feature 15 | type: textarea 16 | attributes: 17 | label: Describe the feature 18 | description: | 19 | Please describe what you want to be implemented and why. 20 | validations: 21 | required: true 22 | - id: code-of-conduct 23 | type: checkboxes 24 | attributes: 25 | label: Code of Conduct 26 | description: By submitting this feature, you agree to follow our [Code of Conduct](./CODE_OF_CONDUCT.md) 27 | options: 28 | - label: I agree to follow this project’s Code of Conduct 29 | required: true 30 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/dependency-mutator/expected-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | template: 11 | metadata: 12 | annotations: 13 | mia-platform.eu/dependencies-checksum: e6639472ab29288cafccc49c310dcf7b21109602c2db25e34b10de1041389043 14 | labels: 15 | app: example 16 | spec: 17 | initContainers: 18 | - name: example 19 | image: busybox 20 | env: 21 | - name: ENV 22 | valueFrom: 23 | secretKeyRef: 24 | key: data 25 | name: example 26 | containers: 27 | - name: example 28 | image: busybox 29 | resources: 30 | limits: 31 | memory: "128Mi" 32 | cpu: "500m" 33 | volumes: 34 | - name: volume 35 | configMap: 36 | name: example 37 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/externalsecret-mutator/expected-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: example 5 | namespace: externalsecret-test 6 | annotations: 7 | config.kubernetes.io/depends-on: external-secrets.io/namespaces/externalsecret-test/ExternalSecret/external-secret 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: example 12 | template: 13 | metadata: 14 | labels: 15 | app: example 16 | spec: 17 | initContainers: 18 | - name: example 19 | image: busybox 20 | env: 21 | - name: ENV 22 | valueFrom: 23 | secretKeyRef: 24 | key: data 25 | name: external-secret 26 | containers: 27 | - name: example 28 | image: busybox 29 | resources: 30 | limits: 31 | memory: "128Mi" 32 | cpu: "500m" 33 | volumes: 34 | - name: volume 35 | configMap: 36 | name: example 37 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | "os" 20 | 21 | "github.com/mia-platform/mlp/v2/pkg/cmd" 22 | 23 | // Import to initialize client auth plugins. 24 | _ "k8s.io/client-go/plugin/pkg/client/auth" 25 | ) 26 | 27 | func main() { 28 | rootCmd := cmd.NewRootCommand() 29 | if err := rootCmd.Execute(); err != nil { 30 | os.Exit(1) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/deploy-mutator/expected-deployment-smart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: example 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: example 10 | template: 11 | metadata: 12 | annotations: 13 | mia-platform.eu/deploy-checksum: test-identifier 14 | labels: 15 | app: example 16 | spec: 17 | initContainers: 18 | - name: example 19 | image: busybox:stable 20 | env: 21 | - name: ENV 22 | valueFrom: 23 | secretKeyRef: 24 | key: key 25 | name: example 26 | key: key 27 | containers: 28 | - name: example 29 | image: busybox:1.35.0@sha256:5be7104a4306abe768359a5379e6050ef69a29e9a5f99fcf7f46d5f7e9ba29a2 30 | resources: 31 | limits: 32 | memory: "128Mi" 33 | cpu: "500m" 34 | volumes: 35 | - name: volume 36 | configMap: 37 | name: example 38 | -------------------------------------------------------------------------------- /pkg/cmd/interpolate/testdata/results/file.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: test 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: test 10 | template: 11 | replicas: 4 12 | metadata: 13 | labels: 14 | app: test 15 | spec: 16 | containers: 17 | - name: test 18 | image: nginx:latest 19 | env: 20 | - name: env1 21 | value: 'env\\first\line\nenv\tsecondline\nenvthirdline\n' 22 | - name: env2 23 | value: '{ "foo": "bar\ntaz" }' 24 | - name: env3 25 | value: "{\n \"first\": \"field\",\n \"second\": \"field\",\n \"third\": \"field\",\n \"fourth\": \"field\"\n }" 26 | - name: env4 27 | value: "abc{ \"foo\": \"bar\" }def" 28 | - name: env5 29 | value: 'env with spaces and "' 30 | - name: env6 31 | value: "4" 32 | resources: 33 | limits: 34 | memory: "128Mi" 35 | cpu: "500m" 36 | -------------------------------------------------------------------------------- /pkg/cmd/interpolate/testdata/stdin/output.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: test 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: test 10 | template: 11 | replicas: 4 12 | metadata: 13 | labels: 14 | app: test 15 | spec: 16 | containers: 17 | - name: test 18 | image: nginx:latest 19 | env: 20 | - name: env1 21 | value: 'env\\first\line\nenv\tsecondline\nenvthirdline\n' 22 | - name: env2 23 | value: '{ "foo": "bar\ntaz" }' 24 | - name: env3 25 | value: "{\n \"first\": \"field\",\n \"second\": \"field\",\n \"third\": \"field\",\n \"fourth\": \"field\"\n }" 26 | - name: env4 27 | value: "abc{ \"foo\": \"bar\" }def" 28 | - name: env5 29 | value: 'env with spaces and "' 30 | - name: env6 31 | value: "4" 32 | resources: 33 | limits: 34 | memory: "128Mi" 35 | cpu: "500m" 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.yml: -------------------------------------------------------------------------------- 1 | name: Documenation 2 | description: Request a missing documentation or the correction of a wrong one 3 | title: "[Docs]: " 4 | labels: 5 | - documentation 6 | - needs triage 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: "## Thank you for contributing to our project!" 11 | - type: markdown 12 | attributes: 13 | value: Thanks for taking the time to fill out this documenation request. 14 | - id: documentation 15 | type: textarea 16 | attributes: 17 | label: Documentation Revision 18 | description: | 19 | Please describe what you want to be documented or the docs that must be corrected and why. 20 | validations: 21 | required: true 22 | - id: code-of-conduct 23 | type: checkboxes 24 | attributes: 25 | label: Code of Conduct 26 | description: By submitting this documentation revision, you agree to follow our [Code of Conduct](./CODE_OF_CONDUCT.md) 27 | options: 28 | - label: I agree to follow this project’s Code of Conduct 29 | required: true 30 | -------------------------------------------------------------------------------- /pkg/cmd/interpolate/testdata/results-missing-path/file.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: test 5 | namespace: test 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: test 10 | template: 11 | replicas: 4 12 | metadata: 13 | labels: 14 | app: test 15 | spec: 16 | containers: 17 | - name: test 18 | image: nginx:latest 19 | env: 20 | - name: env1 21 | value: 'env\\first\line\nenv\tsecondline\nenvthirdline\n' 22 | - name: env2 23 | value: '{ "foo": "bar\ntaz" }' 24 | - name: env3 25 | value: "{\n \"first\": \"field\",\n \"second\": \"field\",\n \"third\": \"field\",\n \"fourth\": \"field\"\n }" 26 | - name: env4 27 | value: "abc{ \"foo\": \"bar\" }def" 28 | - name: env5 29 | value: 'env with spaces and "' 30 | - name: env6 31 | value: "4" 32 | resources: 33 | limits: 34 | memory: "128Mi" 35 | cpu: "500m" 36 | -------------------------------------------------------------------------------- /pkg/cmd/root_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package cmd 17 | 18 | import ( 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestRootCommand(t *testing.T) { 25 | t.Parallel() 26 | 27 | cmd := NewRootCommand() 28 | assert.NotNil(t, cmd) 29 | } 30 | 31 | func TestVersionCommand(t *testing.T) { 32 | t.Parallel() 33 | 34 | cmd := versionCommand() 35 | assert.NotNil(t, cmd) 36 | assert.NoError(t, cmd.Execute()) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/testdata/expectations/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: example 5 | namespace: mlp-deploy-test 6 | annotations: 7 | config.kubernetes.io/depends-on: external-secrets.io/namespaces/mlp-deploy-test/ExternalSecret/external-secret 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: example 12 | template: 13 | metadata: 14 | annotations: 15 | mia-platform.eu/dependencies-checksum: "e668e6cbb6e786b4b46b853136cfc9fac4effe474dbef3a8420339cc353b13d1" 16 | mia-platform.eu/deploy-checksum: "a2d1ace0489d09c0ca26a1ab8a8bc9b11e4365cb4f904c434565a59119f3eb15" 17 | labels: 18 | app: example 19 | spec: 20 | containers: 21 | - image: nginx:latest 22 | name: example 23 | resources: 24 | limits: 25 | cpu: 500m 26 | memory: 128Mi 27 | env: 28 | - name: ENV 29 | valueFrom: 30 | secretKeyRef: 31 | key: secret-key 32 | name: external-secret 33 | volumes: 34 | - name: example 35 | configMap: 36 | name: example 37 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | # keep up to date the github actions 5 | - package-ecosystem: github-actions 6 | directory: / 7 | schedule: 8 | interval: monthly 9 | timezone: Europe/Rome 10 | groups: 11 | minor-actions-dependencies: 12 | update-types: 13 | - minor 14 | - patch 15 | commit-message: 16 | include: scope 17 | prefix: ci 18 | 19 | # keep up to date the base docker image 20 | - package-ecosystem: docker 21 | directory: / 22 | schedule: 23 | interval: daily 24 | time: "07:00" 25 | timezone: Europe/Rome 26 | commit-message: 27 | include: scope 28 | prefix: build 29 | 30 | # enable go dependencies security updates 31 | - directory: / 32 | open-pull-requests-limit: 0 33 | package-ecosystem: gomod 34 | rebase-strategy: auto 35 | schedule: 36 | interval: daily 37 | time: "07:00" 38 | timezone: Europe/Rome 39 | commit-message: 40 | include: scope 41 | prefix: chore 42 | 43 | # keep up to date devcontainers 44 | - package-ecosystem: devcontainers 45 | directory: "/" 46 | schedule: 47 | interval: monthly 48 | timezone: Europe/Rome 49 | commit-message: 50 | include: scope 51 | prefix: build 52 | -------------------------------------------------------------------------------- /tools/make/release.mk: -------------------------------------------------------------------------------- 1 | # Copyright Mia srl 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ##@ Release Goals 17 | 18 | SNAPSHOT_RELEASE?= 1 19 | GORELEASER_SNAPSHOT:= 20 | 21 | ifeq ($(SNAPSHOT_RELEASE), 1) 22 | GORELEASER_SNAPSHOT=--snapshot 23 | endif 24 | 25 | .PHONY: goreleaser/release 26 | goreleaser/release: 27 | $(GORELEASER_PATH) release $(GORELEASER_SNAPSHOT) --clean --config=.goreleaser.yaml 28 | 29 | goreleaser/check: 30 | $(GORELEASER_PATH) check --config=.goreleaser.yaml 31 | 32 | .PHONY: release-deps 33 | release-deps: $(GORELEASER_PATH) 34 | 35 | .PHONY: ci-release 36 | ci-release: release-deps goreleaser/release 37 | 38 | .PHONY: goreleaser-check 39 | goreleaser-check: release-deps goreleaser/check 40 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: local 5 | hooks: 6 | - id: run-lint 7 | name: Run make lint 8 | entry: make lint 9 | language: system 10 | pass_filenames: false 11 | - id: run-tests 12 | name: Run make test 13 | entry: make test 14 | language: system 15 | pass_filenames: false 16 | - repo: https://github.com/pre-commit/pre-commit-hooks 17 | rev: v6.0.0 18 | hooks: 19 | - id: check-case-conflict 20 | name: Check filename case conflicts 21 | - id: check-merge-conflict 22 | name: Check that no merge conflict marker exists 23 | - id: check-executables-have-shebangs 24 | name: Check that executable files have shebangs 25 | - id: check-shebang-scripts-are-executable 26 | name: Check that files with shebangs are executable 27 | - id: end-of-file-fixer 28 | name: Makes sure files end in a newline and only a newline 29 | - id: trailing-whitespace 30 | name: Trims trailing whitespace 31 | args: [--markdown-linebreak-ext=md] # add exception for markdown linebreaks 32 | - repo: https://github.com/gitleaks/gitleaks 33 | rev: v8.30.0 34 | hooks: 35 | - id: gitleaks 36 | name: Protect and discover secrets using Gitleaks 37 | -------------------------------------------------------------------------------- /tools/make/clean.mk: -------------------------------------------------------------------------------- 1 | # Copyright Mia srl 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ##@ Lint Goals 17 | 18 | .PHONY: clean 19 | clean: 20 | 21 | .PHONY: clean/coverage 22 | clean: clean/coverage 23 | clean/coverage: 24 | $(info Clean coverage file...) 25 | rm -fr coverage.txt 26 | 27 | .PHONY: clean/bin 28 | clean: clean/bin 29 | clean/bin: 30 | $(info Clean artifacts files...) 31 | rm -fr $(OUTPUT_DIR) 32 | 33 | .PHONY: clean/tools 34 | clean/tools: 35 | $(info Clean tools folder...) 36 | [ -d $(TOOLS_BIN)/k8s ] && chmod +w $(TOOLS_BIN)/k8s/* || true 37 | rm -fr $(TOOLS_BIN) 38 | 39 | .PHONY: clean/go 40 | clean/go: 41 | $(info Clean golang cache...) 42 | go clean -cache 43 | 44 | .PHONY: clean-all 45 | clean-all: clean clean/tools clean/go 46 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/externalsecret-mutator/external-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: external-secrets.io/v1beta1 2 | kind: ExternalSecret 3 | metadata: 4 | name: external-secret 5 | namespace: externalsecret-test 6 | spec: 7 | refreshInterval: 1h 8 | secretStoreRef: 9 | name: secret-store 10 | target: 11 | creationPolicy: Owner 12 | data: 13 | - secretKey: secret-key 14 | remoteRef: 15 | key: provider-key 16 | version: provider-key-version 17 | property: provider-key-property 18 | sourceRef: 19 | # point to a SecretStore that should be used to fetch a secret. 20 | # must be defined if no spec.secretStoreRef is defined. 21 | storeRef: 22 | name: secret-store 23 | kind: ClusterSecretStore 24 | - secretKey: 25 | remoteRef: 26 | key: provider-key2 27 | version: provider-key-version2 28 | property: provider-key-property2 29 | sourceRef: 30 | generatorRef: 31 | apiVersion: generators.external-secrets.io/v1alpha1 32 | kind: ECRAuthorizationToken 33 | name: "my-ecr" 34 | dataFrom: 35 | - sourceRef: 36 | # point to a SecretStore that should be used to fetch a secret. 37 | # must be defined if no spec.secretStoreRef is defined. 38 | storeRef: 39 | name: secret-store 40 | kind: SecretStore 41 | - sourceRef: 42 | generatorRef: 43 | apiVersion: generators.external-secrets.io/v1alpha1 44 | kind: ECRAuthorizationToken 45 | name: "my-ecr" 46 | -------------------------------------------------------------------------------- /pkg/cmd/generate/types.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package generate 17 | 18 | // dockerConfigJSON represents a local docker auth config file 19 | // for pulling images. 20 | type dockerConfigJSON struct { 21 | Auths dockerConfig `json:"auths" datapolicy:"token"` 22 | } 23 | 24 | // dockerConfig represents the config file used by the docker CLI. 25 | // This config that represents the credentials that should be used 26 | // when pulling images from specific image repositories. 27 | type dockerConfig map[string]dockerConfigEntry 28 | 29 | // dockerConfigEntry holds the user information that grant the access to docker registry 30 | type dockerConfigEntry struct { 31 | Username string `json:"username,omitempty"` 32 | Password string `json:"password,omitempty" datapolicy:"password"` 33 | Email string `json:"email,omitempty"` 34 | Auth string `json:"auth,omitempty" datapolicy:"token"` 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yaml: -------------------------------------------------------------------------------- 1 | name: Code Scanning 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | paths: 10 | - '**/*.go' 11 | - 'go.mod' 12 | - 'go.sum' 13 | - '.github/workflows/codeql.yml' 14 | schedule: 15 | - cron: 0 5 * * 1 # Run at 05:00 UTC every Monday 16 | 17 | permissions: {} 18 | 19 | # This allows a subsequently queued workflow run to interrupt previous runs 20 | concurrency: 21 | group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | codeql: 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 34 | with: 35 | show-progress: false 36 | - name: Setup Golang 37 | uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 38 | with: 39 | go-version-file: go.mod 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6 42 | with: 43 | languages: go 44 | build-mode: manual 45 | - name: Run Build 46 | run: go build . 47 | - name: Perform CodeQL Analysis 48 | uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6 49 | with: 50 | category: "/language:go" 51 | -------------------------------------------------------------------------------- /docs/50_interpolate.md: -------------------------------------------------------------------------------- 1 | # Interpolatation Template 2 | 3 | `mlp` is supporting a templating engine that is used in some of the commands to been able to use environment values 4 | as sources of part of the manifests generated by Mia-Platform Console. 5 | 6 | For now the templating engine is only supporting the substitution of values contained in environment variables 7 | using their direct name encompassed between `{{}}` without spaces. Like for example: 8 | 9 | ```yaml 10 | apiVersion: v1 11 | kind: Secret 12 | metadata: 13 | name: example 14 | type: Opaque 15 | data: 16 | key: {{ENVIRONMENT_NAME}} 17 | ``` 18 | 19 | The commands that support the interpolation template will also support to set a series of prefixes to prepend to the 20 | environment variable name to allow overrides and default values. The rule is that every prefixes will be check in 21 | order before the actual variables and if a value is found the call chain is stopped. 22 | For example if you set two prefixes like `TEST_` and `PRODUCTION_` for the previous yaml, the interpolation engine 23 | will check the existence of `TEST_ENVIRONMENT_NAME`, `PRODUCTION_ENVIRONMENT_NAME` and `ENVIRONMENT_NAME` in order 24 | and substitute the first of this three variables that contains a value. 25 | 26 | By default this interpolation will set the value on a single line putting the `\n` character explicitly for every 27 | new line found in the value. 28 | If the interpolation sequence is found surrounded by the `"` or `'` character we will also escape the content contained 29 | in the environment for you so that the resulting string will be a valid double or single quoted string. 30 | -------------------------------------------------------------------------------- /pkg/extensions/testdata/externalsecret-mutator/expected-external-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: external-secrets.io/v1beta1 2 | kind: ExternalSecret 3 | metadata: 4 | name: external-secret 5 | namespace: externalsecret-test 6 | annotations: 7 | config.kubernetes.io/depends-on: external-secrets.io/namespaces/externalsecret-test/SecretStore/secret-store 8 | spec: 9 | refreshInterval: 1h 10 | secretStoreRef: 11 | name: secret-store 12 | target: 13 | creationPolicy: Owner 14 | data: 15 | - secretKey: secret-key 16 | remoteRef: 17 | key: provider-key 18 | version: provider-key-version 19 | property: provider-key-property 20 | sourceRef: 21 | # point to a SecretStore that should be used to fetch a secret. 22 | # must be defined if no spec.secretStoreRef is defined. 23 | storeRef: 24 | name: secret-store 25 | kind: ClusterSecretStore 26 | - secretKey: 27 | remoteRef: 28 | key: provider-key2 29 | version: provider-key-version2 30 | property: provider-key-property2 31 | sourceRef: 32 | generatorRef: 33 | apiVersion: generators.external-secrets.io/v1alpha1 34 | kind: ECRAuthorizationToken 35 | name: "my-ecr" 36 | dataFrom: 37 | - sourceRef: 38 | # point to a SecretStore that should be used to fetch a secret. 39 | # must be defined if no spec.secretStoreRef is defined. 40 | storeRef: 41 | name: secret-store 42 | kind: SecretStore 43 | - sourceRef: 44 | generatorRef: 45 | apiVersion: generators.external-secrets.io/v1alpha1 46 | kind: ECRAuthorizationToken 47 | name: "my-ecr" 48 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug that you have experienced 3 | title: "[Bug]: " 4 | labels: 5 | - bug 6 | - needs triage 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: "## Thank you for contributing to our project!" 11 | - type: markdown 12 | attributes: 13 | value: Thanks for taking the time to fill out this bug report. 14 | - id: version 15 | type: input 16 | attributes: 17 | label: What version or versions you have tested? 18 | description: Add one or more version where the bug is present 19 | placeholder: ex. 1.0.0 or 1.0.0,1.0.1 20 | - id: operating-systems 21 | type: checkboxes 22 | attributes: 23 | label: Which operating systems have you used? 24 | description: You may select more than one. 25 | options: 26 | - label: macOS 27 | - label: Windows 28 | - label: Linux 29 | - id: expectation 30 | type: textarea 31 | attributes: 32 | label: What did you expect to happen? 33 | description: | 34 | Describe what did you expect to happen if this bug wasn’t there. 35 | validations: 36 | required: true 37 | - id: problem 38 | type: textarea 39 | attributes: 40 | label: What happened instead? 41 | description: | 42 | Please describe what happened and provide every detail you can for reproducing it. 43 | validations: 44 | required: true 45 | - id: code-of-conduct 46 | type: checkboxes 47 | attributes: 48 | label: Code of Conduct 49 | description: By submitting this issue, you agree to follow our [Code of Conduct](./CODE_OF_CONDUCT.md) 50 | options: 51 | - label: I agree to follow this project’s Code of Conduct 52 | required: true 53 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Go", 3 | "image": "golang:1.25.4", 4 | "features": { 5 | "ghcr.io/devcontainers/features/common-utils:2": { 6 | "installZsh": "true", 7 | "username": "vscode", 8 | "userUid": "1000", 9 | "userGid": "1000", 10 | "upgradePackages": "true" 11 | }, 12 | "ghcr.io/devcontainers/features/go:1": { 13 | "version": "none", 14 | "golangciLintVersion": "2.6.2" 15 | }, 16 | "ghcr.io/devcontainers/features/git:1": { 17 | "version": "latest", 18 | "ppa": "false" 19 | } 20 | }, 21 | "runArgs": [ 22 | "--cap-add=SYS_PTRACE", 23 | "--security-opt", 24 | "seccomp=unconfined" 25 | ], 26 | "customizations": { 27 | "vscode": { 28 | "settings": { 29 | "files.eol": "\n", 30 | "files.trimFinalNewlines": true, 31 | "files.insertFinalNewline": true, 32 | "files.trimTrailingWhitespace": false, 33 | "go.gopath": "/go", 34 | "go.useLanguageServer": true, 35 | "go.toolsManagement.checkForUpdates": "local", 36 | "go.buildFlags": ["-tags=conformance,integration"], 37 | "go.lintTool": "golangci-lint", 38 | "go.lintFlags": [ 39 | "--path-mode=abs", 40 | "--fast-only" 41 | ], 42 | "go.formatTool": "custom", 43 | "go.alternateTools": { 44 | "customFormatter": "golangci-lint" 45 | }, 46 | "go.formatFlags": [ 47 | "fmt", 48 | "--stdin" 49 | ] 50 | }, 51 | "extensions": [ 52 | "redhat.vscode-yaml", 53 | "timonwong.shellcheck", 54 | "editorconfig.editorconfig", 55 | "davidanson.vscode-markdownlint" 56 | ] 57 | }, 58 | "codespaces": { 59 | "openFiles": [ 60 | "README.md", 61 | "CONTRIBUTING.md" 62 | ] 63 | } 64 | }, 65 | "remoteUser": "vscode" 66 | } 67 | -------------------------------------------------------------------------------- /tools/make/lint.mk: -------------------------------------------------------------------------------- 1 | # Copyright Mia srl 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ##@ Lint Goals 17 | 18 | # if not already installed in the system install a pinned version in tools folder 19 | GOLANGCI_PATH:= $(shell command -v golangci-lint 2> /dev/null) 20 | ifndef GOLANGCI_PATH 21 | GOLANGCI_PATH:=$(TOOLS_BIN)/golangci-lint 22 | endif 23 | 24 | .PHONY: lint 25 | lint: 26 | 27 | .PHONY: lint-deps 28 | lint-deps: 29 | 30 | .PHONY: golangci-lint 31 | lint: golangci-lint 32 | golangci-lint: $(GOLANGCI_PATH) 33 | $(info Running golangci-lint with .golangci.yaml config file...) 34 | $(GOLANGCI_PATH) run --config=.golangci.yaml 35 | 36 | lint-deps: $(GOLANGCI_PATH) 37 | $(TOOLS_BIN)/golangci-lint: $(TOOLS_DIR)/GOLANGCI_LINT_VERSION 38 | $(eval GOLANGCI_LINT_VERSION:= $(shell cat $<)) 39 | mkdir -p $(TOOLS_BIN) 40 | $(info Installing golangci-lint $(GOLANGCI_LINT_VERSION) bin in $(TOOLS_BIN)) 41 | GOBIN=$(TOOLS_BIN) go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) 42 | 43 | .PHONY: gomod-lint 44 | lint: gomod-lint 45 | gomod-lint: 46 | $(info Running go mod tidy) 47 | # Always keep this version to latest -1 version of Go 48 | go mod tidy -compat=1.24 49 | 50 | .PHONY: ci-lint 51 | ci-lint: lint 52 | # Block the lint during ci if the go.mod and go.sum will be changed by go mod tidy 53 | git diff --exit-code go.mod; 54 | git diff --exit-code go.sum; 55 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | dist: bin 3 | 4 | builds: 5 | - main: "{{ .Env.BUILD_PATH }}" 6 | binary: >- 7 | {{ .Os }}/ 8 | {{- .Arch }}/ 9 | {{- with .Arm }}v{{ . }}/{{ end }} 10 | {{- .ProjectName }} 11 | no_unique_dist_dir: true 12 | env: 13 | - CGO_ENABLED=0 14 | flags: 15 | - -trimpath 16 | ldflags: 17 | - -s 18 | - -w 19 | - -X {{ .Env.VERSION_MODULE_NAME }}.Version={{ .Version }} 20 | - -X {{ .Env.VERSION_MODULE_NAME }}.BuildDate={{ .Date }} 21 | goos: 22 | - linux 23 | - darwin 24 | goarch: 25 | - amd64 26 | - arm 27 | - arm64 28 | - "386" 29 | goarm: 30 | - "6" 31 | - "7" 32 | 33 | archives: 34 | - formats: 35 | - binary 36 | name_template: >- 37 | {{ .Binary }}- 38 | {{- .Os }}- 39 | {{- .Arch }}{{ with .Arm }}v{{ . }}{{ end }} 40 | {{- with .Mips }}-{{ . }}{{ end }} 41 | {{- if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }} 42 | 43 | checksum: 44 | name_template: checksums.txt 45 | 46 | snapshot: 47 | version_template: "{{ .ShortCommit }}" 48 | 49 | changelog: 50 | sort: asc 51 | groups: 52 | - title: Features 53 | regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$' 54 | order: 0 55 | - title: Bug Fixes 56 | regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$' 57 | order: 1 58 | - title: Others 59 | order: 999 60 | 61 | brews: 62 | - name: "{{ .Env.CMDNAME }}" 63 | repository: 64 | owner: mia-platform 65 | name: homebrew-tap 66 | 67 | commit_author: 68 | name: bot-targa 69 | email: github@mia-platform.eu 70 | 71 | commit_msg_template: "Brew formula update for {{ .ProjectName }} version {{ .Tag }}" 72 | directory: Formula 73 | 74 | install: | 75 | bin.install "{{ .ArtifactName }}" => "{{ .Env.CMDNAME }}" 76 | 77 | chmod 0555, bin/"{{ .Env.CMDNAME }}" # generate_completions_from_executable fails otherwise 78 | generate_completions_from_executable(bin/"{{ .Env.CMDNAME }}", "completion") 79 | 80 | homepage: "https://www.mia-platform.eu" 81 | description: "{{ .Env.DESCRIPTION }}" 82 | 83 | license: "Apache-2.0" 84 | skip_upload: auto 85 | 86 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 87 | # vim: set ts=2 sw=2 tw=0 fo=cnqoj 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mlp 2 | 3 |
4 | 5 | [![Build Status][github-actions-svg]][github-actions] 6 | [![Go Report Card][go-report-card]][go-report-card-link] 7 | [![GoDoc][godoc-svg]][godoc-link] 8 | 9 |
10 | 11 | `mlp` is a command line tool responsible for creating, updating and deleting kubernetes resources based on files 12 | generated by Mia-Platform Console. 13 | The main subcommands that the tool has are: 14 | 15 | - `interpolate`: fill placeholders in kubernetes files with the values of ENV variables 16 | - `generate`: create files for kubernetes `ConfigMap` and `Secret` based on files and/or ENV values 17 | - `deploy`: create and/or update resources in a kubernetes namespace with the intepolated/generated files 18 | - `kustomize`: run kustomize build 19 | - `hydrate`: helper to fill kustomization.yml with resources and patches 20 | - `completion`: generate the autocompletion 21 | 22 | ## To Start Using `mlp` 23 | 24 | Read the documentation [here](./docs/10_overview.md). 25 | 26 | ## To Start Developing `mlp` 27 | 28 | To start developing the CLI you must have this requirements: 29 | 30 | - golang 1.22 31 | - make 32 | 33 | Once you have pulled the code locally, you can build the code with make: 34 | 35 | ```sh 36 | make build 37 | ``` 38 | 39 | `make` will download all the dependencies needed and will build the binary for your current system that you can find 40 | in the `bin` folder. 41 | 42 | To build the docker image locally run: 43 | 44 | ```sh 45 | make docker-build 46 | ``` 47 | 48 | ## Testing `mlp` 49 | 50 | To run the tests use the command: 51 | 52 | ```sh 53 | make test 54 | ``` 55 | 56 | Or add the `DEBUG_TEST` flag to run the test with debug mode enabled: 57 | 58 | ```sh 59 | make test DEBUG_TEST=1 60 | ``` 61 | 62 | Before sending a PR be sure that all the linter pass with success: 63 | 64 | ```sh 65 | make lint 66 | ``` 67 | 68 | [github-actions]: https://github.com/mia-platform/mlp/actions 69 | [github-actions-svg]: https://github.com/mia-platform/mlp/workflows/Continuous%20Integration%20Pipeline/badge.svg 70 | [godoc-svg]: https://godoc.org/github.com/mia-platform/mlp?status.svg 71 | [godoc-link]: https://godoc.org/github.com/mia-platform/mlp 72 | [go-report-card]: https://goreportcard.com/badge/github.com/mia-platform/mlp 73 | [go-report-card-link]: https://goreportcard.com/report/github.com/mia-platform/mlp 74 | -------------------------------------------------------------------------------- /pkg/extensions/deployoncefilter.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package extensions 17 | 18 | import ( 19 | "context" 20 | 21 | "github.com/mia-platform/jpl/pkg/client/cache" 22 | "github.com/mia-platform/jpl/pkg/filter" 23 | "github.com/mia-platform/jpl/pkg/resource" 24 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 25 | ) 26 | 27 | // deployOnceFilter will implement a filter that will remove a Secret or ConfigMap if they have a value 28 | // of deployFilterValue in the deployFilterAnnotation and the resource metadata is found in the remote inventory. 29 | // In any other cases the resources are kept. 30 | type deployOnceFilter struct{} 31 | 32 | // NewDeployOnceFilter return a new filter for avoiding to apply a resource more than once in its lifetime 33 | func NewDeployOnceFilter() filter.Interface { 34 | return &deployOnceFilter{} 35 | } 36 | 37 | // Filter implement filter.Interface interface 38 | func (f *deployOnceFilter) Filter(obj *unstructured.Unstructured, getter cache.RemoteResourceGetter) (bool, error) { 39 | objGK := obj.GroupVersionKind().GroupKind() 40 | 41 | switch objGK { 42 | case configMapGK, secretGK: 43 | annotations := obj.GetAnnotations() 44 | if annotations == nil { 45 | return false, nil 46 | } 47 | 48 | if value, found := annotations[deployFilterAnnotation]; !found || value != deployFilterValue { 49 | return false, nil 50 | } 51 | default: 52 | return false, nil 53 | } 54 | 55 | remoteObj, err := getter.Get(context.Background(), resource.ObjectMetadataFromUnstructured(obj)) 56 | return remoteObj != nil, err 57 | } 58 | 59 | // keep it to always check if deployOnceFilter implement correctly the filter.Interface interface 60 | var _ filter.Interface = &deployOnceFilter{} 61 | -------------------------------------------------------------------------------- /docs/10_overview.md: -------------------------------------------------------------------------------- 1 | # mlp CLI 2 | 3 | `mlp`is a Command Line Interface that is responsible for creating, updating and deleting Kubernetes resources 4 | based on files generated by Mia-Platform Console. 5 | It can be viewed as a superpowered `kubectl apply` command that introduce additional functionalities to simply sending 6 | patches to the api-server. 7 | 8 | `mlp` can prune resources that are not present anymore between different deploys because it keeps an inventory 9 | of all the resources that has applied the last time. 10 | It can force new deployment rollout even if there are no differences between deploys, running Jobs immediately from 11 | CronJob definitions, and it will add annotations to workload resources about their Secrets and ConfigMaps dependencies. 12 | The cli will also automatically watch the progression of the applied resources and it will report what and how many 13 | resources failed to reach a ready or successfull state. 14 | 15 | In addition `mlp` can also generate ConfigMaps or Secrets via a dedicate configuration file using a combination of 16 | environment variabiles, literal values and files, giving the user the ability to not commiting sensitive data and giving 17 | the ability to use different configuration for different runtime environments. 18 | 19 | ## Functionalities 20 | 21 | - `deploy`: the main command, is used for creating, updating and pruning resources in a kubernetes 22 | environment using the resource files created by the Mia-Platform Console 23 | - `generate`: create kubernetes `ConfigMap` and `Secret` based on a configuration file 24 | - `hydrate`: is an helper function for configuring correctly the kustomization files inside the target folder 25 | with all the files and patches found 26 | - `interpolate`: will run through all the files passed and run through a templating function for render the final 27 | manifests 28 | - `kustomize`: is the same command of `kustomize build` and can be used if you project is using the kustomize structure 29 | to render the resources to pass to the `interpolate` command 30 | 31 | For more information about the various options available to the various commands you can always run 32 | `mlp --help` to see the helpers. 33 | 34 | ## Guides 35 | 36 | Below, you can find additional documentation for `mlp`: 37 | 38 | - [Setup](./20_setup.md) 39 | - [Generation Configuration](./30_generate.md) 40 | - [Hydration Logic](./40_hydrate.md) 41 | - [Interpolatation Template](./50_interpolate.md) 42 | -------------------------------------------------------------------------------- /pkg/apis/mlp.mia-platform.eu/v1/types.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package v1 17 | 18 | import ( 19 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 | "k8s.io/apimachinery/pkg/runtime/schema" 21 | ) 22 | 23 | const ( 24 | GroupName = "mlp.mia-platform.eu" 25 | 26 | DataFromFile = "file" 27 | DataFromLiteral = "literal" 28 | ) 29 | 30 | var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} 31 | 32 | type GenerateConfiguration struct { 33 | metav1.TypeMeta `json:",inline" yaml:",inline"` 34 | 35 | Secrets []SecretSpec `json:"secrets,omitempty" yaml:"secrets,omitempty"` 36 | 37 | //nolint:tagliatelle 38 | ConfigMaps []ConfigMapSpec `json:"config-maps,omitempty" yaml:"config-maps,omitempty"` 39 | } 40 | 41 | // SecretSpec contains secret configurations 42 | type SecretSpec struct { 43 | Name string `json:"name" yaml:"name"` 44 | When string `json:"when" yaml:"when"` 45 | TLS *TLS `json:"tls" yaml:"tls"` 46 | Docker *DockerConfig `json:"docker" yaml:"docker"` 47 | Data []Data `json:"data" yaml:"data"` 48 | } 49 | 50 | type ConfigMapSpec struct { 51 | Name string `json:"name" yaml:"name"` 52 | Data []Data `json:"data" yaml:"data"` 53 | } 54 | 55 | type TLS struct { 56 | Cert *TLSData `json:"cert" yaml:"cert"` 57 | Key *TLSData `json:"key" yaml:"key"` 58 | } 59 | 60 | type TLSData struct { 61 | From string `json:"from" yaml:"from"` 62 | File string `json:"file" yaml:"file"` 63 | Value string `json:"value" yaml:"value"` 64 | } 65 | 66 | type DockerConfig struct { 67 | Username string `json:"username" yaml:"username"` 68 | Password string `json:"password" yaml:"password"` 69 | Email string `json:"email" yaml:"email"` 70 | Server string `json:"server" yaml:"server"` 71 | } 72 | 73 | type Data struct { 74 | From string `json:"from" yaml:"from"` 75 | File string `json:"file" yaml:"file"` 76 | Key string `json:"key" yaml:"key"` 77 | Value string `json:"value" yaml:"value"` 78 | } 79 | -------------------------------------------------------------------------------- /tools/make/test.mk: -------------------------------------------------------------------------------- 1 | # Copyright Mia srl 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ##@ Go Tests Goals 17 | 18 | DEBUG_TEST?= 19 | ifeq ($(DEBUG_TEST),1) 20 | GO_TEST_DEBUG_FLAG:= -v 21 | else 22 | GO_TEST_DEBUG_FLAG:= 23 | endif 24 | 25 | .PHONY: test/unit 26 | test/unit: 27 | $(info Running tests...) 28 | go test $(GO_TEST_DEBUG_FLAG) -race ./... 29 | 30 | .PHONY: test/integration/setup test/integration test/integration/teardown 31 | test/integration/setup: 32 | test/integration: 33 | $(info Running integration tests...) 34 | go test $(GO_TEST_DEBUG_FLAG) -tags=integration -race ./... 35 | test/integration/teardown: 36 | 37 | .PHONY: test/coverage 38 | test/coverage: 39 | $(info Running tests with coverage on...) 40 | go test $(GO_TEST_DEBUG_FLAG) -race -coverprofile=coverage.txt -covermode=atomic ./... 41 | 42 | .PHONY: test/integration/coverage 43 | test/integration/coverage: 44 | $(info Running ci tests with coverage on...) 45 | go test $(GO_TEST_DEBUG_FLAG) -tags=integration -race -coverprofile=coverage.txt -covermode=atomic ./... 46 | 47 | .PHONY: test/conformance test/conformance/setup test/conformance/teardown 48 | test/conformance/setup: 49 | test/conformance: 50 | $(info Running conformance tests...) 51 | go test $(GO_TEST_DEBUG_FLAG) -tags=conformance -race -count=1 $(CONFORMANCE_TEST_PATH) 52 | test/conformance/teardown: 53 | 54 | test/show/coverage: 55 | go tool cover -func=coverage.txt 56 | 57 | .PHONY: test 58 | test: test/unit 59 | 60 | .PHONY: test-coverage 61 | test-coverage: test/coverage 62 | 63 | .PHONY: test-integration 64 | test-integration: test/integration/setup test/integration test/integration/teardown 65 | 66 | .PHONY: test-integration-coverage 67 | test-integration-coverage: test/integration/setup test/integration/coverage test/integration/teardown 68 | 69 | .PHONY: test-conformance 70 | test-conformance: test/conformance/setup test/conformance test/conformance/teardown 71 | 72 | .PHONY: show-coverage 73 | show-coverage: test-coverage test/show/coverage 74 | 75 | .PHONY: show-integration-coverage 76 | show-integration-coverage: test-integration-coverage test/show/coverage 77 | -------------------------------------------------------------------------------- /tools/make/build.mk: -------------------------------------------------------------------------------- 1 | # Copyright Mia srl 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ##@ Go Builds Goals 17 | 18 | .PHONY: build 19 | build: 20 | 21 | # if not already installed in the system install a pinned version in tools folder 22 | GORELEASER_PATH:= $(shell command -v goreleaser 2> /dev/null) 23 | ifndef GORELEASER_PATH 24 | GORELEASER_PATH:= $(TOOLS_BIN)/goreleaser 25 | endif 26 | 27 | ifeq ($(IS_LIBRARY), 1) 28 | 29 | BUILD_DATE:= $(shell date -u "+%Y-%m-%d") 30 | GO_LDFLAGS+= -s -w 31 | 32 | ifdef VERSION_MODULE_NAME 33 | GO_LDFLAGS+= -X $(VERSION_MODULE_NAME).Version=$(VERSION) 34 | GO_LDFLAGS+= -X $(VERSION_MODULE_NAME).BuildDate=$(BUILD_DATE) 35 | endif 36 | 37 | .PHONY: go/build/% 38 | go/build/%: 39 | $(eval OS:= $(word 1,$(subst /, ,$*))) 40 | $(eval ARCH:= $(word 2,$(subst /, ,$*))) 41 | $(eval ARM:= $(word 3,$(subst /, ,$*))) 42 | $(info Building image for $(OS) $(ARCH) $(ARM)) 43 | 44 | GOOS=$(OS) GOARCH=$(ARCH) GOARM=$(ARM) CGO_ENABLED=0 go build -trimpath \ 45 | -ldflags "$(GO_LDFLAGS)" $(BUILD_PATH) 46 | 47 | else 48 | 49 | .PHONY: go/build/% 50 | go/build/%: 51 | $(eval OS:= $(word 1,$(subst /, ,$*))) 52 | $(eval ARCH:= $(word 2,$(subst /, ,$*))) 53 | $(eval ARM:= $(word 3,$(subst /, ,$*))) 54 | $(info Building image for $(OS) $(ARCH) $(ARM)) 55 | 56 | GOOS=$(OS) GOARCH=$(ARCH) GOARM=$(ARM) $(GORELEASER_PATH) build \ 57 | --single-target --snapshot --clean --config=.goreleaser.yaml 58 | 59 | .PHONY: go/build/multiarch 60 | go/build/multiarch: 61 | $(GORELEASER_PATH) build --snapshot --clean --config=.goreleaser.yaml 62 | 63 | .PHONY: build-deps 64 | build-deps: 65 | 66 | build-deps: $(GORELEASER_PATH) 67 | 68 | build: build-deps 69 | 70 | .PHONY: build-multiarch 71 | build-multiarch: $(GORELEASER_PATH) go/build/multiarch 72 | 73 | endif 74 | 75 | .PHONY: build 76 | build: go/build/$(GOOS)/$(GOARCH)/$(GOARM) 77 | 78 | $(TOOLS_BIN)/goreleaser: $(TOOLS_DIR)/GORELEASER_VERSION 79 | $(eval GORELEASER_VERSION:= $(shell cat $<)) 80 | mkdir -p $(TOOLS_BIN) 81 | $(info Installing goreleaser $(GORELEASER_VERSION) bin in $(TOOLS_BIN)) 82 | GOBIN=$(TOOLS_BIN) go install github.com/goreleaser/goreleaser/v2@$(GORELEASER_VERSION) 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom gitignore rules 2 | 3 | bin/ 4 | coverage.txt 5 | 6 | # End of Custom gitignore rules 7 | 8 | # Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,linux,windows,macos,go 9 | # Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,linux,windows,macos,go 10 | 11 | ### Go ### 12 | # If you prefer the allow list template instead of the deny list, see community template: 13 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 14 | # 15 | # Binaries for programs and plugins 16 | *.exe 17 | *.exe~ 18 | *.dll 19 | *.so 20 | *.dylib 21 | 22 | # Test binary, built with `go test -c` 23 | *.test 24 | 25 | # Output of the go coverage tool, specifically when used with LiteIDE 26 | *.out 27 | 28 | # Dependency directories (remove the comment below to include it) 29 | # vendor/ 30 | 31 | # Go workspace file 32 | go.work 33 | 34 | ### Linux ### 35 | *~ 36 | 37 | # temporary files which can be created if a process still has a handle open of a deleted file 38 | .fuse_hidden* 39 | 40 | # KDE directory preferences 41 | .directory 42 | 43 | # Linux trash folder which might appear on any partition or disk 44 | .Trash-* 45 | 46 | # .nfs files are created when an open file is removed but is still being accessed 47 | .nfs* 48 | 49 | ### macOS ### 50 | # General 51 | .DS_Store 52 | .AppleDouble 53 | .LSOverride 54 | 55 | # Icon must end with two \r 56 | Icon 57 | 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | .com.apple.timemachine.donotpresent 70 | 71 | # Directories potentially created on remote AFP share 72 | .AppleDB 73 | .AppleDesktop 74 | Network Trash Folder 75 | Temporary Items 76 | .apdisk 77 | 78 | ### macOS Patch ### 79 | # iCloud generated files 80 | *.icloud 81 | 82 | ### VisualStudioCode ### 83 | .vscode/* 84 | !.vscode/settings.json 85 | !.vscode/tasks.json 86 | !.vscode/launch.json 87 | !.vscode/extensions.json 88 | !.vscode/*.code-snippets 89 | 90 | # Local History for Visual Studio Code 91 | .history/ 92 | 93 | # Built Visual Studio Code Extensions 94 | *.vsix 95 | 96 | ### VisualStudioCode Patch ### 97 | # Ignore all local history of files 98 | .history 99 | .ionide 100 | 101 | ### Windows ### 102 | # Windows thumbnail cache files 103 | Thumbs.db 104 | Thumbs.db:encryptable 105 | ehthumbs.db 106 | ehthumbs_vista.db 107 | 108 | # Dump file 109 | *.stackdump 110 | 111 | # Folder config file 112 | [Dd]esktop.ini 113 | 114 | # Recycle Bin used on file shares 115 | $RECYCLE.BIN/ 116 | 117 | # Windows Installer files 118 | *.cab 119 | *.msi 120 | *.msix 121 | *.msm 122 | *.msp 123 | 124 | # Windows shortcuts 125 | *.lnk 126 | 127 | # End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,linux,windows,macos,go 128 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We welcome contributions from the community. Please read the following guidelines carefully to 4 | maximize the chances of your PR being merged. 5 | 6 | ## Communication 7 | 8 | - If you want to work on some open issue, please reach out to us via a comment on it before starting your work to 9 | check if someone else is already working on it. 10 | - Before working on a new feature please open an issue for the feature request and partecipate in the discussion for 11 | reaching an agreement on its design and utility for the project. 12 | - Small patches, bug fixes and documentation typos fixing don’t need prior communication. 13 | 14 | ## Inclusive Language 15 | 16 | Every PR, issue, code and documentation must be inclusive to all and must adhere to the following guidance: 17 | 18 | - Every documentation should follow an inclusive style. A nice writeup has been done by google in its [Google Developer 19 | Documentation Style Guide]. 20 | - Every contribution will be covered by our [Code of Conduct](./CODE_OF_CONDUCT.md) so read it carefully. 21 | - We will follow and will amend this list with the best practice and guidance that will emerge in the industry in the 22 | future and more comments and correction can be made during review by the mantainers. 23 | 24 | ## Opening a PR 25 | 26 | - Fork the repo 27 | - Read the [README.md](./README.md) file and the others documentation files, if presents, for guidances on how to setup 28 | the project locally and running the tests. 29 | - If your PR is adding codes it must also contains test that will cover it, try to cover 100% of your added code if 30 | possibile. During the review be ready to explain why you cannot reach that percentage. 31 | - We will not merge PR with failing tests or that will lower the coverage of the existing ones. 32 | - When you open a PR please follow the indication that are provided in the template and provide all the relevant 33 | information 34 | - Your PR title should be descriptive. 35 | - If your PR is co-authored or based on an earlier PR from another contributor, 36 | please attribute them with `Co-authored-by: name `. 37 | See [GitHub’s multiple author guidance] for further details. 38 | 39 | ## Commit Message Styling 40 | 41 | Every commit in this repository must follow the guidelines provided by [Conventional commits]. 42 | The following *types* are allowed: 43 | 44 | 1. `fix:` a commit that fixes a bug. 45 | 1. `feat:` a commit that adds new functionality. 46 | 1. `docs:` a commit that adds or improves the documentation. 47 | 1. `test:` a commit that adds unit tests. 48 | 1. `ci:` a commit that improves the pipelines or the integration mechanisms. 49 | 1. `style:` a commit that changes the code or documentation format and/or style without modifying the implementation. 50 | 1. `chore:` a catch-all type for any other commits. Generally used for commits that do not add or improve 51 | functionalities to code or documentation. 52 | 53 | [Google Developer Documentation Style Guide]: https://developers.google.com/style/inclusive-documentation 54 | [GitHub’s multiple author guidance]: https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors 55 | [Conventional commits]: https://www.conventionalcommits.org/en/v1.0.0/ 56 | -------------------------------------------------------------------------------- /pkg/cmd/hydrate/kustomizationFile.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package hydrate 17 | 18 | import ( 19 | "bytes" 20 | "fmt" 21 | "path/filepath" 22 | 23 | "sigs.k8s.io/kustomize/api/konfig" 24 | "sigs.k8s.io/kustomize/api/types" 25 | "sigs.k8s.io/kustomize/kyaml/filesys" 26 | yaml "sigs.k8s.io/yaml/goyaml.v3" 27 | ) 28 | 29 | // kustomizationFile is a struct for incapsulating operations on a kustomization file 30 | type kustomizationFile struct { 31 | path string 32 | fSys filesys.FileSystem 33 | } 34 | 35 | // newKustomizationFile returns a new instance for the given FileSystem and path 36 | func newKustomizationFile(fSys filesys.FileSystem, path string) (*kustomizationFile, error) { 37 | kf := &kustomizationFile{fSys: fSys} 38 | err := kf.validate(path) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return kf, nil 43 | } 44 | 45 | // GetPath return the full path of the kustomization file including its name 46 | func (kf *kustomizationFile) GetPath() string { 47 | return kf.path 48 | } 49 | 50 | // validate will validate that only one kustomization file exists at path and is not a folder 51 | func (kf *kustomizationFile) validate(path string) error { 52 | match := 0 53 | var paths []string 54 | for _, kfilename := range konfig.RecognizedKustomizationFileNames() { 55 | fullpath := filepath.Join(path, kfilename) 56 | if kf.fSys.Exists(fullpath) { 57 | match++ 58 | paths = append(paths, fullpath) 59 | } 60 | } 61 | 62 | switch match { 63 | case 0: 64 | return fmt.Errorf("missing kustomization file %q", konfig.DefaultKustomizationFileName()) 65 | case 1: 66 | kf.path = paths[0] 67 | default: 68 | return fmt.Errorf("found multiple kustomization file: %v", path) 69 | } 70 | 71 | if kf.fSys.IsDir(kf.path) { 72 | return fmt.Errorf("%s should be a file", kf.path) 73 | } 74 | return nil 75 | } 76 | 77 | // read will return the Kustomization struct from file 78 | func (kf *kustomizationFile) read() (*types.Kustomization, error) { 79 | data, err := kf.fSys.ReadFile(kf.path) 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | var k types.Kustomization 85 | if err := k.Unmarshal(data); err != nil { 86 | return nil, err 87 | } 88 | 89 | k.FixKustomization() 90 | 91 | return &k, nil 92 | } 93 | 94 | // write will save the data in the kustomization structure overriding the previous content 95 | func (kf *kustomizationFile) write(kustomization *types.Kustomization) error { 96 | buffer := new(bytes.Buffer) 97 | encoder := yaml.NewEncoder(buffer) 98 | encoder.SetIndent(2) 99 | encoder.CompactSeqIndent() 100 | 101 | if err := encoder.Encode(kustomization); err != nil { 102 | return err 103 | } 104 | 105 | if err := encoder.Close(); err != nil { 106 | return err 107 | } 108 | 109 | return kf.fSys.WriteFile(kf.path, buffer.Bytes()) 110 | } 111 | -------------------------------------------------------------------------------- /pkg/extensions/deployoncefilter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package extensions 17 | 18 | import ( 19 | "errors" 20 | "path/filepath" 21 | "testing" 22 | 23 | "github.com/mia-platform/jpl/pkg/client/cache" 24 | "github.com/mia-platform/jpl/pkg/resource" 25 | jpltesting "github.com/mia-platform/jpl/pkg/testing" 26 | "github.com/stretchr/testify/assert" 27 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 28 | ) 29 | 30 | func TestFilter(t *testing.T) { 31 | t.Parallel() 32 | 33 | testdata := filepath.Join("testdata", "filter") 34 | filtered := jpltesting.UnstructuredFromFile(t, filepath.Join(testdata, "filtered.yaml")) 35 | 36 | tests := map[string]struct { 37 | object *unstructured.Unstructured 38 | getter cache.RemoteResourceGetter 39 | expected bool 40 | expectedError string 41 | }{ 42 | "no filtering if no config map or secret": { 43 | object: jpltesting.UnstructuredFromFile(t, filepath.Join(testdata, "deployment.yaml")), 44 | }, 45 | "no filtering if config map use labels and not annotations": { 46 | object: jpltesting.UnstructuredFromFile(t, filepath.Join(testdata, "configmap.yaml")), 47 | }, 48 | "no filtering if annotations with other value": { 49 | object: jpltesting.UnstructuredFromFile(t, filepath.Join(testdata, "secret.yaml")), 50 | }, 51 | "filtering if annotation is present and remote object is found": { 52 | object: jpltesting.UnstructuredFromFile(t, filepath.Join(testdata, "filtered.yaml")), 53 | getter: &testGetter{ 54 | availableObjects: map[resource.ObjectMetadata]*unstructured.Unstructured{ 55 | resource.ObjectMetadataFromUnstructured(filtered): filtered, 56 | }, 57 | }, 58 | expected: true, 59 | }, 60 | "no filtering if annotation is present but no remote object is found": { 61 | object: jpltesting.UnstructuredFromFile(t, filepath.Join(testdata, "filtered.yaml")), 62 | getter: &testGetter{ 63 | availableObjects: map[resource.ObjectMetadata]*unstructured.Unstructured{}, 64 | }, 65 | }, 66 | "error getting remote object": { 67 | object: jpltesting.UnstructuredFromFile(t, filepath.Join(testdata, "filtered.yaml")), 68 | getter: &testGetter{ 69 | availableObjects: map[resource.ObjectMetadata]*unstructured.Unstructured{}, 70 | errors: map[resource.ObjectMetadata]error{ 71 | resource.ObjectMetadataFromUnstructured(filtered): errors.New("error on load"), 72 | }, 73 | }, 74 | expectedError: "error on load", 75 | }, 76 | } 77 | 78 | for name, test := range tests { 79 | t.Run(name, func(t *testing.T) { 80 | t.Parallel() 81 | 82 | filter := NewDeployOnceFilter() 83 | filtered, err := filter.Filter(test.object, test.getter) 84 | switch len(test.expectedError) { 85 | case 0: 86 | assert.NoError(t, err) 87 | default: 88 | assert.ErrorContains(t, err, test.expectedError) 89 | } 90 | assert.Equal(t, test.expected, filtered) 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | build-tags: 4 | - conformance 5 | - integration 6 | linters: 7 | default: none 8 | enable: 9 | - asasalint 10 | - asciicheck 11 | - bidichk 12 | - bodyclose 13 | - canonicalheader 14 | - contextcheck 15 | - copyloopvar 16 | - decorder 17 | - dogsled 18 | - durationcheck 19 | - embeddedstructfieldcheck 20 | - errcheck 21 | - errname 22 | - errorlint 23 | - exptostd 24 | - fatcontext 25 | - gocheckcompilerdirectives 26 | - goconst 27 | - gocritic 28 | - gocyclo 29 | - godoclint 30 | - goheader 31 | - gosec 32 | - gosmopolitan 33 | - govet 34 | - iface 35 | - ineffassign 36 | - intrange 37 | - iotamixing 38 | - misspell 39 | - mnd 40 | - nolintlint 41 | - perfsprint 42 | - prealloc 43 | - predeclared 44 | - protogetter 45 | - revive 46 | - staticcheck 47 | - tagliatelle 48 | - testifylint 49 | - thelper 50 | - tparallel 51 | - unconvert 52 | - unparam 53 | - unused 54 | - usestdlibvars 55 | - usetesting 56 | - whitespace 57 | - zerologlint 58 | settings: 59 | gocyclo: 60 | min-complexity: 15 61 | goheader: 62 | values: 63 | const: 64 | MY COMPANY: Mia srl 65 | template: |- 66 | Copyright {{ MY COMPANY }} 67 | SPDX-License-Identifier: Apache-2.0 68 | 69 | Licensed under the Apache License, Version 2.0 (the "License"); 70 | you may not use this file except in compliance with the License. 71 | You may obtain a copy of the License at 72 | 73 | http://www.apache.org/licenses/LICENSE-2.0 74 | 75 | Unless required by applicable law or agreed to in writing, software 76 | distributed under the License is distributed on an "AS IS" BASIS, 77 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 78 | See the License for the specific language governing permissions and 79 | limitations under the License. 80 | mnd: 81 | checks: 82 | - case 83 | - condition 84 | - return 85 | revive: 86 | rules: 87 | - name: var-naming 88 | arguments: 89 | - [] # allow list 90 | - [] # block list 91 | - - skip-package-name-checks: true # options 92 | tagliatelle: 93 | case: 94 | rules: 95 | yaml: camel 96 | use-field-name: true 97 | testifylint: 98 | disable: 99 | - require-error 100 | go-require: 101 | ignore-http-handlers: true 102 | unparam: 103 | check-exported: false 104 | usetesting: 105 | context-background: true 106 | context-todo: true 107 | os-chdir: true 108 | os-mkdir-temp: true 109 | os-setenv: true 110 | os-temp-dir: true 111 | os-create-temp: true 112 | exclusions: 113 | generated: lax 114 | presets: 115 | - comments 116 | - common-false-positives 117 | - legacy 118 | - std-error-handling 119 | rules: 120 | - linters: 121 | - errcheck 122 | - goconst 123 | - gocyclo 124 | - gosec 125 | - tagliatelle 126 | path: _test\.go 127 | paths: 128 | - third_party$ 129 | - builtin$ 130 | - examples$ 131 | formatters: 132 | enable: 133 | - gofmt 134 | - goimports 135 | settings: 136 | gofmt: 137 | simplify: true 138 | goimports: 139 | local-prefixes: 140 | - github.com/mia-platform/mlp 141 | exclusions: 142 | generated: lax 143 | paths: 144 | - third_party$ 145 | - builtin$ 146 | - examples$ 147 | - zz_generated 148 | -------------------------------------------------------------------------------- /pkg/extensions/externalsecretpoller.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package extensions 17 | 18 | import ( 19 | extsecv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1" 20 | "github.com/mia-platform/jpl/pkg/poller" 21 | corev1 "k8s.io/api/core/v1" 22 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | ) 25 | 26 | func ExternalSecretStatusCheckers() poller.CustomStatusCheckers { 27 | return poller.CustomStatusCheckers{ 28 | extsecGK: externalSecretStatusChecker, 29 | extSecStoreGK: secretStoreStatusChecker, 30 | } 31 | } 32 | 33 | // externalSecretStatusChecker contains the logic for checking if an ExternalSecret has finished to sync its data 34 | func externalSecretStatusChecker(object *unstructured.Unstructured) (*poller.Result, error) { 35 | externalSecret := new(extsecv1.ExternalSecret) 36 | if err := runtime.DefaultUnstructuredConverter.FromUnstructured(object.Object, externalSecret); err != nil { 37 | return nil, err 38 | } 39 | 40 | for _, condition := range externalSecret.Status.Conditions { 41 | switch condition.Type { 42 | case extsecv1.ExternalSecretReady: 43 | switch condition.Status { 44 | case corev1.ConditionTrue: 45 | return &poller.Result{ 46 | Status: poller.StatusCurrent, 47 | Message: condition.Message, 48 | }, nil 49 | case corev1.ConditionFalse: 50 | return &poller.Result{ 51 | Status: poller.StatusInProgress, 52 | Message: condition.Message, 53 | }, nil 54 | case corev1.ConditionUnknown: 55 | return &poller.Result{ 56 | Status: poller.StatusInProgress, 57 | Message: condition.Message, 58 | }, nil 59 | } 60 | case extsecv1.ExternalSecretDeleted: 61 | if condition.Status == corev1.ConditionTrue { 62 | return &poller.Result{ 63 | Status: poller.StatusTerminating, 64 | Message: condition.Message, 65 | }, nil 66 | } 67 | } 68 | } 69 | 70 | return &poller.Result{ 71 | Status: poller.StatusInProgress, 72 | Message: "ExternalSecret sync is in progress", 73 | }, nil 74 | } 75 | 76 | // secretStoreStatusChecker contains the logic for checking if an SecretStore has 77 | func secretStoreStatusChecker(object *unstructured.Unstructured) (*poller.Result, error) { 78 | secretStore := new(extsecv1.SecretStore) 79 | if err := runtime.DefaultUnstructuredConverter.FromUnstructured(object.Object, secretStore); err != nil { 80 | return nil, err 81 | } 82 | 83 | for _, condition := range secretStore.Status.Conditions { 84 | if condition.Type != extsecv1.SecretStoreReady { 85 | continue 86 | } 87 | switch condition.Status { 88 | case corev1.ConditionTrue: 89 | return &poller.Result{ 90 | Status: poller.StatusCurrent, 91 | Message: condition.Message, 92 | }, nil 93 | case corev1.ConditionFalse: 94 | return &poller.Result{ 95 | Status: poller.StatusInProgress, 96 | Message: condition.Message, 97 | }, nil 98 | case corev1.ConditionUnknown: 99 | return &poller.Result{ 100 | Status: poller.StatusInProgress, 101 | Message: condition.Message, 102 | }, nil 103 | } 104 | } 105 | 106 | return &poller.Result{ 107 | Status: poller.StatusInProgress, 108 | Message: "SecretStore is in progress", 109 | }, nil 110 | } 111 | -------------------------------------------------------------------------------- /tests/e2e/main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | //go:build conformance 17 | 18 | //nolint:thelper 19 | package e2e 20 | 21 | import ( 22 | "context" 23 | "fmt" 24 | "os" 25 | "path/filepath" 26 | "strings" 27 | "testing" 28 | 29 | "sigs.k8s.io/e2e-framework/pkg/env" 30 | "sigs.k8s.io/e2e-framework/pkg/envconf" 31 | "sigs.k8s.io/e2e-framework/pkg/envfuncs" 32 | "sigs.k8s.io/e2e-framework/support/kind" 33 | ) 34 | 35 | type namespaceCtxKey string 36 | 37 | var ( 38 | testenv env.Environment 39 | ) 40 | 41 | func TestMain(m *testing.M) { 42 | testenv = env.New() 43 | 44 | // Specifying a run ID so that multiple runs wouldn't collide. 45 | runID := envconf.RandomName("ns", 4) 46 | 47 | kindClusterName := "mlp-e2e-tests" 48 | kindImageName := "kindest/node:v1.32.5@sha256:e3b2327e3a5ab8c76f5ece68936e4cafaa82edf58486b769727ab0b3b97a5b0d" 49 | if nameFromEnv, found := os.LookupEnv("KIND_NODE_IMAGE"); found { 50 | kindImageName = nameFromEnv 51 | } 52 | 53 | imageOpts := kind.WithImage(kindImageName) 54 | 55 | kindConfigPath := filepath.Join("testdata", "kind.yaml") 56 | crdsPath := filepath.Join("testdata", "crds") 57 | 58 | // Use pre-defined environment funcs to create a kind cluster prior to test run 59 | testenv.Setup( 60 | envfuncs.CreateClusterWithConfig(kind.NewProvider(), kindClusterName, kindConfigPath, imageOpts), 61 | envfuncs.SetupCRDs(crdsPath, "*"), 62 | ) 63 | 64 | testenv.BeforeEachTest(func(ctx context.Context, cfg *envconf.Config, t *testing.T) (context.Context, error) { 65 | return createNSForTest(ctx, t, cfg, runID) 66 | }) 67 | 68 | testenv.AfterEachTest(func(ctx context.Context, cfg *envconf.Config, t *testing.T) (context.Context, error) { 69 | return deleteNSForTest(ctx, t, cfg) 70 | }) 71 | 72 | // Use pre-defined environment funcs to teardown kind cluster after tests 73 | testenv.Finish( 74 | // envfuncs.ExportClusterLogs(kindClusterName, "logs"), 75 | envfuncs.TeardownCRDs(crdsPath, "*"), 76 | // envfuncs.DestroyCluster(kindClusterName), 77 | ) 78 | 79 | // launch package tests 80 | os.Exit(testenv.Run(m)) 81 | } 82 | 83 | // CreateNSForTest creates a random namespace with the runID as a prefix. It is stored in the context 84 | // so that the deleteNSForTest routine can look it up and delete it. 85 | func createNSForTest(ctx context.Context, t *testing.T, cfg *envconf.Config, runID string) (context.Context, error) { 86 | ns := envconf.RandomName(runID, 10) 87 | t.Logf("Creating NS %q for test %q", ns, t.Name()) 88 | ctx = context.WithValue(ctx, getNamespaceKey(t), ns) 89 | 90 | return envfuncs.CreateNamespace(ns)(ctx, cfg) 91 | } 92 | 93 | // DeleteNSForTest looks up the namespace corresponding to the given test and deletes it. 94 | func deleteNSForTest(ctx context.Context, t *testing.T, cfg *envconf.Config) (context.Context, error) { 95 | ns := fmt.Sprint(ctx.Value(getNamespaceKey(t))) 96 | t.Logf("Deleting NS %q for test %q", ns, t.Name()) 97 | return envfuncs.DeleteNamespace(ns)(ctx, cfg) 98 | } 99 | 100 | // GetNamespaceKey returns the context key for a given test 101 | func getNamespaceKey(t *testing.T) namespaceCtxKey { 102 | // When we pass t.Name() from inside an `assess` step, the name is in the form TestName/Features/Assess 103 | if strings.Contains(t.Name(), "/") { 104 | return namespaceCtxKey(strings.Split(t.Name(), "/")[0]) 105 | } 106 | 107 | // When pass t.Name() from inside a `testenv.BeforeEachTest` function, the name is just TestName 108 | return namespaceCtxKey(t.Name()) 109 | } 110 | -------------------------------------------------------------------------------- /docs/30_generate.md: -------------------------------------------------------------------------------- 1 | # Generation Configuration 2 | 3 | The `generate` command is used to generate secrets and configmaps from a configuration `.yaml`, interpolating it 4 | with environment variables when necessary and saves the generated files in a specified directory. 5 | 6 | The configuration file supports environment variable interpolation following the regular expression `{{[A-Z0-9_]+}}`. 7 | The interpolation works in the same way described in the [interpolate](./50_interpolate.md) guide. 8 | The file has a `secrets` section where the keys `tls`,`docker`, and`data` are mutually exclusive and a 9 | `config-maps` section where the only section supported is `data`. 10 | 11 | An configuration file example can be like this: 12 | 13 | ```yaml 14 | secrets: 15 | - name: tls-secret 16 | when: once 17 | tls: 18 | cert: 19 | from: literal 20 | value: value 21 | key: 22 | from: file 23 | file: /path/to/file 24 | value: value 25 | - name: docker-pull-secret 26 | when: always 27 | docker: 28 | username: username 29 | password: password 30 | email: emal@example.com 31 | server: example.com 32 | - name: secret-name 33 | when: always 34 | data: 35 | - from: file 36 | file: ./path/to/file 37 | key: key 38 | config-maps: 39 | - name: config-map-name 40 | when: always 41 | data: 42 | - from: literal 43 | key: key 44 | value: value 45 | ``` 46 | 47 | ## Details 48 | 49 | We will going more in depth on the meaning and possible values of the various sections of the configuration file. 50 | 51 | ## `secrets` and `config-maps` 52 | 53 | These sections are the more obvious ones, and they are used to indicate what type of Kubernetes resource you want to 54 | generate. 55 | `secrets` will generate one or more `Secret` resource and `config-maps` will generate one or more `ConfigMap`. 56 | 57 | ## `when` 58 | 59 | The `when` option will accept only the value of `once` and `always`. Omitting the key or setting to another value 60 | will fallback on the `always`value. 61 | 62 | This key is used during apply time, and if set to `once` will generate a configuration with a particular annotation 63 | that will apply the generated file only if a resources of the same kind, name and namespace is not already present 64 | on the remote Kubernetes cluster. 65 | This option can be useful for generating resources with placeholder values that will be updated from other tools but 66 | are necessary for the correct rollout of other resources that might depends of them. A tipical application 67 | can be a `Secret` resource of `tls` type that contains a tls that will be handled by an external tools for keeping it 68 | updated before the end of the valid timestamp. 69 | 70 | ## `data` 71 | 72 | The `data` block is the only valid block for a `ConfigMap` resource and one of the valid one for the `Secret` 73 | resource. 74 | This block is used for setting one or more key in the resource using literal values or files. 75 | 76 | The `from` key can be one of `literal` or `file` and is used to select where to find the value used to popolate `key` 77 | in the final resource. If the value is `literal` the `value` key is used and its values is used; in the other case the 78 | `file` key is used as path to find the file to load for the value. The path can be absolute or relative to the folder 79 | where the command will be launched. 80 | 81 | ## `docker` 82 | 83 | The `docker` block is a special block valid only for `secrets` and will generate a Kubernete `Secret` of type 84 | `kubernetes.io/dockerconfigjson` that can be used as an `ImagePullSecret` for setting authorization to one or more 85 | docker registries. 86 | The four keys `username`, `password`, `email` and `server` are used for generate the json configuration and must 87 | contains a valid authorized user for the given `server` url of a remote repository. 88 | 89 | ## `tls` 90 | 91 | The `tls` block is the last supported type of `secrets` and will generate a Kubernetes `Secret` of type 92 | `kubernetes.io/tls`. This secret is usually used to set a certificate/private key pair for a TLS connection like 93 | exposing an HTTPS connection for an `Ingress` the keys of the object are always `tls.crt` for the certificate and 94 | `tls.key` for the private key. 95 | 96 | The values can be passed by file or directly in the configuration, but we highly recommend to use files for avoiding 97 | to accidentally leak sensible data. 98 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mia-platform/mlp/v2 2 | 3 | go 1.25 4 | 5 | toolchain go1.25.4 6 | 7 | require ( 8 | github.com/MakeNowJust/heredoc/v2 v2.0.1 9 | github.com/blang/semver/v4 v4.0.0 10 | github.com/distribution/reference v0.6.0 11 | github.com/external-secrets/external-secrets v0.19.2 12 | github.com/go-logr/logr v1.4.3 13 | github.com/go-logr/stdr v1.2.2 14 | github.com/mia-platform/jpl v0.9.0 15 | github.com/spf13/cobra v1.10.1 16 | github.com/spf13/pflag v1.0.10 17 | github.com/stretchr/testify v1.11.1 18 | k8s.io/api v0.33.4 19 | k8s.io/apimachinery v0.33.4 20 | k8s.io/cli-runtime v0.33.4 21 | k8s.io/client-go v0.33.4 22 | k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d 23 | sigs.k8s.io/e2e-framework v0.6.0 24 | sigs.k8s.io/kustomize/api v0.20.1 25 | sigs.k8s.io/kustomize/kyaml v0.20.1 26 | sigs.k8s.io/yaml v1.5.0 27 | ) 28 | 29 | require ( 30 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect 31 | github.com/beorn7/perks v1.0.1 // indirect 32 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 33 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 34 | github.com/emicklei/go-restful/v3 v3.12.2 // indirect 35 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 36 | github.com/fsnotify/fsnotify v1.9.0 // indirect 37 | github.com/fxamacker/cbor/v2 v2.9.0 // indirect 38 | github.com/go-errors/errors v1.4.2 // indirect 39 | github.com/go-openapi/jsonpointer v0.21.1 // indirect 40 | github.com/go-openapi/jsonreference v0.21.0 // indirect 41 | github.com/go-openapi/swag v0.23.1 // indirect 42 | github.com/gogo/protobuf v1.3.2 // indirect 43 | github.com/google/btree v1.1.3 // indirect 44 | github.com/google/gnostic-models v0.7.0 // indirect 45 | github.com/google/go-cmp v0.7.0 // indirect 46 | github.com/google/uuid v1.6.0 // indirect 47 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect 48 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect 49 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 50 | github.com/josharian/intern v1.0.0 // indirect 51 | github.com/json-iterator/go v1.1.12 // indirect 52 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect 53 | github.com/mailru/easyjson v0.9.0 // indirect 54 | github.com/moby/spdystream v0.5.0 // indirect 55 | github.com/moby/term v0.5.0 // indirect 56 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 57 | github.com/modern-go/reflect2 v1.0.2 // indirect 58 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 59 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 60 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect 61 | github.com/opencontainers/go-digest v1.0.0 // indirect 62 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect 63 | github.com/pkg/errors v0.9.1 // indirect 64 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 65 | github.com/prometheus/client_golang v1.22.0 // indirect 66 | github.com/prometheus/client_model v0.6.2 // indirect 67 | github.com/prometheus/common v0.65.0 // indirect 68 | github.com/prometheus/procfs v0.17.0 // indirect 69 | github.com/vladimirvivien/gexe v0.4.1 // indirect 70 | github.com/x448/float16 v0.8.4 // indirect 71 | github.com/xlab/treeprint v1.2.0 // indirect 72 | go.opentelemetry.io/otel v1.37.0 // indirect 73 | go.opentelemetry.io/otel/trace v1.37.0 // indirect 74 | go.yaml.in/yaml/v2 v2.4.2 // indirect 75 | go.yaml.in/yaml/v3 v3.0.4 // indirect 76 | golang.org/x/net v0.42.0 // indirect 77 | golang.org/x/oauth2 v0.30.0 // indirect 78 | golang.org/x/sync v0.16.0 // indirect 79 | golang.org/x/sys v0.34.0 // indirect 80 | golang.org/x/term v0.33.0 // indirect 81 | golang.org/x/text v0.27.0 // indirect 82 | golang.org/x/time v0.12.0 // indirect 83 | gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect 84 | google.golang.org/protobuf v1.36.6 // indirect 85 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 86 | gopkg.in/inf.v0 v0.9.1 // indirect 87 | gopkg.in/yaml.v3 v3.0.1 // indirect 88 | k8s.io/apiextensions-apiserver v0.33.4 // indirect 89 | k8s.io/component-base v0.33.4 // indirect 90 | k8s.io/klog/v2 v2.130.1 // indirect 91 | k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911 // indirect 92 | sigs.k8s.io/controller-runtime v0.21.0 // indirect 93 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 94 | sigs.k8s.io/randfill v1.0.0 // indirect 95 | sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect 96 | ) 97 | -------------------------------------------------------------------------------- /pkg/cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package cmd 17 | 18 | import ( 19 | "context" 20 | "fmt" 21 | "log" 22 | "runtime" 23 | 24 | "github.com/MakeNowJust/heredoc/v2" 25 | "github.com/go-logr/logr" 26 | "github.com/go-logr/stdr" 27 | "github.com/spf13/cobra" 28 | "github.com/spf13/pflag" 29 | "k8s.io/cli-runtime/pkg/genericclioptions" 30 | 31 | "github.com/mia-platform/mlp/v2/pkg/cmd/deploy" 32 | "github.com/mia-platform/mlp/v2/pkg/cmd/generate" 33 | "github.com/mia-platform/mlp/v2/pkg/cmd/hydrate" 34 | "github.com/mia-platform/mlp/v2/pkg/cmd/interpolate" 35 | "github.com/mia-platform/mlp/v2/pkg/cmd/kustomize" 36 | ) 37 | 38 | var ( 39 | // Version is dynamically set by the ci or overridden by the Makefile. 40 | Version = "DEV" 41 | // BuildDate is dynamically set at build time by the cli or overridden in the Makefile. 42 | BuildDate = "" // YYYY-MM-DD 43 | ) 44 | 45 | const ( 46 | cmdShort = "mlp can deploy a Mia-Platform application on a Kubernetes cluster" 47 | cmdLong = `mlp can deploy a Mia-Platform application on a Kubernetes cluster. 48 | 49 | Handle resource files generated by Mia-Platform Console for correct deployment 50 | on Kubernetes. It provides additional capabilities on top of traditional 51 | "kubectl apply" like resource inventory, resource generation and force redeploy.` 52 | cmdExamples = `$ mlp interpolate 53 | $ mlp deploy -f ./folder 54 | $ mlp generate --env-prefix DEV_` 55 | 56 | versionCmdShort = "Show mlp version" 57 | versionCmdLong = "Show mlp version" 58 | 59 | verboseFlagName = "verbose" 60 | verboseFlagShortName = "v" 61 | verboseUsage = "setting logging verbosity; use number between 0 and 10" 62 | ) 63 | 64 | type Flags struct { 65 | verbosity int 66 | } 67 | 68 | func NewRootCommand() *cobra.Command { 69 | flags := &Flags{} 70 | cmd := &cobra.Command{ 71 | Use: "mlp", 72 | 73 | Short: heredoc.Doc(cmdShort), 74 | Long: heredoc.Doc(cmdLong), 75 | Example: heredoc.Doc(cmdExamples), 76 | 77 | SilenceErrors: true, 78 | Version: versionString(), 79 | 80 | Args: cobra.NoArgs, 81 | ValidArgsFunction: cobra.NoFileCompletions, 82 | PersistentPreRun: func(*cobra.Command, []string) { 83 | stdr.SetVerbosity(flags.verbosity) 84 | }, 85 | } 86 | 87 | flags.AddFlags(cmd.PersistentFlags()) 88 | 89 | logger := stdr.New(log.Default()) 90 | cmd.SetContext(logr.NewContext(context.Background(), logger)) 91 | 92 | cmd.AddCommand( 93 | deploy.NewCommand(genericclioptions.NewConfigFlags(true)), 94 | generate.NewCommand(), 95 | hydrate.NewCommand(), 96 | interpolate.NewCommand(), 97 | kustomize.NewCommand(), 98 | versionCommand(), 99 | ) 100 | 101 | return cmd 102 | } 103 | 104 | // AddFlags set the connection between Flags property to command line flags 105 | func (f *Flags) AddFlags(flags *pflag.FlagSet) { 106 | flags.IntVarP(&f.verbosity, verboseFlagName, verboseFlagShortName, f.verbosity, verboseUsage) 107 | } 108 | 109 | // versionCommand return the command for printing the version string, like --version flag 110 | func versionCommand() *cobra.Command { 111 | cmd := &cobra.Command{ 112 | Use: "version", 113 | 114 | Short: heredoc.Doc(versionCmdShort), 115 | Long: heredoc.Doc(versionCmdLong), 116 | 117 | SilenceErrors: true, 118 | Args: cobra.NoArgs, 119 | ValidArgsFunction: cobra.NoFileCompletions, 120 | Run: func(cobra *cobra.Command, _ []string) { 121 | cobra.Println(versionString()) 122 | }, 123 | } 124 | 125 | return cmd 126 | } 127 | 128 | // versionString format a complete version string to output to the user 129 | func versionString() string { 130 | version := Version 131 | 132 | if BuildDate != "" { 133 | version = fmt.Sprintf("%s (%s)", version, BuildDate) 134 | } 135 | 136 | return fmt.Sprintf("%s, Go Version: %s", version, runtime.Version()) 137 | } 138 | -------------------------------------------------------------------------------- /pkg/cmd/kustomize/kustomize.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package kustomize 17 | 18 | import ( 19 | "context" 20 | "errors" 21 | "io" 22 | 23 | "github.com/MakeNowJust/heredoc/v2" 24 | "github.com/go-logr/logr" 25 | "github.com/spf13/cobra" 26 | "github.com/spf13/pflag" 27 | "sigs.k8s.io/kustomize/api/krusty" 28 | "sigs.k8s.io/kustomize/kyaml/filesys" 29 | ) 30 | 31 | const ( 32 | cmdUsage = "kustomize DIR" 33 | cmdShort = "Build a kustomization target from a directory" 34 | cmdLong = `Build a set of KRM resources using a 'kustomization.yaml' file. 35 | The DIR argument must be a path to a directory containing a 36 | 'kustomization.yaml' file. 37 | If DIR is omitted, '.' is assumed. 38 | ` 39 | cmdExamples = `# Build the current working directory 40 | mlp kustomize 41 | 42 | # Build a specific path 43 | mlp kustomize /home/config/project 44 | 45 | # Save output to a file 46 | mlp kustomize --output /home/config/build-results.yaml 47 | ` 48 | 49 | outputFlagName = "output" 50 | outputFlagShort = "o" 51 | outputFlagUsage = "If specified, write output to the file at this path" 52 | outputIsADirectoryError = "output path is a directory instead of a file" 53 | ) 54 | 55 | // Flags contains all the flags for the `kustomize` command. They will be converted to Options 56 | // that contains all runtime options for the command. 57 | type Flags struct { 58 | outputPath string 59 | } 60 | 61 | // Options have the data required to perform the kustomize operation 62 | type Options struct { 63 | inputPath string 64 | outputPath string 65 | fSys filesys.FileSystem 66 | writer io.Writer 67 | } 68 | 69 | // NewCommand return the command for build a kustomization target from a directory 70 | func NewCommand() *cobra.Command { 71 | flags := &Flags{} 72 | cmd := &cobra.Command{ 73 | Use: cmdUsage, 74 | Short: heredoc.Doc(cmdShort), 75 | Long: heredoc.Doc(cmdLong), 76 | Example: heredoc.Doc(cmdExamples), 77 | 78 | Args: cobra.RangeArgs(0, 1), 79 | 80 | Run: func(cmd *cobra.Command, args []string) { 81 | o, err := flags.ToOptions(args, filesys.MakeFsOnDisk(), cmd.OutOrStderr()) 82 | cobra.CheckErr(err) 83 | cobra.CheckErr(o.Run(cmd.Context())) 84 | }, 85 | } 86 | 87 | flags.AddFlags(cmd.Flags()) 88 | 89 | return cmd 90 | } 91 | 92 | // AddFlags set the connection between Flags property to command line flags 93 | func (f *Flags) AddFlags(set *pflag.FlagSet) { 94 | set.StringVarP(&f.outputPath, outputFlagName, outputFlagShort, "", outputFlagUsage) 95 | } 96 | 97 | // ToOptions transform the command flags in command runtime arguments 98 | func (f *Flags) ToOptions(args []string, fSys filesys.FileSystem, writer io.Writer) (*Options, error) { 99 | var inputPath string 100 | switch len(args) { 101 | case 0: 102 | inputPath = filesys.SelfDir 103 | default: 104 | inputPath = args[0] 105 | } 106 | 107 | if len(f.outputPath) > 0 && fSys.IsDir(f.outputPath) { 108 | return nil, errors.New(outputIsADirectoryError) 109 | } 110 | 111 | return &Options{ 112 | inputPath: inputPath, 113 | outputPath: f.outputPath, 114 | fSys: fSys, 115 | writer: writer, 116 | }, nil 117 | } 118 | 119 | // Run execute the kustomize command 120 | func (o *Options) Run(ctx context.Context) error { 121 | logger := logr.FromContextOrDiscard(ctx) 122 | 123 | logger.V(5).Info("reading kustomize files", "path", o.inputPath) 124 | kustomizer := krusty.MakeKustomizer(krusty.MakeDefaultOptions()) 125 | resourceMap, err := kustomizer.Run(o.fSys, o.inputPath) 126 | if err != nil { 127 | return err 128 | } 129 | 130 | yaml, err := resourceMap.AsYaml() 131 | if err != nil { 132 | return err 133 | } 134 | 135 | if len(o.outputPath) > 0 { 136 | logger.V(5).Info("writing accumulated data", "path", o.outputPath) 137 | return o.fSys.WriteFile(o.outputPath, yaml) 138 | } 139 | 140 | _, err = o.writer.Write(yaml) 141 | return err 142 | } 143 | -------------------------------------------------------------------------------- /pkg/cmd/kustomize/kustomize_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package kustomize 17 | 18 | import ( 19 | "bytes" 20 | "errors" 21 | "path/filepath" 22 | "testing" 23 | 24 | "github.com/stretchr/testify/assert" 25 | "github.com/stretchr/testify/require" 26 | "sigs.k8s.io/kustomize/kyaml/filesys" 27 | ) 28 | 29 | func TestCommand(t *testing.T) { 30 | t.Parallel() 31 | 32 | cmd := NewCommand() 33 | assert.NotNil(t, cmd) 34 | 35 | buffer := new(bytes.Buffer) 36 | cmd.SetArgs([]string{"testdata"}) 37 | cmd.SetOut(buffer) 38 | assert.NoError(t, cmd.Execute()) 39 | t.Log(buffer.String()) 40 | buffer.Reset() 41 | } 42 | 43 | func TestToOptions(t *testing.T) { 44 | t.Parallel() 45 | 46 | testPath := filepath.Join("test", "path") 47 | fSys := filesys.MakeEmptyDirInMemory() 48 | require.NoError(t, fSys.MkdirAll(testPath)) 49 | buffer := new(bytes.Buffer) 50 | tests := map[string]struct { 51 | flags *Flags 52 | args []string 53 | expectedOptions *Options 54 | expectedError string 55 | }{ 56 | "create options from flags": { 57 | flags: &Flags{ 58 | outputPath: filepath.Join(testPath, "file.txt"), 59 | }, 60 | args: []string{"input"}, 61 | expectedOptions: &Options{ 62 | outputPath: filepath.Join(testPath, "file.txt"), 63 | inputPath: "input", 64 | fSys: fSys, 65 | writer: buffer, 66 | }, 67 | }, 68 | "create options without args": { 69 | flags: &Flags{ 70 | outputPath: filepath.Join(testPath, "file.txt"), 71 | }, 72 | expectedOptions: &Options{ 73 | outputPath: filepath.Join(testPath, "file.txt"), 74 | inputPath: filesys.SelfDir, 75 | fSys: fSys, 76 | writer: buffer, 77 | }, 78 | }, 79 | "output path is a dir": { 80 | flags: &Flags{ 81 | outputPath: testPath, 82 | }, 83 | expectedError: outputIsADirectoryError, 84 | }, 85 | } 86 | 87 | for name, test := range tests { 88 | t.Run(name, func(t *testing.T) { 89 | t.Parallel() 90 | 91 | o, err := test.flags.ToOptions(test.args, fSys, buffer) 92 | switch len(test.expectedError) { 93 | case 0: 94 | assert.NoError(t, err) 95 | default: 96 | assert.ErrorContains(t, err, test.expectedError) 97 | } 98 | 99 | assert.Equal(t, test.expectedOptions, o) 100 | }) 101 | } 102 | } 103 | 104 | func TestRun(t *testing.T) { 105 | t.Parallel() 106 | 107 | tests := map[string]struct { 108 | options *Options 109 | expectedOutput string 110 | expectedError string 111 | }{ 112 | "run correctly": { 113 | options: &Options{ 114 | inputPath: "testdata", 115 | outputPath: "", 116 | fSys: filesys.MakeFsOnDisk(), 117 | writer: new(bytes.Buffer), 118 | }, 119 | }, 120 | "run saving on file": { 121 | options: &Options{ 122 | inputPath: "testdata", 123 | outputPath: filepath.Join(t.TempDir(), "output.yaml"), 124 | fSys: filesys.MakeFsOnDisk(), 125 | }, 126 | }, 127 | "error reading files": { 128 | options: &Options{ 129 | inputPath: "testdata", 130 | outputPath: "", 131 | fSys: filesys.MakeEmptyDirInMemory(), 132 | }, 133 | expectedError: "not a valid directory", 134 | }, 135 | "error during writes": { 136 | options: &Options{ 137 | inputPath: "testdata", 138 | outputPath: "", 139 | fSys: filesys.MakeFsOnDisk(), 140 | writer: failWriter{}, 141 | }, 142 | expectedError: "nope", 143 | }, 144 | } 145 | 146 | for name, test := range tests { 147 | t.Run(name, func(t *testing.T) { 148 | t.Parallel() 149 | 150 | err := test.options.Run(t.Context()) 151 | switch len(test.expectedError) { 152 | case 0: 153 | assert.NoError(t, err) 154 | default: 155 | assert.ErrorContains(t, err, test.expectedError) 156 | } 157 | }) 158 | } 159 | } 160 | 161 | // FailWriter is a writer that always returns an error. 162 | type failWriter struct{} 163 | 164 | // Write implements the Writer interface's Write method and returns an error. 165 | func (failWriter) Write([]byte) (int, error) { return 0, errors.New("nope") } 166 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright Mia srl 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | DEBUG_MAKEFILE?= 17 | ifeq ($(DEBUG_MAKEFILE),1) 18 | $(warning ***** executing goal(s) "$(MAKECMDGOALS)") 19 | $(warning ***** $(shell date)) 20 | else 21 | # If we're not debugging the Makefile, always hide the commands inside the goals 22 | MAKEFLAGS+= -s 23 | endif 24 | 25 | # It's necessary to set this because some environments don't link sh -> bash. 26 | # Using env is more portable than setting the path directly 27 | SHELL:= /usr/bin/env bash 28 | 29 | .EXPORT_ALL_VARIABLES: 30 | 31 | .SUFFIXES: 32 | 33 | ## Set all variables 34 | ifeq ($(origin PROJECT_DIR),undefined) 35 | PROJECT_DIR:= $(abspath $(shell pwd -P)) 36 | endif 37 | 38 | ifeq ($(origin OUTPUT_DIR),undefined) 39 | OUTPUT_DIR:= $(PROJECT_DIR)/bin 40 | endif 41 | 42 | ifeq ($(origin TOOLS_DIR),undefined) 43 | TOOLS_DIR:= $(PROJECT_DIR)/tools 44 | endif 45 | 46 | ifeq ($(origin TOOLS_BIN),undefined) 47 | TOOLS_BIN:= $(TOOLS_DIR)/bin 48 | endif 49 | 50 | # Set here the name of the package you want to build 51 | CMDNAME:= mlp 52 | BUILD_PATH:= . 53 | CONFORMANCE_TEST_PATH:= $(PROJECT_DIR)/tests/e2e 54 | IS_LIBRARY:= 55 | 56 | # enable modules 57 | GO111MODULE:= on 58 | GOOS:= $(shell go env GOOS) 59 | GOARCH:= $(shell go env GOARCH) 60 | GOARM:= $(shell go env GOARM) 61 | 62 | ## Build Variables 63 | GIT_REV:= $(shell git rev-parse --short HEAD 2>/dev/null) 64 | VERSION:= $(shell git describe --tags --exact-match 2>/dev/null || (echo $(GIT_REV) | cut -c1-12)) 65 | # insert here the go module where to add the version metadata 66 | VERSION_MODULE_NAME:= github.com/mia-platform/mlp/v2/pkg/cmd 67 | 68 | # supported platforms for container creation, these are a subset of the supported 69 | # platforms of the base image. 70 | # Or if you start from scratch the platforms you want to support in your image 71 | # This link contains the rules on how the strings must be formed https://github.com/containerd/containerd/blob/v1.4.3/platforms/platforms.go#L63 72 | SUPPORTED_PLATFORMS:= linux/386 linux/amd64 linux/arm64 linux/arm/v6 linux/arm/v7 73 | # Default platform for which building the docker image (darwin can run linux images for the same arch) 74 | # as SUPPORTED_PLATFORMS it highly depends on which platform are supported by the base image 75 | DEFAULT_DOCKER_PLATFORM:= linux/$(GOARCH)/$(GOARM) 76 | # List of one or more container registries for tagging the resulting docker images 77 | CONTAINER_REGISTRIES:= docker.io/miaplatform ghcr.io/mia-platform 78 | # The description used on the org.opencontainers.description label of the container 79 | DESCRIPTION:= mlp is a CLI used to interpolate and deploy resource on Kubernetes 80 | # The vendor name used on the org.opencontainers.image.vendor label of the container 81 | VENDOR_NAME:= Mia s.r.l. 82 | # The license used on the org.opencontainers.image.license label of the container 83 | LICENSE:= Apache-2.0 84 | # The documentation url used on the org.opencontainers.image.documentation label of the container 85 | DOCUMENTATION_URL:= https://docs.mia-platform.eu 86 | # The source url used on the org.opencontainers.image.source label of the container 87 | SOURCE_URL:= https://github.com/mia-platform/mlp 88 | BUILDX_CONTEXT?= mlp-build-context 89 | 90 | # Add additional targets that you want to run when calling make without arguments 91 | .PHONY: all 92 | all: lint test 93 | 94 | ## Includes 95 | include tools/make/clean.mk 96 | include tools/make/lint.mk 97 | include tools/make/test.mk 98 | include tools/make/generate.mk 99 | include tools/make/build.mk 100 | include tools/make/container.mk 101 | include tools/make/release.mk 102 | 103 | # Uncomment the correct test suite to run during CI 104 | .PHONY: ci 105 | ci: test-coverage 106 | # ci: test-integration-coverage 107 | 108 | ### Put your custom import, define or goals under here ### 109 | 110 | generate-deps: $(TOOLS_BIN)/deepcopy-gen 111 | $(TOOLS_BIN)/deepcopy-gen: $(TOOLS_DIR)/DEEPCOPY_GEN_VERSION 112 | $(eval DEEPCOPY_GEN_VERSION:= $(shell cat $<)) 113 | mkdir -p $(TOOLS_BIN) 114 | $(info Installing deepcopy-gen $(DEEPCOPY_GEN_VERSION) bin in $(TOOLS_BIN)) 115 | GOBIN=$(TOOLS_BIN) go install k8s.io/code-generator/cmd/deepcopy-gen@$(DEEPCOPY_GEN_VERSION) 116 | -------------------------------------------------------------------------------- /pkg/extensions/externalsecretpoller_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package extensions 17 | 18 | import ( 19 | "path/filepath" 20 | "testing" 21 | 22 | "github.com/mia-platform/jpl/pkg/poller" 23 | jpltesting "github.com/mia-platform/jpl/pkg/testing" 24 | "github.com/stretchr/testify/assert" 25 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 | ) 27 | 28 | func TestExtendedPoller(t *testing.T) { 29 | t.Parallel() 30 | 31 | customCheckers := ExternalSecretStatusCheckers() 32 | assert.Len(t, customCheckers, 2) 33 | } 34 | 35 | func TestExternalSecretStatusChecker(t *testing.T) { 36 | t.Parallel() 37 | 38 | testdata := filepath.Join("testdata", "custom-pollers") 39 | tests := map[string]struct { 40 | object *unstructured.Unstructured 41 | expectedResult *poller.Result 42 | expectedError string 43 | }{ 44 | "resource secret deleted condition is terminating": { 45 | object: jpltesting.UnstructuredFromFile(t, filepath.Join(testdata, "extsec-terminating.yaml")), 46 | expectedResult: &poller.Result{ 47 | Status: poller.StatusTerminating, 48 | Message: "custom message", 49 | }, 50 | }, 51 | "resource with ready condition true is current": { 52 | object: jpltesting.UnstructuredFromFile(t, filepath.Join(testdata, "extsec-ready-true.yaml")), 53 | expectedResult: &poller.Result{ 54 | Status: poller.StatusCurrent, 55 | Message: "custom message", 56 | }, 57 | }, 58 | "resource with ready condition false is in progress": { 59 | object: jpltesting.UnstructuredFromFile(t, filepath.Join(testdata, "extsec-ready-false.yaml")), 60 | expectedResult: &poller.Result{ 61 | Status: poller.StatusInProgress, 62 | Message: "custom message", 63 | }, 64 | }, 65 | "resource without status is in progress": { 66 | object: jpltesting.UnstructuredFromFile(t, filepath.Join(testdata, "extsec-no-status.yaml")), 67 | expectedResult: &poller.Result{ 68 | Status: poller.StatusInProgress, 69 | Message: "ExternalSecret sync is in progress", 70 | }, 71 | }, 72 | } 73 | 74 | for testName, testCase := range tests { 75 | t.Run(testName, func(t *testing.T) { 76 | t.Parallel() 77 | 78 | result, err := externalSecretStatusChecker(testCase.object) 79 | if len(testCase.expectedError) > 0 { 80 | assert.ErrorContains(t, err, testCase.expectedError) 81 | assert.Equal(t, testCase.expectedResult, result) 82 | return 83 | } 84 | 85 | assert.NoError(t, err) 86 | assert.Equal(t, testCase.expectedResult, result) 87 | }) 88 | } 89 | } 90 | 91 | func TestSecretStoreStatusChecker(t *testing.T) { 92 | t.Parallel() 93 | 94 | testdata := filepath.Join("testdata", "custom-pollers") 95 | tests := map[string]struct { 96 | object *unstructured.Unstructured 97 | expectedResult *poller.Result 98 | expectedError string 99 | }{ 100 | "resource with ready condition true is current": { 101 | object: jpltesting.UnstructuredFromFile(t, filepath.Join(testdata, "secstore-ready-true.yaml")), 102 | expectedResult: &poller.Result{ 103 | Status: poller.StatusCurrent, 104 | Message: "custom message", 105 | }, 106 | }, 107 | "resource with ready condition false is in progress": { 108 | object: jpltesting.UnstructuredFromFile(t, filepath.Join(testdata, "secstore-ready-false.yaml")), 109 | expectedResult: &poller.Result{ 110 | Status: poller.StatusInProgress, 111 | Message: "custom message", 112 | }, 113 | }, 114 | "resource without status is in progress": { 115 | object: jpltesting.UnstructuredFromFile(t, filepath.Join(testdata, "secstore-no-status.yaml")), 116 | expectedResult: &poller.Result{ 117 | Status: poller.StatusInProgress, 118 | Message: "SecretStore is in progress", 119 | }, 120 | }, 121 | } 122 | 123 | for testName, testCase := range tests { 124 | t.Run(testName, func(t *testing.T) { 125 | t.Parallel() 126 | 127 | result, err := secretStoreStatusChecker(testCase.object) 128 | if len(testCase.expectedError) > 0 { 129 | assert.ErrorContains(t, err, testCase.expectedError) 130 | assert.Equal(t, testCase.expectedResult, result) 131 | return 132 | } 133 | 134 | assert.NoError(t, err) 135 | assert.Equal(t, testCase.expectedResult, result) 136 | }) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /pkg/extensions/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package extensions 17 | 18 | import ( 19 | "crypto/sha512" 20 | "encoding/hex" 21 | "fmt" 22 | "reflect" 23 | 24 | extsecv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1" 25 | appsv1 "k8s.io/api/apps/v1" 26 | corev1 "k8s.io/api/core/v1" 27 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 28 | "k8s.io/apimachinery/pkg/runtime" 29 | "k8s.io/apimachinery/pkg/runtime/schema" 30 | "sigs.k8s.io/yaml" 31 | ) 32 | 33 | const ( 34 | miaPlatformPrefix = "mia-platform.eu/" 35 | 36 | deployFilterAnnotation = miaPlatformPrefix + "deploy" 37 | deployFilterValue = "once" 38 | 39 | deployChecksumAnnotation = miaPlatformPrefix + "deploy-checksum" 40 | 41 | checksumAnnotation = miaPlatformPrefix + "dependencies-checksum" 42 | 43 | DeployAll = "deploy_all" 44 | DeploySmart = "smart_deploy" 45 | ) 46 | 47 | var ( 48 | configMapGK = corev1.SchemeGroupVersion.WithKind(reflect.TypeOf(corev1.ConfigMap{}).Name()).GroupKind() 49 | secretGK = corev1.SchemeGroupVersion.WithKind(reflect.TypeOf(corev1.Secret{}).Name()).GroupKind() 50 | 51 | extsecGK = extsecv1.SchemeGroupVersion.WithKind(extsecv1.ExtSecretKind).GroupKind() 52 | extSecStoreGK = extsecv1.SchemeGroupVersion.WithKind(extsecv1.SecretStoreKind).GroupKind() 53 | 54 | deployGK = appsv1.SchemeGroupVersion.WithKind(reflect.TypeOf(appsv1.Deployment{}).Name()).GroupKind() 55 | dsGK = appsv1.SchemeGroupVersion.WithKind(reflect.TypeOf(appsv1.DaemonSet{}).Name()).GroupKind() 56 | stsGK = appsv1.SchemeGroupVersion.WithKind(reflect.TypeOf(appsv1.StatefulSet{}).Name()).GroupKind() 57 | podGK = corev1.SchemeGroupVersion.WithKind(reflect.TypeOf(corev1.Pod{}).Name()).GroupKind() 58 | ) 59 | 60 | // podFieldsForGroupKind return the pieces of the path for the pod spec and pod annotations for an unstructured 61 | // object described by gvk. This arrays can be used for retrieving information wihout casting the resource. 62 | func podFieldsForGroupKind(gvk schema.GroupVersionKind) ([]string, []string, error) { 63 | podSpecFields := []string{} 64 | annotationsFields := []string{} 65 | var err error 66 | switch gvk.GroupKind() { 67 | case deployGK: 68 | podSpecFields = []string{"spec", "template", "spec"} 69 | annotationsFields = []string{"spec", "template", "metadata", "annotations"} 70 | case dsGK: 71 | podSpecFields = []string{"spec", "template", "spec"} 72 | annotationsFields = []string{"spec", "template", "metadata", "annotations"} 73 | case stsGK: 74 | podSpecFields = []string{"spec", "template", "spec"} 75 | annotationsFields = []string{"spec", "template", "metadata", "annotations"} 76 | case podGK: 77 | podSpecFields = []string{"spec"} 78 | annotationsFields = []string{"metadata", "annotations"} 79 | default: 80 | apiVersion, kind := gvk.ToAPIVersionAndKind() 81 | err = fmt.Errorf("unsupported object type for dependencies mutator: \"%s, %s\"", apiVersion, kind) 82 | } 83 | 84 | return podSpecFields, annotationsFields, err 85 | } 86 | 87 | // podSpecFromUnstructured try to extract a podSpec from obj at fields path 88 | func podSpecFromUnstructured(obj *unstructured.Unstructured, fields []string) (corev1.PodSpec, error) { 89 | var podSpec corev1.PodSpec 90 | unstrPodSpec, _, err := unstructured.NestedMap(obj.Object, fields...) 91 | if err != nil { 92 | return podSpec, err 93 | } 94 | 95 | if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstrPodSpec, &podSpec); err != nil { 96 | return podSpec, err 97 | } 98 | 99 | return podSpec, nil 100 | } 101 | 102 | // annotationsFromUnstructuredFields return the annotation present at fields in the obj or an empty map 103 | func annotationsFromUnstructuredFields(obj *unstructured.Unstructured, fields []string) (map[string]string, error) { 104 | annotations, _, err := unstructured.NestedStringMap(obj.Object, fields...) 105 | if err != nil { 106 | return nil, err 107 | } 108 | 109 | if annotations == nil { 110 | annotations = make(map[string]string) 111 | } 112 | 113 | return annotations, nil 114 | } 115 | 116 | // ChecksumFromData create a Sum512_256 checksum for arbitrary data 117 | func ChecksumFromData(data interface{}) string { 118 | encoded, err := yaml.Marshal(data) 119 | if err != nil { 120 | return "" 121 | } 122 | 123 | shasum := sha512.Sum512_256(encoded) 124 | return hex.EncodeToString(shasum[:]) 125 | } 126 | -------------------------------------------------------------------------------- /tools/make/container.mk: -------------------------------------------------------------------------------- 1 | # Copyright Mia srl 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | ##@ Docker Images Goals 17 | 18 | # Force enable buildkit as a build engine 19 | DOCKER_CMD:= DOCKER_BUILDKIT=1 docker 20 | REPO_TAG:= $(shell git describe --tags --exact-match 2>/dev/null || echo latest) 21 | # Making the subst function works with spaces and comas required this hack 22 | COMMA:= , 23 | EMPTY:= 24 | SPACE:= $(EMPTY) $(EMPTY) 25 | DOCKER_SUPPORTED_PLATFORMS:= $(subst $(SPACE),$(COMMA),$(SUPPORTED_PLATFORMS)) 26 | PARSED_TAGS:= $(shell $(TOOLS_DIR)/parse-tags.sh $(REPO_TAG)) 27 | IMAGE_TAGS:= $(addprefix --tag , $(foreach REGISTRY, $(CONTAINER_REGISTRIES), $(foreach TAG, $(PARSED_TAGS), $(REGISTRY)/$(CMDNAME):$(TAG)))) 28 | CONTAINER_BUILD_DATE:= $(shell date -u "+%Y-%m-%dT%H:%M:%SZ") 29 | 30 | DOCKER_LABELS:= --label "org.opencontainers.image.title=$(CMDNAME)" 31 | DOCKER_LABELS+= --label "org.opencontainers.image.description=$(DESCRIPTION)" 32 | DOCKER_LABELS+= --label "org.opencontainers.image.url=$(SOURCE_URL)" 33 | DOCKER_LABELS+= --label "org.opencontainers.image.source=$(SOURCE_URL)" 34 | DOCKER_LABELS+= --label "org.opencontainers.image.version=$(REPO_TAG)" 35 | DOCKER_LABELS+= --label "org.opencontainers.image.created=$(CONTAINER_BUILD_DATE)" 36 | DOCKER_LABELS+= --label "org.opencontainers.image.revision=$(shell git rev-parse HEAD 2>/dev/null)" 37 | DOCKER_LABELS+= --label "org.opencontainers.image.licenses=$(LICENSE)" 38 | DOCKER_LABELS+= --label "org.opencontainers.image.documentation=$(DOCUMENTATION_URL)" 39 | DOCKER_LABELS+= --label "org.opencontainers.image.vendor=$(VENDOR_NAME)" 40 | 41 | DOCKER_ANNOTATIONS:= --annotation "org.opencontainers.image.title=$(CMDNAME)" 42 | DOCKER_ANNOTATIONS+= --annotation "org.opencontainers.image.description=$(DESCRIPTION)" 43 | DOCKER_ANNOTATIONS+= --annotation "org.opencontainers.image.url=$(SOURCE_URL)" 44 | DOCKER_ANNOTATIONS+= --annotation "org.opencontainers.image.source=$(SOURCE_URL)" 45 | DOCKER_ANNOTATIONS+= --annotation "org.opencontainers.image.version=$(REPO_TAG)" 46 | DOCKER_ANNOTATIONS+= --annotation "org.opencontainers.image.created=$(CONTAINER_BUILD_DATE)" 47 | DOCKER_ANNOTATIONS+= --annotation "org.opencontainers.image.revision=$(shell git rev-parse HEAD 2>/dev/null)" 48 | DOCKER_ANNOTATIONS+= --annotation "org.opencontainers.image.licenses=$(LICENSE)" 49 | DOCKER_ANNOTATIONS+= --annotation "org.opencontainers.image.documentation=$(DOCUMENTATION_URL)" 50 | DOCKER_ANNOTATIONS+= --annotation "org.opencontainers.image.vendor=$(VENDOR_NAME)" 51 | 52 | .PHONY: docker/%/multiarch 53 | docker/%/multiarch: 54 | $(eval ACTION:= $(word 1,$(subst /, , $*))) 55 | $(eval IS_PUSH:= $(filter push,$(ACTION))) 56 | $(eval ADDITIONAL_PARAMETER:= $(if $(IS_PUSH), --push)) 57 | $(info Building image for following platforms: $(SUPPORTED_PLATFORMS)) 58 | $(DOCKER_CMD) buildx build --platform "$(DOCKER_SUPPORTED_PLATFORMS)" \ 59 | --build-arg CMD_NAME=$(CMDNAME) \ 60 | --provenance=false \ 61 | $(IMAGE_TAGS) \ 62 | $(DOCKER_LABELS) \ 63 | $(DOCKER_ANNOTATIONS) \ 64 | --file ./Dockerfile $(OUTPUT_DIR) $(ADDITIONAL_PARAMETER) 65 | 66 | .PHONY: docker/build/% 67 | docker/build/%: 68 | $(eval OS:= $(word 1,$(subst /, ,$*))) 69 | $(eval ARCH:= $(word 2,$(subst /, ,$*))) 70 | $(eval ARM:= $(word 3,$(subst /, ,$*))) 71 | $(info Building image for $(OS) $(ARCH) $(ARM)) 72 | $(DOCKER_CMD) build --platform $* \ 73 | --build-arg CMD_NAME=$(CMDNAME) \ 74 | $(IMAGE_TAGS) \ 75 | $(DOCKER_LABELS) \ 76 | $(DOCKER_ANNOTATIONS) \ 77 | --file ./Dockerfile $(OUTPUT_DIR) 78 | 79 | .PHONY: docker/setup/multiarch 80 | docker/setup/multiarch: 81 | $(info Setup multiarch emulation...) 82 | docker run --rm --privileged tonistiigi/binfmt:latest --install $(SUPPORTED_PLATFORMS) 83 | 84 | .PHONY: docker/buildx/setup docker/buildx/teardown 85 | docker/buildx/setup: 86 | docker buildx rm $(BUILDX_CONTEXT) 2>/dev/null || : 87 | docker buildx create --use --name $(BUILDX_CONTEXT) --platform "$(DOCKER_SUPPORTED_PLATFORMS)" 88 | 89 | docker/buildx/teardown: 90 | docker buildx rm $(BUILDX_CONTEXT) 91 | 92 | .PHONY: docker-build 93 | docker-build: go/build/$(DEFAULT_DOCKER_PLATFORM) docker/build/$(DEFAULT_DOCKER_PLATFORM) 94 | 95 | .PHONY: docker-setup-multiarch 96 | docker-setup-multiarch: docker/setup/multiarch 97 | 98 | .PHONY: docker-build-multiarch 99 | docker-build-multiarch: build-multiarch docker/buildx/setup docker/build/multiarch docker/buildx/teardown 100 | 101 | .PHONY: ci-docker 102 | ci-docker: docker/push/multiarch 103 | -------------------------------------------------------------------------------- /pkg/apis/mlp.mia-platform.eu/v1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | // Copyright Mia srl 5 | // SPDX-License-Identifier: Apache-2.0 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | // Code generated by deepcopy-gen. DO NOT EDIT. 20 | 21 | package v1 22 | 23 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 24 | func (in *ConfigMapSpec) DeepCopyInto(out *ConfigMapSpec) { 25 | *out = *in 26 | if in.Data != nil { 27 | in, out := &in.Data, &out.Data 28 | *out = make([]Data, len(*in)) 29 | copy(*out, *in) 30 | } 31 | return 32 | } 33 | 34 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapSpec. 35 | func (in *ConfigMapSpec) DeepCopy() *ConfigMapSpec { 36 | if in == nil { 37 | return nil 38 | } 39 | out := new(ConfigMapSpec) 40 | in.DeepCopyInto(out) 41 | return out 42 | } 43 | 44 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 45 | func (in *Data) DeepCopyInto(out *Data) { 46 | *out = *in 47 | return 48 | } 49 | 50 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Data. 51 | func (in *Data) DeepCopy() *Data { 52 | if in == nil { 53 | return nil 54 | } 55 | out := new(Data) 56 | in.DeepCopyInto(out) 57 | return out 58 | } 59 | 60 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 61 | func (in *DockerConfig) DeepCopyInto(out *DockerConfig) { 62 | *out = *in 63 | return 64 | } 65 | 66 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DockerConfig. 67 | func (in *DockerConfig) DeepCopy() *DockerConfig { 68 | if in == nil { 69 | return nil 70 | } 71 | out := new(DockerConfig) 72 | in.DeepCopyInto(out) 73 | return out 74 | } 75 | 76 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 77 | func (in *GenerateConfiguration) DeepCopyInto(out *GenerateConfiguration) { 78 | *out = *in 79 | out.TypeMeta = in.TypeMeta 80 | if in.Secrets != nil { 81 | in, out := &in.Secrets, &out.Secrets 82 | *out = make([]SecretSpec, len(*in)) 83 | for i := range *in { 84 | (*in)[i].DeepCopyInto(&(*out)[i]) 85 | } 86 | } 87 | if in.ConfigMaps != nil { 88 | in, out := &in.ConfigMaps, &out.ConfigMaps 89 | *out = make([]ConfigMapSpec, len(*in)) 90 | for i := range *in { 91 | (*in)[i].DeepCopyInto(&(*out)[i]) 92 | } 93 | } 94 | return 95 | } 96 | 97 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenerateConfiguration. 98 | func (in *GenerateConfiguration) DeepCopy() *GenerateConfiguration { 99 | if in == nil { 100 | return nil 101 | } 102 | out := new(GenerateConfiguration) 103 | in.DeepCopyInto(out) 104 | return out 105 | } 106 | 107 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 108 | func (in *SecretSpec) DeepCopyInto(out *SecretSpec) { 109 | *out = *in 110 | if in.TLS != nil { 111 | in, out := &in.TLS, &out.TLS 112 | *out = new(TLS) 113 | (*in).DeepCopyInto(*out) 114 | } 115 | if in.Docker != nil { 116 | in, out := &in.Docker, &out.Docker 117 | *out = new(DockerConfig) 118 | **out = **in 119 | } 120 | if in.Data != nil { 121 | in, out := &in.Data, &out.Data 122 | *out = make([]Data, len(*in)) 123 | copy(*out, *in) 124 | } 125 | return 126 | } 127 | 128 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretSpec. 129 | func (in *SecretSpec) DeepCopy() *SecretSpec { 130 | if in == nil { 131 | return nil 132 | } 133 | out := new(SecretSpec) 134 | in.DeepCopyInto(out) 135 | return out 136 | } 137 | 138 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 139 | func (in *TLS) DeepCopyInto(out *TLS) { 140 | *out = *in 141 | if in.Cert != nil { 142 | in, out := &in.Cert, &out.Cert 143 | *out = new(TLSData) 144 | **out = **in 145 | } 146 | if in.Key != nil { 147 | in, out := &in.Key, &out.Key 148 | *out = new(TLSData) 149 | **out = **in 150 | } 151 | return 152 | } 153 | 154 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLS. 155 | func (in *TLS) DeepCopy() *TLS { 156 | if in == nil { 157 | return nil 158 | } 159 | out := new(TLS) 160 | in.DeepCopyInto(out) 161 | return out 162 | } 163 | 164 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 165 | func (in *TLSData) DeepCopyInto(out *TLSData) { 166 | *out = *in 167 | return 168 | } 169 | 170 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSData. 171 | func (in *TLSData) DeepCopy() *TLSData { 172 | if in == nil { 173 | return nil 174 | } 175 | out := new(TLSData) 176 | in.DeepCopyInto(out) 177 | return out 178 | } 179 | -------------------------------------------------------------------------------- /docs/20_setup.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | ## Installation 4 | 5 | `mlp` can be installed in different ways, you can choose the one that better fits your needs and the operating system 6 | you are using: 7 | 8 | - [Linux and MacOs](#linux-and-macos) 9 | - [Homebrew](#homebrew) 10 | - [Go](#go) 11 | - [Binary Download](#binary-download) 12 | - [Docker](#docker) 13 | - [Windows (with WSL)](#windows) 14 | - [Shell Autocompletion](#shell-autocompletion) 15 | 16 | ### Linux and MacOs 17 | 18 | #### Homebrew 19 | 20 | If you have [Homebrew] installed on your system `mlp` is only a command away: 21 | 22 | ```sh 23 | brew install mia-platform/tap/mlp 24 | ``` 25 | 26 | This method will also automatically setup the shell completions for `bash`, `zsh` and `fish`. 27 | 28 | #### Go 29 | 30 | If you have [Golang] installed with a version >= 1.13 in your system and you have the `$GOPATH`env set, you can 31 | install `mlp` like this: 32 | 33 | ```sh 34 | go install github.com/mia-platform/mlp@v2.5.0 35 | ``` 36 | 37 | Or like this if the `install` command is not available 38 | 39 | ```sh 40 | go get -u github.com/mia-platform/mlp@v2.5.0 41 | ``` 42 | 43 | #### Binary Download 44 | 45 | You can install `mlp` with the use of `curl` or `wget` and downloading the latest packages available on GitHub 46 | choosing the correct platform and operating system: 47 | 48 | ```sh 49 | curl -fsSL --proto '=https' --tlsv1.2 https://github.com/mia-platform/mlp/releases/download/v2.5.0/mlp-linux-amd64 -o /tmp/mlp 50 | ``` 51 | 52 | ```sh 53 | wget -q --https-only --secure-protocol=TLSv1_2 https://github.com/mia-platform/mlp/releases/download/v2.5.0/mlp-linux-amd64 -O /tmp/mlp 54 | ``` 55 | 56 | After you have downloaded the file you can validate it against the [checksum] running the command: 57 | 58 | ```sh 59 | sha256sum /tmp/mlp 60 | ``` 61 | 62 | After you have validated that the downloaded file is correct, move the binary in your `/usr/local/bin` folder 63 | 64 | ```sh 65 | sudo install -g root -o root /tmp/mlp /usr/local/bin 66 | ``` 67 | 68 | #### Docker 69 | 70 | If you want to run the cli in its environment or you want to test the cli you can use the Docker image: 71 | 72 | ```sh 73 | docker run ghcr.io/mia-platform/mlp:v2.5.0 74 | ``` 75 | 76 | ### Windows 77 | 78 | `mlp` is not directly compatible with Windows, even if you have Go installed compilation on this OS 79 | is not possible due to current technical restrictions. 80 | 81 | However, it is still possible to use `mlp` with Windows Subsystem for Linux (WSL), as explained here below. 82 | 83 | #### Installation of WSL 84 | 85 | If you don't have WSL on your system, follow the [official guide] to get it. 86 | 87 | Once WSL is installed, to open a Linux bash terminal, press Start+R, enter `bash` in the text box and press OK. 88 | 89 | #### Install `mlp` 90 | 91 | You can now install mlp with any of the methods explained above for Linux, 92 | we suggest the [binary installation](#binary-download) since it's the most straightforward. 93 | 94 | ## Shell Autocompletion 95 | 96 | If you have chosen to use an installation method different from the brew one, you will have to setup the 97 | commands autocompletion for your shell following one of these guides: 98 | 99 | - [`bash`](#bash) 100 | - [`zsh`](#zsh) 101 | - [`fish`](#fish) 102 | 103 | When you update the command remember to relaunch the command for your shell to update the completion definition 104 | and get the latest command and/or flags that has been added. 105 | 106 | ### `bash` 107 | 108 | The `bash` autocompletion needs the [`bash-completion`] package installed on your system. 109 | 110 | **Warning:** for working correctly you need the `bash-completion` V2 that is compatible only with Bash 4.1+, 111 | please be sure to have the correct versions installed on your system before running the command. 112 | 113 | ```sh 114 | mlp completion bash >/usr/local/etc/bash_completion.d/mlp 115 | ``` 116 | 117 | After done this you must restart your shell environment or launch `exec bash` for reloading the configurations 118 | and enable the autocompletion. 119 | 120 | ### `zsh` 121 | 122 | For setting up the `zsh` completion, you must enable it. You can use the following command: 123 | 124 | ```sh 125 | echo "autoload -U compinit; compinit" >> ~/.zshrc 126 | ``` 127 | 128 | Or use something like [`oh-my-zsh`] that will enable it by default. Once you have done it you can launch the 129 | following command to create the file needed by `zsh`: 130 | 131 | ```sh 132 | mlp completion zsh > /usr/local/share/zsh/site-functions/_mlp 133 | ``` 134 | 135 | After done this you must restart your shell environment or launch `exec zsh` for reloading the configurations and 136 | enable the autocompletion. 137 | 138 | ### `fish` 139 | 140 | To enable the autocompletion in `fish` you have to run this command: 141 | 142 | ```sh 143 | mlp completion fish > ~/.config/fish/completions/mlp.fish 144 | ``` 145 | 146 | After done this you must restart your shell environment or launch `exec fish` for reloading the configurations and 147 | enable the autocompletion. 148 | 149 | [Homebrew]: https://brew.sh "The Missing Package Manager for macOS (or Linux)" 150 | [Golang]: https://go.dev "Build simple, secure, scalable systems with Go" 151 | [checksum]: https://github.com/mia-platform/mlp/releases/download/v2.5.0/checksums.txt "mlp checksums" 152 | [`bash-completion`]: https://github.com/scop/bash-completion "Programmable completion functions for bash" 153 | [`oh-my-zsh`]: https://ohmyz.sh "Oh My Zsh is a delightful, open source, community-driven 154 | framework for managing your Zsh configuration" 155 | [official guide]: https://learn.microsoft.com/en-us/windows/wsl/install "How to install Linux on Windows with WSL" 156 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official email address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [report@mia-platform.eu]. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | [report@mia-platform.eu]: mailto:report@mia-platform.eu 134 | -------------------------------------------------------------------------------- /pkg/cmd/hydrate/hydrate_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package hydrate 17 | 18 | import ( 19 | "os" 20 | "path/filepath" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | "github.com/stretchr/testify/require" 25 | "sigs.k8s.io/kustomize/api/konfig" 26 | "sigs.k8s.io/kustomize/kyaml/filesys" 27 | ) 28 | 29 | func TestNewCommand(t *testing.T) { 30 | t.Parallel() 31 | 32 | tmpDir := t.TempDir() 33 | file, err := os.Create(filepath.Join(tmpDir, "Kustomization")) 34 | require.NoError(t, err) 35 | require.NoError(t, file.Close()) 36 | 37 | cmd := NewCommand() 38 | assert.NotNil(t, cmd) 39 | cmd.SetArgs([]string{tmpDir}) 40 | assert.NoError(t, cmd.Execute()) 41 | } 42 | 43 | func TestToOptions(t *testing.T) { 44 | t.Parallel() 45 | 46 | flags := &Flags{} 47 | fSys := filesys.MakeEmptyDirInMemory() 48 | o, err := flags.ToOptions(nil, fSys) 49 | require.NoError(t, err) 50 | assert.Equal(t, &Options{paths: []string{filesys.SelfDir}, fSys: fSys}, o) 51 | 52 | paths := []string{"one", "two"} 53 | o, err = flags.ToOptions(paths, fSys) 54 | require.NoError(t, err) 55 | assert.Equal(t, &Options{paths: paths, fSys: fSys}, o) 56 | } 57 | 58 | func TestRun(t *testing.T) { 59 | t.Parallel() 60 | 61 | expectedConfigurationData := `kind: Kustomization 62 | apiVersion: kustomize.config.k8s.io/v1beta1 63 | metadata: 64 | labels: 65 | app.kubernetes.io/managed-by: mlp 66 | resources: 67 | - Test4.YAML 68 | - test1.yaml 69 | - test2.yml 70 | ` 71 | expectedOverlaysData := `kind: Kustomization 72 | apiVersion: kustomize.config.k8s.io/v1beta1 73 | metadata: 74 | labels: 75 | app.kubernetes.io/managed-by: mlp 76 | patches: 77 | - path: path.yaml 78 | target: 79 | group: apps 80 | version: v1 81 | kind: Deployment 82 | - path: test4.patch.yml 83 | - path: config-file.yaml 84 | - path: test2.patch.YAML 85 | resources: 86 | - test2.patch.yaml 87 | - Test1.YAML 88 | - test3.patches.YAML 89 | ` 90 | 91 | tests := map[string]struct { 92 | paths []string 93 | expectedError string 94 | }{ 95 | "run correctly": { 96 | paths: []string{"configuration", filepath.Join("overlays", "environment")}, 97 | }, 98 | "wrong path": { 99 | paths: []string{"configurations"}, 100 | expectedError: "'configurations' doesn't exist", 101 | }, 102 | "missing kustomize file": { 103 | paths: []string{"overlays"}, 104 | expectedError: "missing kustomization file", 105 | }, 106 | "multiple kustomization files": { 107 | paths: []string{"multiple-files"}, 108 | expectedError: "found multiple kustomization file", 109 | }, 110 | } 111 | 112 | for name, test := range tests { 113 | t.Run(name, func(t *testing.T) { 114 | t.Parallel() 115 | 116 | fSys := testingInMemoryFSys(t) 117 | options := &Options{ 118 | paths: test.paths, 119 | fSys: fSys, 120 | } 121 | err := options.Run(t.Context()) 122 | switch len(test.expectedError) { 123 | case 0: 124 | assert.NoError(t, err) 125 | data, err := fSys.ReadFile(filepath.Join("configuration", konfig.DefaultKustomizationFileName())) 126 | require.NoError(t, err) 127 | assert.Equal(t, expectedConfigurationData, string(data)) 128 | data, err = fSys.ReadFile(filepath.Join("overlays", "environment", "kustomization.yml")) 129 | require.NoError(t, err) 130 | assert.Equal(t, expectedOverlaysData, string(data)) 131 | default: 132 | assert.ErrorContains(t, err, test.expectedError) 133 | } 134 | }) 135 | } 136 | } 137 | 138 | func testingInMemoryFSys(t *testing.T) filesys.FileSystem { 139 | t.Helper() 140 | overlaysFolder := filepath.Join("overlays", "environment") 141 | configurationFolder := "configuration" 142 | fSys := filesys.MakeEmptyDirInMemory() 143 | 144 | fSys.Mkdir("multiple-files") 145 | for _, kustomization := range konfig.RecognizedKustomizationFileNames() { 146 | err := fSys.WriteFile(filepath.Join("multiple-files", kustomization), []byte{}) 147 | require.NoError(t, err) 148 | } 149 | 150 | fSys.MkdirAll(overlaysFolder) 151 | fSys.Mkdir(configurationFolder) 152 | err := fSys.WriteFile(filepath.Join(configurationFolder, "test1.yaml"), []byte{}) 153 | require.NoError(t, err) 154 | err = fSys.WriteFile(filepath.Join(configurationFolder, "test2.yml"), []byte{}) 155 | require.NoError(t, err) 156 | err = fSys.WriteFile(filepath.Join(configurationFolder, "test3.json"), []byte{}) 157 | require.NoError(t, err) 158 | err = fSys.WriteFile(filepath.Join(configurationFolder, "Test4.YAML"), []byte{}) 159 | require.NoError(t, err) 160 | err = fSys.WriteFile(filepath.Join(configurationFolder, konfig.DefaultKustomizationFileName()), []byte{}) 161 | require.NoError(t, err) 162 | 163 | err = fSys.WriteFile(filepath.Join(overlaysFolder, "Test1.YAML"), []byte{}) 164 | require.NoError(t, err) 165 | err = fSys.WriteFile(filepath.Join(overlaysFolder, "test2.patch.YAML"), []byte{}) 166 | require.NoError(t, err) 167 | err = fSys.WriteFile(filepath.Join(overlaysFolder, "test3.patches.YAML"), []byte{}) 168 | require.NoError(t, err) 169 | err = fSys.WriteFile(filepath.Join(overlaysFolder, "test4.patch.yml"), []byte{}) 170 | require.NoError(t, err) 171 | err = fSys.WriteFile(filepath.Join(overlaysFolder, "config-file.yaml"), []byte{}) 172 | require.NoError(t, err) 173 | err = fSys.WriteFile(filepath.Join(overlaysFolder, "kustomization.yml"), []byte(`kind: Kustomization 174 | apiVersion: kustomize.config.k8s.io/v1beta1 175 | resources: 176 | - test2.patch.yaml 177 | patches: 178 | - path: path.yaml 179 | target: 180 | group: apps 181 | version: v1 182 | kind: Deployment 183 | - path: test4.patch.yml 184 | - path: config-file.yaml 185 | `)) 186 | require.NoError(t, err) 187 | 188 | return fSys 189 | } 190 | -------------------------------------------------------------------------------- /pkg/cmd/hydrate/hydrate.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package hydrate 17 | 18 | import ( 19 | "cmp" 20 | "context" 21 | "path/filepath" 22 | "regexp" 23 | "slices" 24 | "strings" 25 | 26 | "github.com/MakeNowJust/heredoc/v2" 27 | "github.com/go-logr/logr" 28 | "github.com/spf13/cobra" 29 | "sigs.k8s.io/kustomize/api/types" 30 | "sigs.k8s.io/kustomize/kyaml/filesys" 31 | ) 32 | 33 | const ( 34 | cmdUsage = "hydrate [DIR]..." 35 | cmdShort = "Generate 'kustomization.yaml' files with content of DIRs" 36 | cmdLong = `Generate 'kustomization.yaml' files with content of DIRs. 37 | 38 | The command will create a new 'kustomization.yaml' file if does not already exists 39 | in the target folders and then will add all the file called '*.patch.yaml' or 40 | '*.patch.yml' as patches and all the rest '*.yaml' or '*.yml' file as resources. 41 | ` 42 | cmdExamples = `# hydrate only one folder 43 | mlp hydrate configuration 44 | 45 | # hydrate multiple folders 46 | mlp hydrate configuration overlays/production 47 | 48 | # hydrate current folder 49 | mlp hydrate 50 | ` 51 | ) 52 | 53 | // Flags contains all the flags for the `hydrate` command. They will be converted to Options 54 | // that contains all runtime options for the command. 55 | type Flags struct{} 56 | 57 | // Options have the data required to perform the hydrate operation 58 | type Options struct { 59 | paths []string 60 | fSys filesys.FileSystem 61 | } 62 | 63 | // NewCommand return the command for generating kustomization files in target folders and populating the resource 64 | // property with the contents alredy present 65 | func NewCommand() *cobra.Command { 66 | flags := &Flags{} 67 | cmd := &cobra.Command{ 68 | Use: cmdUsage, 69 | Short: heredoc.Doc(cmdShort), 70 | Long: heredoc.Doc(cmdLong), 71 | Example: heredoc.Doc(cmdExamples), 72 | 73 | Run: func(cmd *cobra.Command, args []string) { 74 | o, err := flags.ToOptions(args, filesys.MakeFsOnDisk()) 75 | cobra.CheckErr(err) 76 | cobra.CheckErr(o.Run(cmd.Context())) 77 | }, 78 | } 79 | 80 | return cmd 81 | } 82 | 83 | // ToOptions transform the command flags in command runtime arguments 84 | func (*Flags) ToOptions(args []string, fSys filesys.FileSystem) (*Options, error) { 85 | var paths []string 86 | switch len(args) { 87 | case 0: 88 | paths = append(paths, filesys.SelfDir) 89 | default: 90 | paths = args 91 | } 92 | 93 | return &Options{ 94 | paths: paths, 95 | fSys: fSys, 96 | }, nil 97 | } 98 | 99 | // Run execute the hydrate command 100 | func (o *Options) Run(ctx context.Context) error { 101 | logger := logr.FromContextOrDiscard(ctx) 102 | 103 | logger.V(5).Info("hydrating files", "paths", strings.Join(o.paths, ", ")) 104 | for _, path := range o.paths { 105 | if err := o.hydrateKustomize(ctx, path); err != nil { 106 | return err 107 | } 108 | } 109 | 110 | return nil 111 | } 112 | 113 | // hydrateKustomize will read the folder at path and insert the files as resources or patches based on a regex 114 | func (o *Options) hydrateKustomize(ctx context.Context, path string) error { 115 | logger := logr.FromContextOrDiscard(ctx) 116 | 117 | logger.V(5).Info("hydrating", "path", path) 118 | files, err := o.fSys.ReadDir(path) 119 | if err != nil { 120 | return err 121 | } 122 | 123 | logger.V(8).Info("files found", "paths", strings.Join(files, ", ")) 124 | var resources []string 125 | var patches []string 126 | regex := regexp.MustCompile(`(^|\.)patch\.ya?ml$`) 127 | yamlExtensions := []string{".yaml", ".yml"} 128 | for _, file := range files { 129 | normalizedName := strings.ToLower(file) 130 | extension := filepath.Ext(normalizedName) 131 | if !slices.Contains(yamlExtensions, extension) { 132 | continue 133 | } 134 | 135 | switch regex.MatchString(normalizedName) { 136 | case true: 137 | logger.V(10).Info("patch found", "path", file) 138 | patches = append(patches, file) 139 | case false: 140 | logger.V(10).Info("resource found", "path", file) 141 | resources = append(resources, file) 142 | } 143 | } 144 | 145 | slices.SortStableFunc(resources, cmp.Compare) 146 | slices.SortStableFunc(patches, cmp.Compare) 147 | return updateKustomize(ctx, o.fSys, path, resources, patches) 148 | } 149 | 150 | // updateKustomize will read the kustomization file at path and will add resources and patches if not already 151 | // present in the file 152 | func updateKustomize(ctx context.Context, fSys filesys.FileSystem, path string, resources, patches []string) error { 153 | logger := logr.FromContextOrDiscard(ctx) 154 | 155 | kf, err := newKustomizationFile(fSys, path) 156 | if err != nil { 157 | return err 158 | } 159 | 160 | logger.V(3).Info("reading kustomization file", "path", path) 161 | k, err := kf.read() 162 | if err != nil { 163 | return err 164 | } 165 | 166 | alreadyPresentFiles := k.Resources 167 | for _, patch := range k.Patches { 168 | alreadyPresentFiles = append(alreadyPresentFiles, patch.Path) 169 | } 170 | 171 | for _, resource := range resources { 172 | if kf.GetPath() == filepath.Join(path, resource) || slices.Contains(alreadyPresentFiles, resource) { 173 | continue 174 | } 175 | logger.V(8).Info("adding resource", "path", resource) 176 | k.Resources = append(k.Resources, resource) 177 | } 178 | 179 | for _, patch := range patches { 180 | p := types.Patch{ 181 | Path: patch, 182 | } 183 | 184 | found := false 185 | for _, path := range alreadyPresentFiles { 186 | if path == patch { 187 | found = true 188 | break 189 | } 190 | } 191 | 192 | if !found { 193 | logger.V(8).Info("adding patch", "path", patch) 194 | k.Patches = append(k.Patches, p) 195 | } 196 | } 197 | 198 | // add managed by annotation to allow empty kustomization files 199 | k.MetaData = &types.ObjectMeta{Labels: map[string]string{"app.kubernetes.io/managed-by": "mlp"}} 200 | logger.V(5).Info("saving kustomization file", "path", path) 201 | return kf.write(k) 202 | } 203 | -------------------------------------------------------------------------------- /pkg/cmd/deploy/convert_inventory.go: -------------------------------------------------------------------------------- 1 | // Copyright Mia srl 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package deploy 17 | 18 | import ( 19 | "context" 20 | "encoding/json" 21 | "fmt" 22 | 23 | "github.com/mia-platform/jpl/pkg/inventory" 24 | "github.com/mia-platform/jpl/pkg/resource" 25 | "github.com/mia-platform/jpl/pkg/util" 26 | apierrors "k8s.io/apimachinery/pkg/api/errors" 27 | "k8s.io/apimachinery/pkg/api/meta" 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 30 | "k8s.io/apimachinery/pkg/runtime/schema" 31 | "k8s.io/apimachinery/pkg/util/sets" 32 | "k8s.io/client-go/kubernetes" 33 | ) 34 | 35 | const ( 36 | oldInventoryName = "resources-deployed" 37 | oldInventoryKey = "resources" 38 | ) 39 | 40 | // Inventory wrap 41 | type Inventory struct { 42 | delegate inventory.Store 43 | namespace string 44 | 45 | compatibilityMode bool 46 | 47 | clientset kubernetes.Interface 48 | mapper meta.RESTMapper 49 | } 50 | 51 | func NewInventory(factory util.ClientFactory, name, namespace, filedManager string) (inventory.Store, error) { 52 | cmInventory, err := inventory.NewConfigMapStore(factory, name, namespace, filedManager) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | clientset, err := factory.KubernetesClientSet() 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | mapper, err := factory.ToRESTMapper() 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | return &Inventory{ 68 | delegate: cmInventory, 69 | namespace: namespace, 70 | 71 | compatibilityMode: true, 72 | 73 | clientset: clientset, 74 | mapper: mapper, 75 | }, nil 76 | } 77 | 78 | func (s *Inventory) Load(ctx context.Context) (sets.Set[resource.ObjectMetadata], error) { 79 | objs, err := s.delegate.Load(ctx) 80 | if err != nil || len(objs) > 0 { 81 | s.compatibilityMode = false 82 | } 83 | 84 | if s.compatibilityMode { 85 | return s.oldInventoryObjects(ctx) 86 | } 87 | 88 | return objs, err 89 | } 90 | 91 | func (s *Inventory) Save(ctx context.Context, dryRun bool) error { 92 | if err := s.delegate.Save(ctx, dryRun); err != nil || !s.compatibilityMode { 93 | return err 94 | } 95 | 96 | return s.deleteOldInventory(ctx, dryRun) 97 | } 98 | 99 | func (s *Inventory) Delete(ctx context.Context, dryRun bool) error { 100 | return s.delegate.Delete(ctx, dryRun) 101 | } 102 | 103 | func (s *Inventory) SetObjects(objects sets.Set[*unstructured.Unstructured]) { 104 | s.delegate.SetObjects(objects) 105 | } 106 | 107 | func (s *Inventory) oldInventoryObjects(ctx context.Context) (sets.Set[resource.ObjectMetadata], error) { 108 | metadataSet := make(sets.Set[resource.ObjectMetadata], 0) 109 | sec, err := s.clientset.CoreV1().Secrets(s.namespace).Get(ctx, oldInventoryName, metav1.GetOptions{}) 110 | if err != nil { 111 | if apierrors.IsNotFound(err) { 112 | s.compatibilityMode = false 113 | return metadataSet, nil 114 | } 115 | return nil, fmt.Errorf("failed to find inventory: %w", err) 116 | } 117 | 118 | secretData := sec.Data[oldInventoryKey] 119 | resourceList := make(map[string]*resourceList) 120 | 121 | if err := json.Unmarshal(secretData, &resourceList); err != nil { 122 | var convertError error 123 | resourceList, convertError = convertSecretFormat(secretData) 124 | if convertError != nil { 125 | return nil, fmt.Errorf("error unmarshalling resource map in secret %s: %w in namespace %s. Try to convert format from v0, but fails", oldInventoryName, err, s.namespace) 126 | } 127 | } 128 | 129 | set := make(sets.Set[resource.ObjectMetadata]) 130 | for _, list := range resourceList { 131 | gvk := list.Gvk 132 | 133 | mapping, err := s.mapper.RESTMapping(gvk.GroupKind()) 134 | if err != nil { 135 | if meta.IsNoMatchError(err) { 136 | continue 137 | } 138 | return nil, err 139 | } 140 | 141 | namespace := "" 142 | if mapping.Scope == meta.RESTScopeNamespace { 143 | namespace = s.namespace 144 | } 145 | for _, name := range list.Resources { 146 | set.Insert(resource.ObjectMetadata{ 147 | Name: name, 148 | Namespace: namespace, 149 | Group: gvk.Group, 150 | Kind: gvk.Kind, 151 | }) 152 | } 153 | } 154 | 155 | return set, nil 156 | } 157 | 158 | func (s *Inventory) deleteOldInventory(ctx context.Context, dryRun bool) error { 159 | if dryRun { 160 | return nil 161 | } 162 | 163 | s.compatibilityMode = false 164 | propagation := metav1.DeletePropagationBackground 165 | opts := metav1.DeleteOptions{ 166 | PropagationPolicy: &propagation, 167 | } 168 | 169 | if err := s.clientset.CoreV1().Secrets(s.namespace).Delete(ctx, oldInventoryName, opts); err != nil && !apierrors.IsNotFound(err) { 170 | return fmt.Errorf("failed to delete inventory: %w", err) 171 | } 172 | 173 | return nil 174 | } 175 | 176 | // resourceList is the base block used to build the secret containing 177 | // the resources deployed in the cluster. 178 | type resourceList struct { 179 | Gvk schema.GroupVersionKind `json:"kind"` //nolint:tagliatelle 180 | Resources []string `json:"resources"` 181 | } 182 | 183 | // oldResourceList is the v0 of old secret base inventory 184 | type oldResourceList struct { 185 | Kind string `json:"kind"` 186 | Mapping schema.GroupVersionResource 187 | Resources []string `json:"resources"` 188 | } 189 | 190 | // Resources secrets created with helper/builer version of mlp is incompatible with newer versions 191 | // this function convert old format in the new one 192 | func convertSecretFormat(resources []byte) (map[string]*resourceList, error) { 193 | oldres := make(map[string]*oldResourceList) 194 | err := json.Unmarshal(resources, &oldres) 195 | if err != nil { 196 | return nil, err 197 | } 198 | 199 | res := make(map[string]*resourceList) 200 | 201 | for k, v := range oldres { 202 | res[k] = &resourceList{ 203 | Gvk: schema.GroupVersionKind{ 204 | Group: v.Mapping.Group, 205 | Version: v.Mapping.Version, 206 | Kind: k, 207 | }, 208 | Resources: v.Resources} 209 | } 210 | return res, nil 211 | } 212 | --------------------------------------------------------------------------------