From bd3fe1ab97375a96a0d6fe2dd572ff884cebcc36 Mon Sep 17 00:00:00 2001 From: Atze de Vries Date: Wed, 6 Dec 2023 12:26:56 +0100 Subject: [PATCH] feat: Bootstrap Mage Kubernetes library * 1:1 Copies what is in the engineering kubernetes image (https://github.com/coopnorge/engineering-docker-images/tree/main/images/devtools-kubernetes-v1beta1/context/magefiles) * Modify it to make it a library * Make it validate using golang devtools next up is adding features/tests --- .github/workflows/cicd.yaml | 41 +++++++ .pallet/gitconfig.yaml | 1 + argocd.go | 112 ++++++++++++++++++ coverage.out | 107 +++++++++++++++++ docker-compose.yaml | 23 ++++ docker-compose/Dockerfile | 12 ++ go.mod | 8 ++ go.sum | 6 + metadata.go | 47 ++++++++ targets.go | 93 +++++++++++++++ targets_test.go | 38 ++++++ template.go | 112 ++++++++++++++++++ template_test.go | 89 ++++++++++++++ tests/helm/fail/.helmignore | 23 ++++ tests/helm/fail/Chart.yaml | 24 ++++ tests/helm/fail/templates/NOTES.txt | 22 ++++ tests/helm/fail/templates/_helpers.tpl | 62 ++++++++++ tests/helm/fail/templates/deployment.yaml | 61 ++++++++++ tests/helm/fail/templates/hpa.yaml | 28 +++++ tests/helm/fail/templates/ingress.yaml | 61 ++++++++++ tests/helm/fail/templates/service.yaml | 15 +++ tests/helm/fail/templates/serviceaccount.yaml | 12 ++ .../fail/templates/tests/test-connection.yaml | 15 +++ tests/helm/fail/values.yaml | 82 +++++++++++++ tests/helm/ok/.helmignore | 23 ++++ tests/helm/ok/Chart.yaml | 24 ++++ tests/helm/ok/templates/NOTES.txt | 22 ++++ tests/helm/ok/templates/_helpers.tpl | 62 ++++++++++ tests/helm/ok/templates/deployment.yaml | 61 ++++++++++ tests/helm/ok/templates/hpa.yaml | 28 +++++ tests/helm/ok/templates/ingress.yaml | 61 ++++++++++ tests/helm/ok/templates/service.yaml | 15 +++ tests/helm/ok/templates/serviceaccount.yaml | 12 ++ .../ok/templates/tests/test-connection.yaml | 15 +++ tests/helm/ok/values.yaml | 82 +++++++++++++ tests/kustomize/base/deployment.yaml | 97 +++++++++++++++ tests/kustomize/base/externalsecret.yaml | 11 ++ tests/kustomize/base/kustomization.yaml | 10 ++ .../kustomize/base/pod-disruption-budget.yaml | 9 ++ tests/kustomize/base/service.yaml | 15 +++ tests/kustomize/base/serviceentry.yaml | 47 ++++++++ tests/kustomize/base/virtualservice.yaml | 36 ++++++ tests/kustomize/fail/deployment.yaml | 97 +++++++++++++++ tests/kustomize/fail/externalsecret.yaml | 11 ++ tests/kustomize/fail/kustomization.yaml | 10 ++ .../kustomize/fail/pod-disruption-budget.yaml | 9 ++ tests/kustomize/fail/service.yaml | 15 +++ tests/kustomize/fail/serviceentry.yaml | 47 ++++++++ tests/kustomize/fail/virtualservice.yaml | 36 ++++++ .../overlay/production/deployment.yaml | 16 +++ .../overlay/production/externalsecret.yaml | 7 ++ .../overlay/production/kustomization.yaml | 10 ++ .../overlay/production/virtualservice.yaml | 8 ++ .../kustomize/overlay/staging/deployment.yaml | 12 ++ .../overlay/staging/externalsecret.yaml | 7 ++ .../overlay/staging/kustomization.yaml | 10 ++ .../overlay/staging/virtualservice.yaml | 8 ++ tests/listfiles/a.yaml | 0 tests/listfiles/dir/b.yaml | 0 .../fail-schema/templates/deployment.yaml | 42 +++++++ .../fail-schema/templates/service.yaml | 22 ++++ .../templates/fail/templates/deployment.yaml | 47 ++++++++ tests/templates/fail/templates/service.yaml | 22 ++++ tests/templates/ok/templates/configmap.yaml | 18 +++ 64 files changed, 2178 insertions(+) create mode 100644 .github/workflows/cicd.yaml create mode 100644 argocd.go create mode 100644 coverage.out create mode 100644 docker-compose.yaml create mode 100644 docker-compose/Dockerfile create mode 100644 go.mod create mode 100644 go.sum create mode 100644 metadata.go create mode 100644 targets.go create mode 100644 targets_test.go create mode 100644 template.go create mode 100644 template_test.go create mode 100644 tests/helm/fail/.helmignore create mode 100644 tests/helm/fail/Chart.yaml create mode 100644 tests/helm/fail/templates/NOTES.txt create mode 100644 tests/helm/fail/templates/_helpers.tpl create mode 100644 tests/helm/fail/templates/deployment.yaml create mode 100644 tests/helm/fail/templates/hpa.yaml create mode 100644 tests/helm/fail/templates/ingress.yaml create mode 100644 tests/helm/fail/templates/service.yaml create mode 100644 tests/helm/fail/templates/serviceaccount.yaml create mode 100644 tests/helm/fail/templates/tests/test-connection.yaml create mode 100644 tests/helm/fail/values.yaml create mode 100644 tests/helm/ok/.helmignore create mode 100644 tests/helm/ok/Chart.yaml create mode 100644 tests/helm/ok/templates/NOTES.txt create mode 100644 tests/helm/ok/templates/_helpers.tpl create mode 100644 tests/helm/ok/templates/deployment.yaml create mode 100644 tests/helm/ok/templates/hpa.yaml create mode 100644 tests/helm/ok/templates/ingress.yaml create mode 100644 tests/helm/ok/templates/service.yaml create mode 100644 tests/helm/ok/templates/serviceaccount.yaml create mode 100644 tests/helm/ok/templates/tests/test-connection.yaml create mode 100644 tests/helm/ok/values.yaml create mode 100644 tests/kustomize/base/deployment.yaml create mode 100644 tests/kustomize/base/externalsecret.yaml create mode 100644 tests/kustomize/base/kustomization.yaml create mode 100644 tests/kustomize/base/pod-disruption-budget.yaml create mode 100644 tests/kustomize/base/service.yaml create mode 100644 tests/kustomize/base/serviceentry.yaml create mode 100644 tests/kustomize/base/virtualservice.yaml create mode 100644 tests/kustomize/fail/deployment.yaml create mode 100644 tests/kustomize/fail/externalsecret.yaml create mode 100644 tests/kustomize/fail/kustomization.yaml create mode 100644 tests/kustomize/fail/pod-disruption-budget.yaml create mode 100644 tests/kustomize/fail/service.yaml create mode 100644 tests/kustomize/fail/serviceentry.yaml create mode 100644 tests/kustomize/fail/virtualservice.yaml create mode 100644 tests/kustomize/overlay/production/deployment.yaml create mode 100644 tests/kustomize/overlay/production/externalsecret.yaml create mode 100644 tests/kustomize/overlay/production/kustomization.yaml create mode 100644 tests/kustomize/overlay/production/virtualservice.yaml create mode 100644 tests/kustomize/overlay/staging/deployment.yaml create mode 100644 tests/kustomize/overlay/staging/externalsecret.yaml create mode 100644 tests/kustomize/overlay/staging/kustomization.yaml create mode 100644 tests/kustomize/overlay/staging/virtualservice.yaml create mode 100644 tests/listfiles/a.yaml create mode 100644 tests/listfiles/dir/b.yaml create mode 100644 tests/templates/fail-schema/templates/deployment.yaml create mode 100644 tests/templates/fail-schema/templates/service.yaml create mode 100644 tests/templates/fail/templates/deployment.yaml create mode 100644 tests/templates/fail/templates/service.yaml create mode 100644 tests/templates/ok/templates/configmap.yaml diff --git a/.github/workflows/cicd.yaml b/.github/workflows/cicd.yaml new file mode 100644 index 0000000..95242b3 --- /dev/null +++ b/.github/workflows/cicd.yaml @@ -0,0 +1,41 @@ +--- +on: + pull_request: {} + push: + branches: + - main +jobs: + go-ci: + name: Go CI + runs-on: ubuntu-latest + env: + docker-compose-service: golang-devtools + XDG_CACHE_HOME: ${{ github.workspace }}/.cache/xdg + steps: + - uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - id: xdg_cache_hash + run: echo "xdg_cache_hash=${{hashFiles('./docker-compose.yml', './docker-compose/Dockerfile', './go.sum')}}" >> $GITHUB_OUTPUT + - name: Cache xdg + uses: actions/cache@v3 + with: + path: ${{ env.XDG_CACHE_HOME }} + key: xdg-${{ github.repository }}-${{ github.job }}-${{ steps.xdg_cache_hash.outputs.xdg_cache_hash }} + restore-keys: | + xdg-${{ github.repository }}-${{ github.job }}-${{ steps.xdg_cache_hash.outputs.xdg_cache_hash }} + xdg-${{ github.repository }}-${{ github.job }}- + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Configure access to internal and private GitHub repos + run: git config --global url."https://${{ secrets.REVIEWBOT_GITHUB_TOKEN }}:x-oauth-basic@github.com/coopnorge".insteadOf "https://github.com/coopnorge" + - name: Build devtools + run: docker compose build + - name: Validate + run: docker compose run --rm ${{ env.docker-compose-service }} validate VERBOSE=all diff --git a/.pallet/gitconfig.yaml b/.pallet/gitconfig.yaml index c66f1c1..0a1d40a 100644 --- a/.pallet/gitconfig.yaml +++ b/.pallet/gitconfig.yaml @@ -17,6 +17,7 @@ spec: requiredStatusChecks: checks: - "policy-bot: main" + - "Go CI" strict: false requiredPullRequestReviews: requiredApprovingReviewCount: 0 diff --git a/argocd.go b/argocd.go new file mode 100644 index 0000000..7a1a83b --- /dev/null +++ b/argocd.go @@ -0,0 +1,112 @@ +package magekubernetes + +import ( + "fmt" + "os" + "strings" + + "github.com/magefile/mage/sh" + "gopkg.in/yaml.v3" +) + +// ArgoCDAppHelm contains the info for rendering a helm file +type ArgoCDAppHelm struct { + ReleaseName string `yaml:"releaseName"` + ValueFiles []string `yaml:"valueFiles"` +} + +// ArgoCDAppSource contains the info where to find the source for rendering +type ArgoCDAppSource struct { + Helm ArgoCDAppHelm `yaml:"helm"` + Path string `yaml:"path"` +} + +// ArgoCDAppSpec contains the app source +type ArgoCDAppSpec struct { + Source ArgoCDAppSource `yaml:"source"` +} + +// ArgoCDAppMetadata contains the app name +type ArgoCDAppMetadata struct { + Name string `yaml:"name"` +} + +// ArgoCDApp contains the spec and metadata of an app +type ArgoCDApp struct { + Spec ArgoCDAppSpec `yaml:"spec"` + Metadata ArgoCDAppMetadata `yaml:"metadata"` +} + +func getArgoCDDeployments(repoURL string) ([]ArgoCDApp, error) { + var argoCDAppList []ArgoCDApp + env := map[string]string{} + + if token, ok := os.LookupEnv("ARGOCD_API_TOKEN"); ok { + server, ok := env["ARGOCD_SERVER_NAME"] + if !ok { + return nil, fmt.Errorf("When using ARGOCD_API_TOKEN, you are also required to set ARGOCD_SERVER_NAME") + } + env["ARGOCD_API_TOKEN"] = token + env["ARGOCD_SERVER_NAME"] = server + } else { + err := sh.Run("argocd", "context") + if err != nil { + fmt.Println("Make use $HOME/.argocd is correctly mounted or use ARGOCD_API_TOKEN env var") + return nil, err + } + } + + appYaml, err := sh.OutputWith(env, "argocd", "--grpc-web", "app", "list", "-r", repoURL, "-o", "yaml") + if err != nil { + return nil, err + } + err = yaml.Unmarshal([]byte(appYaml), &argoCDAppList) + if err != nil { + return nil, err + } + return argoCDAppList, nil +} + +func getArgoCDDiff(apps []ArgoCDApp) error { + env := map[string]string{"KUBECTL_EXTERNAL_DIFF": "dyff between --omit-header"} + if token, ok := os.LookupEnv("ARGOCD_API_TOKEN"); ok { + env["ARGOCD_API_TOKEN"] = token + } + for _, app := range apps { + diff, err := sh.OutputWith(env, "argocd", "--loglevel", "error", "--grpc-web", "app", "diff", app.Metadata.Name, "--refresh", "--local", app.Spec.Source.Path) + if sh.ExitStatus(err) == 2 { + return err + } + fmt.Println("---- Diff of " + app.Metadata.Name + " ----") + fmt.Println(diff) + } + return nil +} + +func listArgoCDDeployments() error { + repo, err := repoURL() + if err != nil { + return err + } + apps, err := getArgoCDDeployments(repo) + if err != nil { + return err + } + for _, trackedDeployment := range apps { + if trackedDeployment.Spec.Source.Helm.ReleaseName != "" { + fmt.Println("---") + fmt.Println("Found helm deployment with name: " + trackedDeployment.Metadata.Name) + fmt.Println(" path: " + trackedDeployment.Spec.Source.Path) + fmt.Println(" valueFiles: " + strings.Join(trackedDeployment.Spec.Source.Helm.ValueFiles, ", ")) + } else if _, err := os.Stat(trackedDeployment.Spec.Source.Path + "/kustomize.yaml"); err == nil { + fmt.Println("---") + fmt.Println("Found kustomize deployment with name: " + trackedDeployment.Metadata.Name) + fmt.Println(" path: " + trackedDeployment.Spec.Source.Path) + } else { + fmt.Println("---") + fmt.Println("Found plain deployment with name: " + trackedDeployment.Metadata.Name) + fmt.Println(" path: " + trackedDeployment.Spec.Source.Path) + } + } + return nil +} diff --git a/coverage.out b/coverage.out new file mode 100644 index 0000000..76ecff5 --- /dev/null +++ b/coverage.out @@ -0,0 +1,107 @@ +mode: atomic +mage/argocd.go:40.64,44.55 3 0 +mage/argocd.go:44.55,46.10 2 0 +mage/argocd.go:46.10,48.4 1 0 +mage/argocd.go:49.3,50.37 2 0 +mage/argocd.go:51.8,53.17 2 0 +mage/argocd.go:53.17,56.4 2 0 +mage/argocd.go:59.2,60.16 2 0 +mage/argocd.go:60.16,62.3 1 0 +mage/argocd.go:63.2,64.16 2 0 +mage/argocd.go:64.16,66.3 1 0 +mage/argocd.go:67.2,67.27 1 0 +mage/argocd.go:70.44,72.55 2 0 +mage/argocd.go:72.55,74.3 1 0 +mage/argocd.go:75.2,75.27 1 0 +mage/argocd.go:75.27,77.30 2 0 +mage/argocd.go:77.30,79.4 1 0 +mage/argocd.go:80.3,81.20 2 0 +mage/argocd.go:83.2,83.12 1 0 +mage/argocd.go:86.36,88.16 2 0 +mage/argocd.go:88.16,90.3 1 0 +mage/argocd.go:91.2,92.16 2 0 +mage/argocd.go:92.16,94.3 1 0 +mage/argocd.go:95.2,95.41 1 0 +mage/argocd.go:95.41,96.59 1 0 +mage/argocd.go:96.59,101.4 4 0 +mage/argocd.go:101.9,101.99 1 0 +mage/argocd.go:101.99,105.4 3 0 +mage/argocd.go:105.9,109.4 3 0 +mage/argocd.go:111.2,111.12 1 0 +mage/metadata.go:25.33,29.16 3 0 +mage/metadata.go:29.16,32.3 2 0 +mage/metadata.go:33.2,34.16 2 0 +mage/metadata.go:34.16,37.3 2 0 +mage/metadata.go:38.2,38.58 1 0 +mage/metadata.go:41.32,43.16 2 0 +mage/metadata.go:43.16,45.3 1 0 +mage/metadata.go:46.2,46.46 1 0 +mage/targets.go:12.23,14.16 2 0 +mage/targets.go:14.16,16.3 1 0 +mage/targets.go:17.2,20.12 4 0 +mage/targets.go:24.24,26.16 2 0 +mage/targets.go:26.16,28.3 1 0 +mage/targets.go:29.2,29.29 1 0 +mage/targets.go:33.26,35.16 2 0 +mage/targets.go:35.16,37.3 1 0 +mage/targets.go:38.2,38.31 1 0 +mage/targets.go:42.29,44.16 2 0 +mage/targets.go:44.16,46.3 1 0 +mage/targets.go:47.2,47.12 1 0 +mage/targets.go:51.25,53.16 2 0 +mage/targets.go:53.16,55.3 1 0 +mage/targets.go:56.2,57.16 2 0 +mage/targets.go:57.16,59.3 1 0 +mage/targets.go:60.2,61.16 2 0 +mage/targets.go:61.16,63.3 1 0 +mage/targets.go:64.2,64.12 1 0 +mage/targets.go:67.36,71.16 3 2 +mage/targets.go:71.16,74.3 2 1 +mage/targets.go:75.2,76.12 2 1 +mage/targets.go:79.38,87.16 3 2 +mage/targets.go:87.16,90.3 2 1 +mage/targets.go:91.2,92.12 2 1 +mage/template.go:12.52,13.44 1 0 +mage/template.go:13.44,15.3 1 0 +mage/template.go:15.8,15.88 1 0 +mage/template.go:15.88,17.3 1 0 +mage/template.go:18.2,18.34 1 0 +mage/template.go:21.57,23.16 2 2 +mage/template.go:23.16,25.3 1 0 +mage/template.go:26.2,27.16 2 2 +mage/template.go:27.16,29.3 1 0 +mage/template.go:30.2,31.16 2 2 +mage/template.go:31.16,33.3 1 0 +mage/template.go:34.2,36.16 3 2 +mage/template.go:36.16,38.3 1 1 +mage/template.go:39.2,43.16 2 1 +mage/template.go:43.16,45.3 1 0 +mage/template.go:46.2,47.16 2 1 +mage/template.go:47.16,49.3 1 0 +mage/template.go:50.2,50.17 1 1 +mage/template.go:53.51,55.16 2 2 +mage/template.go:55.16,57.3 1 0 +mage/template.go:58.2,60.16 3 2 +mage/template.go:60.16,62.3 1 1 +mage/template.go:63.2,63.17 1 1 +mage/template.go:68.40,71.16 3 0 +mage/template.go:71.16,73.3 1 0 +mage/template.go:74.2,75.16 2 0 +mage/template.go:75.16,77.3 1 0 +mage/template.go:78.2,78.41 1 0 +mage/template.go:78.41,80.17 2 0 +mage/template.go:80.17,82.4 1 0 +mage/template.go:83.3,84.17 2 0 +mage/template.go:84.17,86.4 1 0 +mage/template.go:87.3,87.40 1 0 +mage/template.go:89.2,89.38 1 0 +mage/template.go:92.58,94.82 2 1 +mage/template.go:94.82,95.20 1 4 +mage/template.go:95.20,97.4 1 2 +mage/template.go:98.3,98.13 1 4 +mage/template.go:100.2,100.16 1 1 +mage/template.go:100.16,102.3 1 0 +mage/template.go:103.2,103.19 1 1 +mage/template.go:106.32,108.16 2 5 +mage/template.go:108.16,110.3 1 0 +mage/template.go:111.2,111.17 1 5 diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..2df7ee3 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,23 @@ +services: + golang-devtools: + build: + context: docker-compose + target: golang-devtools + dockerfile: Dockerfile + privileged: false + command: validate + security_opt: + - seccomp:unconfined + - apparmor:unconfined + volumes: + - .:/srv/workspace:z + - ${DOCKER_CONFIG:-~/.docker}:/root/.docker + - ${GIT_CONFIG:-~/.gitconfig}:${GIT_CONFIG_GUEST:-/root/.gitconfig} + - ${SSH_CONFIG:-~/.ssh}:/root/.ssh + - ${XDG_CACHE_HOME:-xdg-cache-home}:/root/.cache + # ${x:-y} explained here https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html#:~:text=3.5.3%20Shell%20Parameter%20Expansion + environment: + GOMODCACHE: /root/.cache/go-mod +volumes: + xdg-cache-home: { } + nothing: { } diff --git a/docker-compose/Dockerfile b/docker-compose/Dockerfile new file mode 100644 index 0000000..6d62250 --- /dev/null +++ b/docker-compose/Dockerfile @@ -0,0 +1,12 @@ + +FROM docker.io/argoproj/argocd:v2.6.15@sha256:58ebb4ed23c8db4cd4cc3f954f8d94c4b4e3d9669c0751c484108d22b86d52de as argocd +FROM zegl/kube-score:v1.17.0@sha256:d4c34b9560d9ad35a5113fbbff6d798c87e6986a306ee097a0eb4242f9714810 as kube-score +FROM ghcr.io/yannh/kubeconform:v0.6.4@sha256:e68a0b638c6e9b76f1b7d58b4ec94340ef3b6601db25b2e40b29e3ac2d68e4bf as kubeconform + +FROM ghcr.io/coopnorge/engineering-docker-images/e0/devtools-golang-v1beta1:latest@sha256:7726a5038270ece93360e3ec0c6928ef3813eb6b11007f18bab982d42c769f01 AS golang-devtools + +COPY --from=kube-score /kube-score /usr/local/bin/kube-score +COPY --from=argocd /usr/local/bin/argocd /usr/local/bin/argocd +COPY --from=argocd /usr/local/bin/helm /usr/local/bin/helm +COPY --from=argocd /usr/local/bin/kustomize /usr/local/bin/kustomize +COPY --from=kubeconform /kubeconform /usr/local/bin/kubeconform diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9ce8061 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module mage + +go 1.20 + +require ( + github.com/magefile/mage v1.15.0 + gopkg.in/yaml.v3 v3.0.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ae302af --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= +github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/metadata.go b/metadata.go new file mode 100644 index 0000000..5a3c647 --- /dev/null +++ b/metadata.go @@ -0,0 +1,47 @@ +package magekubernetes + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +// Annotations contains the github repo slug +type Annotations struct { + ProjectSlug string `yaml:"github.com/project-slug"` +} + +// Metadata contains the annotations struct +type Metadata struct { + Annotations Annotations `yaml:"annotations"` +} + +// CatalogInfo contains the metadata of this repo +type CatalogInfo struct { + Metadata Metadata `yaml:"metadata"` +} + +func repoName() (string, error) { + var catalogInfo CatalogInfo + + yamlFile, err := os.ReadFile("catalog-info.yaml") + if err != nil { + fmt.Printf("yamlFile.Get err #%v ", err) + return "", err + } + err = yaml.Unmarshal(yamlFile, &catalogInfo) + if err != nil { + fmt.Printf("Unmarshal: %v", err) + return "", err + } + return catalogInfo.Metadata.Annotations.ProjectSlug, nil +} + +func repoURL() (string, error) { + repoName, err := repoName() + if err != nil { + return "", err + } + return "https://github.com/" + repoName, nil +} diff --git a/targets.go b/targets.go new file mode 100644 index 0000000..5448b6e --- /dev/null +++ b/targets.go @@ -0,0 +1,93 @@ +package magekubernetes + +import ( + "fmt" + "strings" + + "github.com/magefile/mage/mg" // mg contains helpful utility functions, like Deps + "github.com/magefile/mage/sh" // sh contains helpful utility functions, like RunV +) + +// Validate Kubernetes manifests for ArgCD applications related to this repository +func Validate() error { + templates, err := renderTemplates() + if err != nil { + return err + } + mg.Deps(mg.F(kubeScore, templates)) + mg.Deps(mg.F(kubeConform, templates)) + fmt.Println("Validation passed") + return nil +} + +// KubeScore runs kube-score on Kubernetes manifests for ArgCD applications related to this repository +func KubeScore() error { + templates, err := renderTemplates() + if err != nil { + return err + } + return kubeScore(templates) +} + +// KubeConform runs kubeconform on Kubernetes manifests for ArgCD applications related to this repository +func KubeConform() error { + templates, err := renderTemplates() + if err != nil { + return err + } + return kubeConform(templates) +} + +// ArgoCDListApps show the apps related to this repository +func ArgoCDListApps() error { + err := listArgoCDDeployments() + if err != nil { + return err + } + return nil +} + +// ArgoCDDiff runs a diff between local changes and the current running state in ArgoCD +func ArgoCDDiff() error { + repo, err := repoURL() + if err != nil { + return err + } + apps, err := getArgoCDDeployments(repo) + if err != nil { + return err + } + err = getArgoCDDiff(apps) + if err != nil { + return err + } + return nil +} + +func kubeScore(paths string) error { + cmdOptions := []string{ + "score"} + out, err := sh.Output("kube-score", append(cmdOptions, strings.Split(paths, ",")...)...) + if err != nil { + fmt.Printf("kube-score returned exit code: %d\n Output:\n %v Error:\n %v\n", sh.ExitStatus(err), out, err) + return err + } + fmt.Println("kube-score passed") + return nil +} + +func kubeConform(paths string) error { + cmdOptions := []string{ + "-strict", + "-verbose", + "-ignore-missing-schemas", + "-schema-location", "default", + "-schema-location", "https://raw.githubusercontent.com/coopnorge/kubernetes-schemas/main/api-production/'{{ .ResourceKind }}{{ .KindSuffix }}.json'"} + out, err := sh.Output("kubeconform", append(cmdOptions, strings.Split(paths, ",")...)...) + if err != nil { + fmt.Printf("kubeconform returned exit code: %d\n Output:\n %v Error:\n %v\n", sh.ExitStatus(err), out, err) + return err + } + fmt.Println("kubeconform passed") + return nil +} diff --git a/targets_test.go b/targets_test.go new file mode 100644 index 0000000..50a84c5 --- /dev/null +++ b/targets_test.go @@ -0,0 +1,38 @@ +package magekubernetes + +import ( + "strings" + "testing" +) + +func TestFailedKubeScore(t *testing.T) { + paths := strings.Join([]string{"tests/templates/fail/templates/deployment.yaml", "tests/templates/fail/templates/service.yaml"}, ",") + err := kubeScore(paths) + if err == nil { + t.Fatalf(`kubeScore(paths) should fail but passed`) + } +} + +func TestOKKubeScore(t *testing.T) { + paths := strings.Join([]string{"tests/templates/ok/templates/configmap.yaml"}, ",") + err := kubeScore(paths) + if err != nil { + t.Fatalf(`kubeScore(paths) should pass but failed with error %v`, err) + } +} + +func TestFailedKubeConform(t *testing.T) { + paths := strings.Join([]string{"tests/templates/fail-schema/templates/deployment.yaml", "tests/templates/fail-schema/templates/service.yaml"}, ",") + err := kubeConform(paths) + if err == nil { + t.Fatalf(`kubeConform(paths) should fail but passed`) + } +} + +func TestOKKubeConform(t *testing.T) { + paths := strings.Join([]string{"tests/templates/ok/templates/configmap.yaml"}, ",") + err := kubeConform(paths) + if err != nil { + t.Fatalf(`kubeConform(paths) should pass but failed with error %v`, err) + } +} diff --git a/template.go b/template.go new file mode 100644 index 0000000..6ce0242 --- /dev/null +++ b/template.go @@ -0,0 +1,112 @@ +package magekubernetes + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/magefile/mage/sh" // sh contains helpful utility functions, like RunV +) + +func renderTemplate(app ArgoCDApp) (string, error) { + if app.Spec.Source.Helm.ReleaseName != "" { + return renderHelm(app.Spec.Source) + } else if _, err := os.Stat(app.Spec.Source.Path + "/kustomization.yaml"); err == nil { + return renderKustomize(app.Spec.Source.Path) + } + return app.Spec.Source.Path, nil +} + +func renderHelm(source ArgoCDAppSource) (string, error) { + dir, err := tempDir() + if err != nil { + return "", err + } + pwd, err := os.Getwd() + if err != nil { + return "", err + } + err = os.Chdir(source.Path) + if err != nil { + return "", err + } + fmt.Println("rendering helm templates to: " + dir) + err = sh.Run("helm", "dependency", "build") + if err != nil { + return "", err + } + err = sh.Run("helm", "template", + "-f", strings.Join(source.Helm.ValueFiles, ","), + "--output-dir", dir, + ".") + if err != nil { + return "", err + } + err = os.Chdir(pwd) + if err != nil { + return "", err + } + return dir, nil +} + +func renderKustomize(path string) (string, error) { + dir, err := tempDir() + if err != nil { + return "", err + } + fmt.Println("rendering kustomize templates: " + dir) + err = sh.Run("kustomize", "build", path, "--output", dir) + if err != nil { + return "", err + } + return dir, nil +} + +// Render templates to an temporary directory. Using a comma sep string here because +// mg. can only have int, str and bools as arguments +func renderTemplates() (string, error) { + var files []string + repo, err := repoURL() + if err != nil { + return "", err + } + apps, err := getArgoCDDeployments(repo) + if err != nil { + return "", err + } + for _, trackedDeployment := range apps { + templates, err := renderTemplate(trackedDeployment) + if err != nil { + return "", err + } + tackedFiles, err := listFilesInDirectory(templates) + if err != nil { + return "", err + } + files = append(files, tackedFiles...) + } + return strings.Join(files, ","), nil +} + +func listFilesInDirectory(path string) ([]string, error) { + var files []string + err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + files = append(files, path) + } + return nil + }) + if err != nil { + return nil, err + } + return files, nil +} + +func tempDir() (string, error) { + dir, err := os.MkdirTemp("", "kubernetes-validation-*") + if err != nil { + return "", err + } + return dir, nil +} diff --git a/template_test.go b/template_test.go new file mode 100644 index 0000000..bb658e2 --- /dev/null +++ b/template_test.go @@ -0,0 +1,89 @@ +package magekubernetes + +import ( + "os" + "path/filepath" + "testing" +) + +func TestListFilesInDir(t *testing.T) { + dir := "tests/listfiles" + want := []string{"tests/listfiles/a.yaml", "tests/listfiles/dir/b.yaml"} + out, err := listFilesInDirectory(dir) + if !testStringSliceEq(out, want) || err != nil { + t.Fatalf(`listFilesInDirectory(dir) = %q, want match for %q`, out, want) + } +} + +func TestTempDir(t *testing.T) { + dir, err := tempDir() + _, staterr := os.Stat(dir) + if err != nil || staterr != nil { + t.Fatalf(`tempDir() failed. %v %v`, err, staterr) + } +} + +func TestOkRenderKustomize(t *testing.T) { + out, err := renderKustomize("tests/kustomize/overlay/production") + _, staterr := os.Stat(filepath.Join(out, "apps_v1_deployment_helloworld.yaml")) + if err != nil || staterr != nil { + t.Fatalf(`renderKustomize("tests/kustomize/overlay/production") returned an error %v or %v`, err, staterr) + } +} + +func TestFailRenderKustomize(t *testing.T) { + out, err := renderKustomize("tests/kustomize/fail") + if err == nil { + t.Fatalf(`renderKustomize("tests/kustomize/overlay/fail") should return an error but returned ok status %v`, out) + } +} + +func TestOkRenderHelm(t *testing.T) { + appSource := &ArgoCDAppSource{ + Path: "tests/helm/ok", + Helm: ArgoCDAppHelm{ + ReleaseName: "test", + ValueFiles: []string{"values.yaml"}, + }, + } + out, err := renderHelm(*appSource) + _, staterr := os.Stat(filepath.Join(out, "ok", "templates", "deployment.yaml")) + if err != nil || staterr != nil { + t.Fatalf(`renderHelm("tests/helm/ok") returned an error %v or %v`, err, staterr) + } +} + +func TestFailRenderHelm(t *testing.T) { + appSource := &ArgoCDAppSource{ + Path: "tests/helm/fail", + Helm: ArgoCDAppHelm{ + ReleaseName: "test", + ValueFiles: []string{"values.yaml"}, + }, + } + out, err := renderHelm(*appSource) + if err == nil { + t.Fatalf(`renderKustomize("tests/helm/fail") should return an error but returned ok status %v`, out) + } +} + +//// TestHelloEmpty calls greetings.Hello with an empty string, +//// checking for an error. +//func TestHelloEmpty(t *testing.T) { +// msg, err := Hello("") +// if msg != "" || err == nil { +// t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err) +// } +//} + +func testStringSliceEq(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} diff --git a/tests/helm/fail/.helmignore b/tests/helm/fail/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/tests/helm/fail/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/tests/helm/fail/Chart.yaml b/tests/helm/fail/Chart.yaml new file mode 100644 index 0000000..1f0d48f --- /dev/null +++ b/tests/helm/fail/Chart.yaml @@ -0,0 +1,24 @@ +apiVeron: v2 +name: fail +descrition: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: applicati + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/tests/helm/fail/templates/NOTES.txt b/tests/helm/fail/templates/NOTES.txt new file mode 100644 index 0000000..137d15d --- /dev/null +++ b/tests/helm/fail/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "fail.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "fail.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "fail.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "fail.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/tests/helm/fail/templates/_helpers.tpl b/tests/helm/fail/templates/_helpers.tpl new file mode 100644 index 0000000..fb53001 --- /dev/null +++ b/tests/helm/fail/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "fail.nameo" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "fail.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "fail.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "fail.labels" -}} +helm.sh/chart: {{ include "fail.chart" . }} +{{ include "fail.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "fail.selectorLabels" -}} +app.kubernetes.io/name: {{ include "fail.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "fail.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "fail.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/tests/helm/fail/templates/deployment.yaml b/tests/helm/fail/templates/deployment.yaml new file mode 100644 index 0000000..f952470 --- /dev/null +++ b/tests/helm/fail/templates/deployment.yaml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "fail.fullname" . }} + labels: + {{- include "fail.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "fail.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "fail.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "fail.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 80 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/tests/helm/fail/templates/hpa.yaml b/tests/helm/fail/templates/hpa.yaml new file mode 100644 index 0000000..3e7e00b --- /dev/null +++ b/tests/helm/fail/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "fail.fullname" . }} + labels: + {{- include "fail.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "fail.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/tests/helm/fail/templates/ingress.yaml b/tests/helm/fail/templates/ingress.yaml new file mode 100644 index 0000000..2b71820 --- /dev/null +++ b/tests/helm/fail/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "fail.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "fail.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/tests/helm/fail/templates/service.yaml b/tests/helm/fail/templates/service.yaml new file mode 100644 index 0000000..6985d4d --- /dev/null +++ b/tests/helm/fail/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "fail.fullname" . }} + labels: + {{- include "fail.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "fail.selectorLabels" . | nindent 4 }} diff --git a/tests/helm/fail/templates/serviceaccount.yaml b/tests/helm/fail/templates/serviceaccount.yaml new file mode 100644 index 0000000..d70ca8f --- /dev/null +++ b/tests/helm/fail/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "fail.serviceAccountName" . }} + labels: + {{- include "fail.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/tests/helm/fail/templates/tests/test-connection.yaml b/tests/helm/fail/templates/tests/test-connection.yaml new file mode 100644 index 0000000..7a9e75f --- /dev/null +++ b/tests/helm/fail/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "fail.fullname" . }}-test-connection" + labels: + {{- include "fail.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "fail.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/tests/helm/fail/values.yaml b/tests/helm/fail/values.yaml new file mode 100644 index 0000000..fbbf9cc --- /dev/null +++ b/tests/helm/fail/values.yaml @@ -0,0 +1,82 @@ +# Default values for fail. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: nginx + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/tests/helm/ok/.helmignore b/tests/helm/ok/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/tests/helm/ok/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/tests/helm/ok/Chart.yaml b/tests/helm/ok/Chart.yaml new file mode 100644 index 0000000..317539d --- /dev/null +++ b/tests/helm/ok/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: ok +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/tests/helm/ok/templates/NOTES.txt b/tests/helm/ok/templates/NOTES.txt new file mode 100644 index 0000000..82ecfe5 --- /dev/null +++ b/tests/helm/ok/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "ok.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "ok.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "ok.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "ok.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/tests/helm/ok/templates/_helpers.tpl b/tests/helm/ok/templates/_helpers.tpl new file mode 100644 index 0000000..df24464 --- /dev/null +++ b/tests/helm/ok/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "ok.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "ok.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "ok.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "ok.labels" -}} +helm.sh/chart: {{ include "ok.chart" . }} +{{ include "ok.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "ok.selectorLabels" -}} +app.kubernetes.io/name: {{ include "ok.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "ok.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "ok.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/tests/helm/ok/templates/deployment.yaml b/tests/helm/ok/templates/deployment.yaml new file mode 100644 index 0000000..dea6142 --- /dev/null +++ b/tests/helm/ok/templates/deployment.yaml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "ok.fullname" . }} + labels: + {{- include "ok.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "ok.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "ok.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "ok.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 80 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/tests/helm/ok/templates/hpa.yaml b/tests/helm/ok/templates/hpa.yaml new file mode 100644 index 0000000..f9baaac --- /dev/null +++ b/tests/helm/ok/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "ok.fullname" . }} + labels: + {{- include "ok.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "ok.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/tests/helm/ok/templates/ingress.yaml b/tests/helm/ok/templates/ingress.yaml new file mode 100644 index 0000000..9822fa0 --- /dev/null +++ b/tests/helm/ok/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "ok.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "ok.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/tests/helm/ok/templates/service.yaml b/tests/helm/ok/templates/service.yaml new file mode 100644 index 0000000..5d8c352 --- /dev/null +++ b/tests/helm/ok/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ok.fullname" . }} + labels: + {{- include "ok.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "ok.selectorLabels" . | nindent 4 }} diff --git a/tests/helm/ok/templates/serviceaccount.yaml b/tests/helm/ok/templates/serviceaccount.yaml new file mode 100644 index 0000000..0b69ab7 --- /dev/null +++ b/tests/helm/ok/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "ok.serviceAccountName" . }} + labels: + {{- include "ok.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/tests/helm/ok/templates/tests/test-connection.yaml b/tests/helm/ok/templates/tests/test-connection.yaml new file mode 100644 index 0000000..78f60b7 --- /dev/null +++ b/tests/helm/ok/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "ok.fullname" . }}-test-connection" + labels: + {{- include "ok.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "ok.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/tests/helm/ok/values.yaml b/tests/helm/ok/values.yaml new file mode 100644 index 0000000..2ce484e --- /dev/null +++ b/tests/helm/ok/values.yaml @@ -0,0 +1,82 @@ +# Default values for ok. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: nginx + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/tests/kustomize/base/deployment.yaml b/tests/kustomize/base/deployment.yaml new file mode 100644 index 0000000..81ce13b --- /dev/null +++ b/tests/kustomize/base/deployment.yaml @@ -0,0 +1,97 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: helloworld + labels: + app: helloworld + version: v1 + tags.datadoghq.com/service: "helloworld" + tags.datadoghq.com/version: "v1" + annotations: + kube-score/ignore: pod-networkpolicy, pod-probes +spec: + selector: + matchLabels: + app: helloworld + version: v1 + template: + metadata: + labels: + app: helloworld + version: v1 + tags.datadoghq.com/service: "helloworld" + tags.datadoghq.com/version: "v1" + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - helloworld + topologyKey: failure-domain.beta.kubernetes.io/zone + - weight: 80 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - helloworld + topologyKey: kubernetes.io/hostname + containers: + - name: helloworld + image: europe-docker.pkg.dev/helloworld-shared-0918/helloworld/helloworld:replaceme + env: + - name: SERVICE_PORT + value: ":3000" + - name: SHUTDOWN_TIMEOUT + value: "5s" + - name: DD_SERVICE + valueFrom: + fieldRef: + fieldPath: metadata.labels['tags.datadoghq.com/service'] + - name: DD_VERSION + valueFrom: + fieldRef: + fieldPath: metadata.labels['tags.datadoghq.com/version'] + - name: TEST_SECRET + valueFrom: + secretKeyRef: + name: helloworld-secret + key: my-kubernetes-secret-name + resources: + limits: + cpu: 400m + memory: 200Mi + ephemeral-storage: 1Gi + requests: + cpu: 100m + memory: 100Mi + ephemeral-storage: 1Gi + imagePullPolicy: Always + securityContext: + runAsGroup: 65534 + runAsUser: 65534 + readOnlyRootFilesystem: true + ports: + - containerPort: 3000 + readinessProbe: + tcpSocket: + port: 3000 + livenessProbe: + tcpSocket: + port: 3000 + volumeMounts: + - name: dsdsocket + mountPath: /var/run/datadog + readOnly: true + volumes: + - hostPath: + path: /var/run/datadog/ + name: dsdsocket diff --git a/tests/kustomize/base/externalsecret.yaml b/tests/kustomize/base/externalsecret.yaml new file mode 100644 index 0000000..085a15a --- /dev/null +++ b/tests/kustomize/base/externalsecret.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: kubernetes-client.io/v1 +kind: ExternalSecret +metadata: + name: helloworld-secret # name of the k8s external secret and the k8s secret +spec: + backendType: gcpSecretsManager + data: + - key: test # name of the GCP secret + name: my-kubernetes-secret-name # key name in the k8s secret + diff --git a/tests/kustomize/base/kustomization.yaml b/tests/kustomize/base/kustomization.yaml new file mode 100644 index 0000000..04f73ec --- /dev/null +++ b/tests/kustomize/base/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - deployment.yaml + - externalsecret.yaml + - service.yaml + - serviceentry.yaml + - virtualservice.yaml + - pod-disruption-budget.yaml diff --git a/tests/kustomize/base/pod-disruption-budget.yaml b/tests/kustomize/base/pod-disruption-budget.yaml new file mode 100644 index 0000000..db396d2 --- /dev/null +++ b/tests/kustomize/base/pod-disruption-budget.yaml @@ -0,0 +1,9 @@ +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: helloworld +spec: + minAvailable: 1 + selector: + matchLabels: + app: helloworld \ No newline at end of file diff --git a/tests/kustomize/base/service.yaml b/tests/kustomize/base/service.yaml new file mode 100644 index 0000000..86aa391 --- /dev/null +++ b/tests/kustomize/base/service.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: helloworld + labels: + app: helloworld + service: helloworld +spec: + ports: + - port: 3000 + name: grpc-helloworld + protocol: TCP + selector: + app: helloworld diff --git a/tests/kustomize/base/serviceentry.yaml b/tests/kustomize/base/serviceentry.yaml new file mode 100644 index 0000000..e07ed99 --- /dev/null +++ b/tests/kustomize/base/serviceentry.yaml @@ -0,0 +1,47 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: ServiceEntry +metadata: + name: coop +spec: + hosts: + - coop.no + location: MESH_EXTERNAL + ports: + - name: https + number: 443 + protocol: HTTPS + resolution: DNS + +--- +apiVersion: networking.istio.io/v1beta1 +kind: ServiceEntry +metadata: + name: vg +spec: + hosts: + - vg.no + - www.vg.no + location: MESH_EXTERNAL + ports: + - name: https + number: 443 + protocol: HTTPS + resolution: DNS + +--- +apiVersion: networking.istio.io/v1beta1 +kind: ServiceEntry +metadata: + name: google +spec: + hosts: + - google.no + - google.com + location: MESH_EXTERNAL + ports: + - name: https + number: 443 + protocol: HTTPS + resolution: DNS + diff --git a/tests/kustomize/base/virtualservice.yaml b/tests/kustomize/base/virtualservice.yaml new file mode 100644 index 0000000..c24d504 --- /dev/null +++ b/tests/kustomize/base/virtualservice.yaml @@ -0,0 +1,36 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: helloworld +spec: + gateways: + - istio-system/gateway-grpc + - istio-system/gateway-http + http: + - match: + - gateways: + - istio-system/gateway-grpc + uri: + prefix: /coopnorge.helloworld.v1beta1.HelloWorldAPI + route: + - destination: + host: helloworld + port: + number: 3000 + - match: + - gateways: + - istio-system/gateway-http + uri: + prefix: /helloworld/ + - gateways: + - istio-system/gateway-http + uri: + prefix: /helloworld + rewrite: + uri: "/" + route: + - destination: + host: helloworld + port: + number: 3000 \ No newline at end of file diff --git a/tests/kustomize/fail/deployment.yaml b/tests/kustomize/fail/deployment.yaml new file mode 100644 index 0000000..81ce13b --- /dev/null +++ b/tests/kustomize/fail/deployment.yaml @@ -0,0 +1,97 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: helloworld + labels: + app: helloworld + version: v1 + tags.datadoghq.com/service: "helloworld" + tags.datadoghq.com/version: "v1" + annotations: + kube-score/ignore: pod-networkpolicy, pod-probes +spec: + selector: + matchLabels: + app: helloworld + version: v1 + template: + metadata: + labels: + app: helloworld + version: v1 + tags.datadoghq.com/service: "helloworld" + tags.datadoghq.com/version: "v1" + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - helloworld + topologyKey: failure-domain.beta.kubernetes.io/zone + - weight: 80 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - helloworld + topologyKey: kubernetes.io/hostname + containers: + - name: helloworld + image: europe-docker.pkg.dev/helloworld-shared-0918/helloworld/helloworld:replaceme + env: + - name: SERVICE_PORT + value: ":3000" + - name: SHUTDOWN_TIMEOUT + value: "5s" + - name: DD_SERVICE + valueFrom: + fieldRef: + fieldPath: metadata.labels['tags.datadoghq.com/service'] + - name: DD_VERSION + valueFrom: + fieldRef: + fieldPath: metadata.labels['tags.datadoghq.com/version'] + - name: TEST_SECRET + valueFrom: + secretKeyRef: + name: helloworld-secret + key: my-kubernetes-secret-name + resources: + limits: + cpu: 400m + memory: 200Mi + ephemeral-storage: 1Gi + requests: + cpu: 100m + memory: 100Mi + ephemeral-storage: 1Gi + imagePullPolicy: Always + securityContext: + runAsGroup: 65534 + runAsUser: 65534 + readOnlyRootFilesystem: true + ports: + - containerPort: 3000 + readinessProbe: + tcpSocket: + port: 3000 + livenessProbe: + tcpSocket: + port: 3000 + volumeMounts: + - name: dsdsocket + mountPath: /var/run/datadog + readOnly: true + volumes: + - hostPath: + path: /var/run/datadog/ + name: dsdsocket diff --git a/tests/kustomize/fail/externalsecret.yaml b/tests/kustomize/fail/externalsecret.yaml new file mode 100644 index 0000000..085a15a --- /dev/null +++ b/tests/kustomize/fail/externalsecret.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: kubernetes-client.io/v1 +kind: ExternalSecret +metadata: + name: helloworld-secret # name of the k8s external secret and the k8s secret +spec: + backendType: gcpSecretsManager + data: + - key: test # name of the GCP secret + name: my-kubernetes-secret-name # key name in the k8s secret + diff --git a/tests/kustomize/fail/kustomization.yaml b/tests/kustomize/fail/kustomization.yaml new file mode 100644 index 0000000..dda3d4a --- /dev/null +++ b/tests/kustomize/fail/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kusiasdftomize.config.k8s.io/v1beta1 +kind: Kustomlkajtion + +resources: + - deployment.yaml + - externalsecret.yaml + - service.yaml + - serviceentry.yaml + - virtualservice.yaml + - pod-disruption-budget.yaml diff --git a/tests/kustomize/fail/pod-disruption-budget.yaml b/tests/kustomize/fail/pod-disruption-budget.yaml new file mode 100644 index 0000000..db396d2 --- /dev/null +++ b/tests/kustomize/fail/pod-disruption-budget.yaml @@ -0,0 +1,9 @@ +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: helloworld +spec: + minAvailable: 1 + selector: + matchLabels: + app: helloworld \ No newline at end of file diff --git a/tests/kustomize/fail/service.yaml b/tests/kustomize/fail/service.yaml new file mode 100644 index 0000000..86aa391 --- /dev/null +++ b/tests/kustomize/fail/service.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: helloworld + labels: + app: helloworld + service: helloworld +spec: + ports: + - port: 3000 + name: grpc-helloworld + protocol: TCP + selector: + app: helloworld diff --git a/tests/kustomize/fail/serviceentry.yaml b/tests/kustomize/fail/serviceentry.yaml new file mode 100644 index 0000000..e07ed99 --- /dev/null +++ b/tests/kustomize/fail/serviceentry.yaml @@ -0,0 +1,47 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: ServiceEntry +metadata: + name: coop +spec: + hosts: + - coop.no + location: MESH_EXTERNAL + ports: + - name: https + number: 443 + protocol: HTTPS + resolution: DNS + +--- +apiVersion: networking.istio.io/v1beta1 +kind: ServiceEntry +metadata: + name: vg +spec: + hosts: + - vg.no + - www.vg.no + location: MESH_EXTERNAL + ports: + - name: https + number: 443 + protocol: HTTPS + resolution: DNS + +--- +apiVersion: networking.istio.io/v1beta1 +kind: ServiceEntry +metadata: + name: google +spec: + hosts: + - google.no + - google.com + location: MESH_EXTERNAL + ports: + - name: https + number: 443 + protocol: HTTPS + resolution: DNS + diff --git a/tests/kustomize/fail/virtualservice.yaml b/tests/kustomize/fail/virtualservice.yaml new file mode 100644 index 0000000..c24d504 --- /dev/null +++ b/tests/kustomize/fail/virtualservice.yaml @@ -0,0 +1,36 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: helloworld +spec: + gateways: + - istio-system/gateway-grpc + - istio-system/gateway-http + http: + - match: + - gateways: + - istio-system/gateway-grpc + uri: + prefix: /coopnorge.helloworld.v1beta1.HelloWorldAPI + route: + - destination: + host: helloworld + port: + number: 3000 + - match: + - gateways: + - istio-system/gateway-http + uri: + prefix: /helloworld/ + - gateways: + - istio-system/gateway-http + uri: + prefix: /helloworld + rewrite: + uri: "/" + route: + - destination: + host: helloworld + port: + number: 3000 \ No newline at end of file diff --git a/tests/kustomize/overlay/production/deployment.yaml b/tests/kustomize/overlay/production/deployment.yaml new file mode 100644 index 0000000..7f24604 --- /dev/null +++ b/tests/kustomize/overlay/production/deployment.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: helloworld + labels: + tags.datadoghq.com/env: "production" +spec: + template: + metadata: + labels: + tags.datadoghq.com/env: "production" + spec: + containers: + - name: helloworld + image: europe-docker.pkg.dev/helloworld-shared-0918/helloworld/helloworld:f493f715be6ec2d34d6faf6eb732ceb7cd803f11 diff --git a/tests/kustomize/overlay/production/externalsecret.yaml b/tests/kustomize/overlay/production/externalsecret.yaml new file mode 100644 index 0000000..acc7230 --- /dev/null +++ b/tests/kustomize/overlay/production/externalsecret.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kubernetes-client.io/v1 +kind: ExternalSecret +metadata: + name: helloworld-secret # name of the k8s external secret and the k8s secret +spec: + projectId: helloworld-production-123 # project where the secret is diff --git a/tests/kustomize/overlay/production/kustomization.yaml b/tests/kustomize/overlay/production/kustomization.yaml new file mode 100644 index 0000000..b21c922 --- /dev/null +++ b/tests/kustomize/overlay/production/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../base + +patchesStrategicMerge: + - deployment.yaml + - externalsecret.yaml + - virtualservice.yaml diff --git a/tests/kustomize/overlay/production/virtualservice.yaml b/tests/kustomize/overlay/production/virtualservice.yaml new file mode 100644 index 0000000..2fa3f07 --- /dev/null +++ b/tests/kustomize/overlay/production/virtualservice.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: helloworld +spec: + hosts: + - "api.coop.no" diff --git a/tests/kustomize/overlay/staging/deployment.yaml b/tests/kustomize/overlay/staging/deployment.yaml new file mode 100644 index 0000000..82c7776 --- /dev/null +++ b/tests/kustomize/overlay/staging/deployment.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: helloworld + labels: + tags.datadoghq.com/env: "staging" +spec: + template: + metadata: + labels: + tags.datadoghq.com/env: "staging" \ No newline at end of file diff --git a/tests/kustomize/overlay/staging/externalsecret.yaml b/tests/kustomize/overlay/staging/externalsecret.yaml new file mode 100644 index 0000000..64c3e96 --- /dev/null +++ b/tests/kustomize/overlay/staging/externalsecret.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kubernetes-client.io/v1 +kind: ExternalSecret +metadata: + name: helloworld-secret # name of the k8s external secret and the k8s secret +spec: + projectId: helloworld-staging-f018 # project where the secret is diff --git a/tests/kustomize/overlay/staging/kustomization.yaml b/tests/kustomize/overlay/staging/kustomization.yaml new file mode 100644 index 0000000..b21c922 --- /dev/null +++ b/tests/kustomize/overlay/staging/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../base + +patchesStrategicMerge: + - deployment.yaml + - externalsecret.yaml + - virtualservice.yaml diff --git a/tests/kustomize/overlay/staging/virtualservice.yaml b/tests/kustomize/overlay/staging/virtualservice.yaml new file mode 100644 index 0000000..99a469f --- /dev/null +++ b/tests/kustomize/overlay/staging/virtualservice.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: helloworld +spec: + hosts: + - api.staging.coop diff --git a/tests/listfiles/a.yaml b/tests/listfiles/a.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/listfiles/dir/b.yaml b/tests/listfiles/dir/b.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/templates/fail-schema/templates/deployment.yaml b/tests/templates/fail-schema/templates/deployment.yaml new file mode 100644 index 0000000..dcb2c59 --- /dev/null +++ b/tests/templates/fail-schema/templates/deployment.yaml @@ -0,0 +1,42 @@ +--- +# Source: ok/templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: RELEASE-NAME-ok + labels: + helm.sh/chart: ok-0.1.0 + app.kubernetes.io/name: ok + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm +spec: + template: + metadata: + labels: + app.kubernetes.io/name: ok + app.kubernetes.io/instance: RELEASE-NAME + spec: + serviceAccountName: RELEASE-NAME-ok + securityContext: + {} + containers: + - name: ok + securityContext: + {} + image: "nginx:1.16.0" + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 80 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {} diff --git a/tests/templates/fail-schema/templates/service.yaml b/tests/templates/fail-schema/templates/service.yaml new file mode 100644 index 0000000..fafaaad --- /dev/null +++ b/tests/templates/fail-schema/templates/service.yaml @@ -0,0 +1,22 @@ +--- +# Source: ok/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: RELEASE-NAME-ok + labels: + helm.sh/chart: ok-0.1.0 + app.kubernetes.io/name: ok + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: ok + app.kubernetes.io/instance: RELEASE-NAME diff --git a/tests/templates/fail/templates/deployment.yaml b/tests/templates/fail/templates/deployment.yaml new file mode 100644 index 0000000..467390e --- /dev/null +++ b/tests/templates/fail/templates/deployment.yaml @@ -0,0 +1,47 @@ +--- +# Source: ok/templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: RELEASE-NAME-ok + labels: + helm.sh/chart: ok-0.1.0 + app.kubernetes.io/name: ok + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: ok + app.kubernetes.io/instance: RELEASE-NAME + template: + metadata: + labels: + app.kubernetes.io/name: ok + app.kubernetes.io/instance: RELEASE-NAME + spec: + serviceAccountName: RELEASE-NAME-ok + securityContext: + {} + containers: + - name: ok + securityContext: + {} + image: "nginx:1.16.0" + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 80 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {} diff --git a/tests/templates/fail/templates/service.yaml b/tests/templates/fail/templates/service.yaml new file mode 100644 index 0000000..fafaaad --- /dev/null +++ b/tests/templates/fail/templates/service.yaml @@ -0,0 +1,22 @@ +--- +# Source: ok/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: RELEASE-NAME-ok + labels: + helm.sh/chart: ok-0.1.0 + app.kubernetes.io/name: ok + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: ok + app.kubernetes.io/instance: RELEASE-NAME diff --git a/tests/templates/ok/templates/configmap.yaml b/tests/templates/ok/templates/configmap.yaml new file mode 100644 index 0000000..579b030 --- /dev/null +++ b/tests/templates/ok/templates/configmap.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: game-demo +data: + # property-like keys; each key maps to a simple value + player_initial_lives: "3" + ui_properties_file_name: "user-interface.properties" + + # file-like keys + game.properties: | + enemy.types=aliens,monsters + player.maximum-lives=5 + user-interface.properties: | + color.good=purple + color.bad=yellow + allow.textmode=true