diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2ce852169ca08eab97324bc3617729c9515ee950..c90da0fdc86b1b0b277e118ade011290cde45b44 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,9 @@ +workflow: + rules: + - if: $CI_MERGE_REQUEST_IID + - if: $CI_COMMIT_TAG + - if: $CI_COMMIT_BRANCH && $CI_COMMIT_REF_PROTECTED == 'true' + variables: DOCKER_DRIVER: overlay2 @@ -17,3 +23,4 @@ include: - local: .gitlab/ci/shellcheck.gitlab-ci.yml - local: .gitlab/ci/test.gitlab-ci.yml - local: .gitlab/ci/release.gitlab-ci.yml + - local: .gitlab/ci/chart.gitlab-ci.yml diff --git a/.gitlab/ci/chart.gitlab-ci.yml b/.gitlab/ci/chart.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..bbbafcafa06374d7134a1534253cf506e40ca255 --- /dev/null +++ b/.gitlab/ci/chart.gitlab-ci.yml @@ -0,0 +1,59 @@ +variables: + SAST_DISABLE_DIND: "true" + SCAN_KUBERNETES_MANIFESTS: "true" + SAST_DEFAULT_ANALYZERS: "kubesec,secrets" + +include: + - template: SAST.gitlab-ci.yml + +stages: + - build + - test + - release + +.chart-job: + image: "registry.gitlab.com/gitlab-org/gitlab-build-images:alpine-helm" + before_script: + - cd assets/auto-deploy-app + +chart:compile_manifests: + extends: .chart-job + stage: build + script: + - mkdir manifests + - helm init --client-only + - helm dependency build . + - helm template -f values.yaml --output-dir manifests . + artifacts: + paths: + - manifests + +chart:lint: + extends: .chart-job + stage: test + script: + - helm lint . + +kubesec-sast: + needs: ["chart:compile_manifests"] + +chart:test: + extends: .chart-job + stage: test + script: + - apk add --no-cache build-base go + - helm init --client-only + - helm dependency build . + - cd test && GO111MODULE=auto go test . + +# auto-deploy-image doesn't need to release the chart to https://charts.gitlab.io/, +# as it bundles a chart by default. +# release-chart: +# stage: release +# script: +# - curl --fail --request POST --form "token=${CHARTS_TRIGGER_TOKEN}" --form ref=master +# --form "variables[CHART_NAME]=$CI_PROJECT_NAME" +# --form "variables[RELEASE_REF]=$CI_COMMIT_REF_NAME" +# https://gitlab.com/api/v4/projects/2860651/trigger/pipeline +# only: +# - /\Av[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?\Z/@gitlab-org/charts/auto-deploy-app diff --git a/.gitlab/ci/release.gitlab-ci.yml b/.gitlab/ci/release.gitlab-ci.yml index 089cef1e357f26979892284d887c5031b611b37c..199072f6047caa33843498989f65fef4fab82738 100644 --- a/.gitlab/ci/release.gitlab-ci.yml +++ b/.gitlab/ci/release.gitlab-ci.yml @@ -8,13 +8,6 @@ .semantic-release: image: node:12 stage: release - before_script: - - npm install -g semantic-release @semantic-release/gitlab - script: - - semantic-release $DRY_RUN_OPT - only: - variables: - - $CI_API_V4_URL == "https://gitlab.com/api/v4" release-tag: stage: release @@ -31,24 +24,46 @@ release-tag: - docker tag "$BUILD_IMAGE_NAME" $ci_image:$ci_image_tag - docker push $ci_image:latest - docker push $ci_image:$ci_image_tag - only: - - tags + rules: + - if: $CI_COMMIT_TAG publish: extends: .semantic-release - only: - refs: - - master@gitlab-org/cluster-integration/auto-deploy-image - - beta@gitlab-org/cluster-integration/auto-deploy-image - - /^\d+\.x$/@gitlab-org/cluster-integration/auto-deploy-image - - /^\d+\.\d+\.x$/@gitlab-org/cluster-integration/auto-deploy-image + before_script: + - npm install -g semantic-release @semantic-release/gitlab + script: + - semantic-release + rules: + # Only protected branches on the official project + - if: $CI_COMMIT_BRANCH && $CI_COMMIT_REF_PROTECTED == 'true' && $CI_PROJECT_PATH == 'gitlab-org/cluster-integration/auto-deploy-image' && $CI_API_V4_URL == "https://gitlab.com/api/v4" publish-dryrun: extends: .semantic-release - variables: - DRY_RUN_OPT: '-d' - only: - - branches@gitlab-org/cluster-integration/auto-deploy-image - except: - refs: - - master + before_script: + - npm install -g semantic-release @semantic-release/gitlab + - git fetch origin refs/merge-requests/$CI_MERGE_REQUEST_IID/merge:$CI_MERGE_REQUEST_TARGET_BRANCH_NAME + - git checkout $CI_MERGE_REQUEST_TARGET_BRANCH_NAME + # Overriding the variable that semantic-release runs on. + # https://github.com/pvdlg/env-ci/blob/master/services/gitlab.js + - export CI_COMMIT_REF_NAME=$CI_MERGE_REQUEST_TARGET_BRANCH_NAME + - unset CI_MERGE_REQUEST_ID + script: + - semantic-release -d |tee output.log + # Check if the bundled chart version matches the next auto-deploy-image version. + - next_release_version=$(cat output.log | grep -oP "The next release version is \K.*$") || true + - bundled_chart_version=$(cat assets/auto-deploy-app/Chart.yaml | grep -oP "version:\s*\K.*$") + - echo "next_release_version is $next_release_version" + - echo "bundled_chart_version is $bundled_chart_version" + - | + if [ -n "${next_release_version}" ] && [ -n "${bundled_chart_version}" ] && [ "${next_release_version}" != "${bundled_chart_version}" ]; then + cat <<- EOS + [WARN] auto-deploy-app chart version mismatch error. + This merge request triggers to create a new release, auto-deploy-image ${next_release_version}. + This version must be matched to the auto-deploy-app chart's version, however, currently it's set to ${bundled_chart_version}. + Please set ${next_release_version} to the version column in assets/auto-deploy-app/Chart.yaml to resovle this error. + EOS + exit 1 + fi + rules: + - if: $CI_MERGE_REQUEST_IID && $CI_PROJECT_PATH == 'gitlab-org/cluster-integration/auto-deploy-image' && $CI_API_V4_URL == "https://gitlab.com/api/v4" + needs: [] diff --git a/.gitlab/ci/shellcheck.gitlab-ci.yml b/.gitlab/ci/shellcheck.gitlab-ci.yml index ef110ec611b6693a57475b1fd540f4d5cb245f76..7c57e9febd7ccee9062ca9f1658c89e7def474a1 100644 --- a/.gitlab/ci/shellcheck.gitlab-ci.yml +++ b/.gitlab/ci/shellcheck.gitlab-ci.yml @@ -3,7 +3,7 @@ test-shellcheck: image: koalaman/shellcheck-alpine:stable needs: [] script: - - shellcheck src/bin/auto-deploy test/* + - shellcheck src/bin/auto-deploy test/verify-application-secret test/verify-deployment-database test-shfmt: stage: test @@ -12,4 +12,4 @@ test-shfmt: entrypoint: ["/bin/sh", "-c"] needs: [] script: - - shfmt -i 2 -ci -l -d src/bin/auto-deploy test/* + - shfmt -i 2 -ci -l -d src/bin/auto-deploy test/verify-application-secret test/verify-deployment-database diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index ecbe3860e04d01585ea2a803a51bb95a7c292f4d..b29d11b4a1858a00491e67d17d70fde298c3d577 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -57,11 +57,18 @@ test-kube-domain_error: - auto-deploy check_kube_domain && expected_error || failed_as_expected test-download-chart: + <<: *test-job + script: + - auto-deploy download_chart + - ./test/verify-chart-version 1 + +test-download-chart-from-repo: <<: *test-job variables: - GIT_STRATEGY: none + AUTO_DEVOPS_CHART: gitlab/auto-deploy-app script: - auto-deploy download_chart + - ./test/verify-chart-version 0 test-deploy-name: <<: *test-job @@ -443,3 +450,30 @@ test-delete-canary-postgresql: - helm get production-canary && expected_error || failed_as_expected - helm get production - helm get production-postgresql + +test-chart-major-version-upgrade: + extends: test-deploy + script: + - auto-deploy initialize_tiller + # Downloading legacy v0 chart from charts.gitlab.io and the deployment should succeed + - AUTO_DEVOPS_CHART=gitlab/auto-deploy-app auto-deploy download_chart + - auto-deploy deploy + - rm -Rf chart + # Copying bundled chart from local storage and the deployment should fail + - auto-deploy download_chart + - "sed -i 's/version:.*/version: 10.0.0/g' chart/Chart.yaml" + - cat chart/Chart.yaml + - auto-deploy deploy| tee deploy.log || true + - grep -q "Detected a major version difference" deploy.log || exit 1 + # Force deploy with the AUTO_DEVOPS_FORCE_DEPLOY option and the deployment should succeed + - export AUTO_DEVOPS_FORCE_DEPLOY_V10=true + - auto-deploy deploy| tee deploy.log + - grep -q "allowed to force deploy" deploy.log || exit 1 + +rspec: + stage: test + image: ruby:2.5 + before_script: + - gem install rspec + script: + - rspec test/rspec diff --git a/Dockerfile b/Dockerfile index 65397efe3f30555f327fa120ac639fe4df28b05d..b89144f866cc37801e42a7dd205514f84dd66191 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,14 +6,15 @@ FROM "registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/rele # https://github.com/sgerrand/alpine-pkg-glibc ARG GLIBC_VERSION -COPY src/ build/ - # Install Dependencies RUN apk add --no-cache openssl curl tar gzip bash jq \ && curl -sSL -o /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub \ && curl -sSL -O https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VERSION}/glibc-${GLIBC_VERSION}.apk \ && apk add glibc-${GLIBC_VERSION}.apk \ - && apk add ruby jq \ + && apk add ruby jq ruby-json \ && rm glibc-${GLIBC_VERSION}.apk +COPY src/ build/ +COPY assets/ assets/ + RUN ln -s /build/bin/* /usr/local/bin/ diff --git a/README.md b/README.md index ad6524ba4cb8ab1d29d4f0a9352bd6cdf5c8dc8f..4c1561b0e996c8c80a41295e961a5d59124a7539 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,14 @@ template will use the Docker image generated from this project. Changes from pre * All the other commands should be prepended with `auto-deploy`. For example, `check_kube_domain` now becomes `auto-deploy check_kube_domain`. +### v1.0.0 + +Before v1.0.0, auto-deploy-image was downloading a chart from [the chart repository](https://charts.gitlab.io/), +which was then uploaded by the [auto-deploy-app](https://gitlab.com/gitlab-org/charts/auto-deploy-app) project. + +Since auto-deploy-image v1.0.0, the auto-deploy-app chart is bundled into the auto-deploy-image docker image as a local asset, +and it no longer downloads the chart from the repository. + # Generating a new auto-deploy image To generate a new image you must follow the git commit guidelines below, this diff --git a/assets/auto-deploy-app/CONTRIBUTING.md b/assets/auto-deploy-app/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..2354acbbc56c5570432f50fa7327eac208bba6e1 --- /dev/null +++ b/assets/auto-deploy-app/CONTRIBUTING.md @@ -0,0 +1,53 @@ +## Contributing + +Thank you for your interest in contributing to this GitLab project! We welcome +all contributions. By participating in this project, you agree to abide by the +[code of conduct](#code-of-conduct). + + +## Developer Certificate of Origin + License + +By contributing to GitLab B.V., You accept and agree to the following terms and +conditions for Your present and future Contributions submitted to GitLab B.V. +Except for the license granted herein to GitLab B.V. and recipients of software +distributed by GitLab B.V., You reserve all right, title, and interest in and to +Your Contributions. All Contributions are subject to the following DCO + License +terms. + +[DCO + License](https://gitlab.com/gitlab-org/dco/blob/master/README.md) + +_This notice should stay as the first item in the CONTRIBUTING.md file._ + +## Code of conduct + +We want to create a welcoming environment for everyone who is interested +in contributing. Please visit our [Code of Conduct +page](https://about.gitlab.com/contributing/code-of-conduct) to learn +more about our commitment to an open and welcoming environment. + +## Merge request guidelines + +Below are some guidelines for merge requests: + +- Any new configuration option should be documented in + the `Configuration` section in README.md. +- For any template changes, we encourage a test case be added or + updated in the + [template tests](https://gitlab.com/gitlab-org/charts/auto-deploy-app/-/blob/master/test/template_test.go). + +### Working with the tests + +The tests are written in [Go](https://golang.org) (version 1.13 or later, +with [modules enabled](https://golang.org/cmd/go/#hdr-Module_support)) using +the [Terratest](https://github.com/gruntwork-io/terratest) library. To work +on the tests, you need to have [Helm 2](https://v2.helm.sh/docs/) and +[Go](https://golang.org) installed. + +To run the tests, run the following commands from the root of your copy of `auto-deploy-app`: + +```shell +helm init --client-only # required only once +helm dependency build . # required only once +cd test +GO111MODULE=auto go test . # required for every change to the tests or the template +``` diff --git a/assets/auto-deploy-app/Chart.yaml b/assets/auto-deploy-app/Chart.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d6f908cffea4f5752683848ea705cc752f1067d0 --- /dev/null +++ b/assets/auto-deploy-app/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +description: GitLab's Auto-deploy Helm Chart +name: auto-deploy-app +version: 1.0.0 +icon: https://gitlab.com/gitlab-com/gitlab-artwork/raw/master/logo/logo-square.png diff --git a/assets/auto-deploy-app/LICENSE b/assets/auto-deploy-app/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..a90ea939517d05f44eb4196dcdaba0f57aab15f2 --- /dev/null +++ b/assets/auto-deploy-app/LICENSE @@ -0,0 +1,19 @@ +Copyright GitLab B.V. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/assets/auto-deploy-app/README.md b/assets/auto-deploy-app/README.md new file mode 100644 index 0000000000000000000000000000000000000000..095df11e46134d8d0a0602ea6a774e41b0375c8b --- /dev/null +++ b/assets/auto-deploy-app/README.md @@ -0,0 +1,84 @@ +# GitLab's Auto-deploy Helm Chart + +## Deprecation Notice + +GitLab is moving all development for `auto-deploy-app` into [`auto-deploy-image`](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image). +Going forward, the `auto-deploy-app` Helm chart will be bundled with `auto-deploy-image` +and will no longer released as a stand-alone Helm chart. Existing releases of `auto-deploy-app` +will remain in [GitLab's chart registry](http://charts.gitlab.io/). + +If you have any questions, please ask in <https://gitlab.com/gitlab-org/charts/auto-deploy-app/-/issues/70>. + +## Requirements + +- Helm `2.9.0` and above is required in order support `"helm.sh/hook-delete-policy": before-hook-creation` for migrations + +## Configuration + +| Parameter | Description | Default | +| --- | --- | --- | +| replicaCount | | `1` | +| strategyType | Pod deployment [strategy](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy) | `nil` | +| enableSelector | If `true`, enables selector field for the deployment. Only applicable for `extensions/v1beta1`, as selector field will always be included for `apps/v1` | `nil` | +| deploymentApiVersion | Sets `apiVersion` field for the deployment. Can be set to either `extensions/v1beta1` or `apps/v1`. | `extensions/v1beta1` | +| image.repository | | `gitlab.example.com/group/project` | +| image.tag | | `stable` | +| image.pullPolicy | | `Always` | +| image.secrets | | `[name: gitlab-registry]` | +| podAnnotations | Pod annotations | `{}` | +| application.track | | `stable` | +| application.tier | | `web` | +| application.migrateCommand | If present, this variable will run as a shell command within an application Container as a Helm pre-upgrade Hook. Intended to run migration commands. | `nil` | +| application.initializeCommand | If present, this variable will run as shell command within an application Container as a Helm post-install Hook. Intended to run database initialization commands. When set, the Deployment resource will be skipped.| `nil` | +| application.secretName | Pass in the name of a Secret which the deployment will [load all key-value pairs from the Secret as environment variables](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables) in the application container. | `nil` | +| application.secretChecksum | Pass in the checksum of the secrets referenced by `application.secretName`. | `nil` | +| hpa.enabled | If true, enables horizontal pod autoscaler. A resource request is also required to be set, such as `resources.requests.cpu: 200m`.| `false` | +| hpa.minReplicas | | `1` | +| hpa.maxReplicas | | `5` | +| hpa.targetCPUUtilizationPercentage | Percentage threshold when HPA begins scaling out pods | `80` | +| gitlab.app | GitLab project slug. | `nil` | +| gitlab.env | GitLab environment slug. | `nil` | +| gitlab.envName | GitLab environment name. | `nil` | +| gitlab.envURL | GitLab environment URL. | `nil` | +| service.enabled | | `true` | +| service.annotations | Service annotations | `{}` | +| service.name | | `web` | +| service.type | | `ClusterIP` | +| service.url | | `http://my.host.com/` | +| service.additionalHosts | If present, this list will add additional hostnames to the server configuration. | `nil` | +| service.commonName | If present, this will define the ssl certificate common name to be used by CertManager. `service.url` and `service.additionalHosts` will be added as Subject Alternative Names (SANs) | `nil` | +| service.externalPort | | `5000` | +| service.internalPort | | `5000` | +| ingress.enabled | If true, enables ingress | `true` | +| ingress.tls.enabled | If true, enables SSL | `true` | +| ingress.tls.secretName | Name of the secret used to terminate SSL traffic | `""` | +| ingress.modSecurity.enabled | Enable custom configuration for modsecurity, defaulting to [the Core Rule Set](https://coreruleset.org) | `false` | +| ingress.modSecurity.secRuleEngine | Configuration for [ModSecurity's rule engine](https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-(v2.x)#SecRuleEngine) | `DetectionOnly` | +| ingress.modSecurity.secRules | Configuration for custom [ModSecurity's rules](https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-(v2.x)#secrule) | `nil` | +| ingress.annotations | Ingress annotations | `{kubernetes.io/tls-acme: "true", kubernetes.io/ingress.class: "nginx"}` | +| livenessProbe.path | Path to access on the HTTP server on periodic probe of container liveness. | `/` | +| livenessProbe.scheme | Scheme to access the HTTP server (HTTP or HTTPS). | `HTTP` | +| livenessProbe.initialDelaySeconds | # of seconds after the container has started before liveness probes are initiated. | `15` | +| livenessProbe.timeoutSeconds | # of seconds after which the liveness probe times out. | `15` | +| livenessProbe.probeType | Type of [liveness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes) to use. | `httpGet` +| livenessProbe.command | Commands for use with probe type 'exec'. | `{}` +| readinessProbe.path | Path to access on the HTTP server on periodic probe of container readiness. | `/` | +| readinessProbe.scheme | Scheme to access the HTTP server (HTTP or HTTPS). | `HTTP` | +| readinessProbe.initialDelaySeconds | # of seconds after the container has started before readiness probes are initiated. | `5` | +| readinessProbe.timeoutSeconds | # of seconds after which the readiness probe times out. | `3` | +| readinessProbe.probeType | Type of [readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes) to use. | `httpGet` +| readinessProbe.command | Commands for use with probe type 'exec'. | `{}` +| postgresql.enabled | | `true` | +| postgresql.managed | If true, this will provision a managed Postgres instance via crossplane. | `false` | +| postgresql.managedClassSelector | This will allow provisioning a Postgres instance based on label selectors via Crossplane, eg: `managedClassSelector.matchLabels.stack: gitlab`. The `postgresql.managed` value should be true as well for this to be honoured. [Crossplane Configuration](https://docs.gitlab.com/ee/user/clusters/applications.html#crossplane) | `{}` | +| podDisruptionBudget.enabled | | `false` | +| podDisruptionBudget.maxUnavailable | | `1` | +| podDisruptionBudget.minAvailable | If present, this variable will configure minAvailable in the PodDisruptionBudget. :warning: if you have `replicaCount: 1` and `podDisruptionBudget.minAvailable: 1` `kubectl drain` will be blocked. | `nil` | +| prometheus.metrics | Annotates the service for prometheus auto-discovery. Also denies access to the `/metrics` endpoint from external addresses with Ingress. | `false` | +| networkPolicy.enabled | Enable container network policy | `false` | +| networkPolicy.spec | [Network policy](https://kubernetes.io/docs/concepts/services-networking/network-policies/) definition | `{ podSelector: { matchLabels: {} }, ingress: [{ from: [{ podSelector: { matchLabels: {} } }, { namespaceSelector: { matchLabels: { app.gitlab.com/managed_by: gitlab } } }] }] }` | + +## PostgreSQL + +This chart depends on version 0.7.1 of the `stable/postgresql` chart. +For reference the source code for this specific version can be found at https://github.com/helm/charts/tree/b90ad657e1a226eb52c3eb6a2a95ba3d6d494f58/stable/postgresql \ No newline at end of file diff --git a/assets/auto-deploy-app/requirements.lock b/assets/auto-deploy-app/requirements.lock new file mode 100644 index 0000000000000000000000000000000000000000..c92fba599d5440f06d9929ac994ed1d1da7c8ab8 --- /dev/null +++ b/assets/auto-deploy-app/requirements.lock @@ -0,0 +1,6 @@ +dependencies: +- name: postgresql + repository: https://kubernetes-charts.storage.googleapis.com/ + version: 0.7.1 +digest: sha256:358ce85fe4d3461ea6bb96713470a80de9c1324214a2e6f97d800298c02530e2 +generated: 2017-08-28T15:22:30.690341342-05:00 diff --git a/assets/auto-deploy-app/requirements.yaml b/assets/auto-deploy-app/requirements.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8d8838411e3d3f67a9e64109787ddb205681f33f --- /dev/null +++ b/assets/auto-deploy-app/requirements.yaml @@ -0,0 +1,5 @@ +dependencies: + - name: postgresql + version: "0.7.1" + repository: "https://kubernetes-charts.storage.googleapis.com/" + condition: postgresql.enabled diff --git a/assets/auto-deploy-app/templates/NOTES.txt b/assets/auto-deploy-app/templates/NOTES.txt new file mode 100644 index 0000000000000000000000000000000000000000..5491ce9318e4fd2578c6c86de79f2bd268d00739 --- /dev/null +++ b/assets/auto-deploy-app/templates/NOTES.txt @@ -0,0 +1,12 @@ +{{- if and .Values.ingress.enabled .Values.service.enabled -}} +Application should be accessible at + + {{ .Values.service.url }} +{{- else -}} +Application was deployed reusing the service at + + {{ .Values.service.url }} + +It will share a load balancer with the previous release (or be unavailable if +no service or ingress was previously deployed). +{{- end -}} diff --git a/assets/auto-deploy-app/templates/_helpers.tpl b/assets/auto-deploy-app/templates/_helpers.tpl new file mode 100644 index 0000000000000000000000000000000000000000..87545f424bb65d305dfd257a1c4e8a688ede21cc --- /dev/null +++ b/assets/auto-deploy-app/templates/_helpers.tpl @@ -0,0 +1,50 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 24 | 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). +*/}} +{{- define "fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trimSuffix "-app" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "appname" -}} +{{- $releaseName := default .Release.Name .Values.releaseOverride -}} +{{- printf "%s" $releaseName | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "imagename" -}} +{{- if eq .Values.image.tag "" -}} +{{- .Values.image.repository -}} +{{- else -}} +{{- printf "%s:%s" .Values.image.repository .Values.image.tag -}} +{{- end -}} +{{- end -}} + +{{- define "trackableappname" -}} +{{- $trackableName := printf "%s-%s" (include "appname" .) .Values.application.track -}} +{{- $trackableName | trimSuffix "-stable" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Get a hostname from URL +*/}} +{{- define "hostname" -}} +{{- . | trimPrefix "http://" | trimPrefix "https://" | trimSuffix "/" | quote -}} +{{- end -}} + +{{/* +Get SecRule's arguments with unescaped single&double quotes +*/}} +{{- define "secrule" -}} +{{- $operator := .operator | quote | replace "\"" "\\\"" | replace "'" "\\'" -}} +{{- $action := .action | quote | replace "\"" "\\\"" | replace "'" "\\'" -}} +{{- printf "SecRule %s %s %s" .variable $operator $action -}} +{{- end -}} \ No newline at end of file diff --git a/assets/auto-deploy-app/templates/db-initialize-job.yaml b/assets/auto-deploy-app/templates/db-initialize-job.yaml new file mode 100644 index 0000000000000000000000000000000000000000..535731a51a2093396cb212f1b865234dd93ec395 --- /dev/null +++ b/assets/auto-deploy-app/templates/db-initialize-job.yaml @@ -0,0 +1,43 @@ +{{- if .Values.application.initializeCommand -}} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ template "trackableappname" . }}-db-initialize + labels: + app: {{ template "appname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version| replace "+" "_" }}" + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + annotations: + "helm.sh/hook": post-install + "helm.sh/hook-delete-policy": before-hook-creation + "helm.sh/hook-weight": "0" +spec: + template: + metadata: + labels: + app: {{ template "appname" . }} + release: {{ .Release.Name }} + spec: + restartPolicy: Never + imagePullSecrets: +{{ toYaml .Values.image.secrets | indent 10 }} + containers: + - name: {{ .Chart.Name }} + image: {{ template "imagename" . }} + command: ["/bin/sh"] + args: ["-c", "{{ .Values.application.initializeCommand }}"] + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.application.secretName }} + envFrom: + - secretRef: + name: {{ .Values.application.secretName }} + {{- end }} + env: + - name: DATABASE_URL + value: {{ .Values.application.database_url | quote }} + - name: GITLAB_ENVIRONMENT_NAME + value: {{ .Values.gitlab.envName | quote }} + - name: GITLAB_ENVIRONMENT_URL + value: {{ .Values.gitlab.envURL | quote }} +{{- end -}} diff --git a/assets/auto-deploy-app/templates/db-migrate-hook.yaml b/assets/auto-deploy-app/templates/db-migrate-hook.yaml new file mode 100644 index 0000000000000000000000000000000000000000..78b871fea1421f5051acd415954d204c0316d1cf --- /dev/null +++ b/assets/auto-deploy-app/templates/db-migrate-hook.yaml @@ -0,0 +1,43 @@ +{{- if .Values.application.migrateCommand -}} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ template "trackableappname" . }}-db-migrate + labels: + app: {{ template "appname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version| replace "+" "_" }}" + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + annotations: + "helm.sh/hook": pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation + "helm.sh/hook-weight": "0" +spec: + template: + metadata: + labels: + app: {{ template "appname" . }} + release: {{ .Release.Name }} + spec: + restartPolicy: Never + imagePullSecrets: +{{ toYaml .Values.image.secrets | indent 10 }} + containers: + - name: {{ .Chart.Name }} + image: {{ template "imagename" . }} + command: ["/bin/sh"] + args: ["-c", "{{ .Values.application.migrateCommand }}"] + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.application.secretName }} + envFrom: + - secretRef: + name: {{ .Values.application.secretName }} + {{- end }} + env: + - name: DATABASE_URL + value: {{ .Values.application.database_url | quote }} + - name: GITLAB_ENVIRONMENT_NAME + value: {{ .Values.gitlab.envName | quote }} + - name: GITLAB_ENVIRONMENT_URL + value: {{ .Values.gitlab.envURL | quote }} +{{- end -}} diff --git a/assets/auto-deploy-app/templates/deployment.yaml b/assets/auto-deploy-app/templates/deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c97969a22e1e21eaaa968c1c003a03fe0f4ced52 --- /dev/null +++ b/assets/auto-deploy-app/templates/deployment.yaml @@ -0,0 +1,117 @@ +{{- if not .Values.application.initializeCommand -}} +apiVersion: {{ default "extensions/v1beta1" .Values.deploymentApiVersion }} +kind: Deployment +metadata: + name: {{ template "trackableappname" . }} + annotations: + {{ if .Values.gitlab.app }}app.gitlab.com/app: {{ .Values.gitlab.app | quote }}{{ end }} + {{ if .Values.gitlab.env }}app.gitlab.com/env: {{ .Values.gitlab.env | quote }}{{ end }} + labels: + app: {{ template "appname" . }} + track: "{{ .Values.application.track }}" + tier: "{{ .Values.application.tier }}" + chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: +{{- if or .Values.enableSelector (eq (default "extensions/v1beta1" .Values.deploymentApiVersion) "apps/v1") }} + selector: + matchLabels: + app: {{ template "appname" . }} + track: "{{ .Values.application.track }}" + tier: "{{ .Values.application.tier }}" + release: {{ .Release.Name }} +{{- end }} + replicas: {{ .Values.replicaCount }} +{{- if .Values.strategyType }} + strategy: + type: {{ .Values.strategyType | quote }} +{{- end }} + template: + metadata: + annotations: + checksum/application-secrets: "{{ .Values.application.secretChecksum }}" + {{ if .Values.gitlab.app }}app.gitlab.com/app: {{ .Values.gitlab.app | quote }}{{ end }} + {{ if .Values.gitlab.env }}app.gitlab.com/env: {{ .Values.gitlab.env | quote }}{{ end }} +{{- if .Values.podAnnotations }} +{{ toYaml .Values.podAnnotations | indent 8 }} +{{- end }} + labels: + app: {{ template "appname" . }} + track: "{{ .Values.application.track }}" + tier: "{{ .Values.application.tier }}" + release: {{ .Release.Name }} + spec: + imagePullSecrets: +{{ toYaml .Values.image.secrets | indent 10 }} + containers: + - name: {{ .Chart.Name }} + image: {{ template "imagename" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.application.secretName }} + envFrom: + - secretRef: + name: {{ .Values.application.secretName }} + {{- end }} + env: +{{- if .Values.postgresql.managed }} + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: app-postgres + key: username + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: app-postgres + key: password + - name: POSTGRES_HOST + valueFrom: + secretKeyRef: + name: app-postgres + key: privateIP +{{- end }} + - name: DATABASE_URL + value: {{ .Values.application.database_url | quote }} + - name: GITLAB_ENVIRONMENT_NAME + value: {{ .Values.gitlab.envName | quote }} + - name: GITLAB_ENVIRONMENT_URL + value: {{ .Values.gitlab.envURL | quote }} + ports: + - name: "{{ .Values.service.name }}" + containerPort: {{ .Values.service.internalPort }} + livenessProbe: +{{- if eq .Values.livenessProbe.probeType "httpGet" }} + httpGet: + path: {{ .Values.livenessProbe.path }} + scheme: {{ .Values.livenessProbe.scheme }} + port: {{ .Values.service.internalPort }} +{{- else if eq .Values.livenessProbe.probeType "tcpSocket" }} + tcpSocket: + port: {{ .Values.service.internalPort }} +{{- else if eq .Values.livenessProbe.probeType "exec" }} + exec: + command: +{{ toYaml .Values.livenessProbe.command | indent 14 }} +{{- end }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + readinessProbe: +{{- if eq .Values.readinessProbe.probeType "httpGet" }} + httpGet: + path: {{ .Values.readinessProbe.path }} + scheme: {{ .Values.readinessProbe.scheme }} + port: {{ .Values.service.internalPort }} +{{- else if eq .Values.readinessProbe.probeType "tcpSocket" }} + tcpSocket: + port: {{ .Values.service.internalPort }} +{{- else if eq .Values.readinessProbe.probeType "exec" }} + exec: + command: +{{ toYaml .Values.readinessProbe.command | indent 14 }} +{{- end }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + resources: +{{ toYaml .Values.resources | indent 12 }} +{{- end -}} diff --git a/assets/auto-deploy-app/templates/hpa.yaml b/assets/auto-deploy-app/templates/hpa.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f4cb3adea3b5813a5d3f2b5a30eb82fe84d6a0c1 --- /dev/null +++ b/assets/auto-deploy-app/templates/hpa.yaml @@ -0,0 +1,19 @@ +{{- if and .Values.hpa.enabled .Values.resources.requests -}} +apiVersion: autoscaling/v1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ template "fullname" . }} + labels: + app: {{ template "appname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + scaleTargetRef: + kind: Deployment + name: {{ template "appname" . }} + apiVersion: apps/v1beta1 + minReplicas: {{ .Values.hpa.minReplicas }} + maxReplicas: {{ .Values.hpa.maxReplicas }} + targetCPUUtilizationPercentage: {{ .Values.hpa.targetCPUUtilizationPercentage }} +{{- end -}} diff --git a/assets/auto-deploy-app/templates/ingress.yaml b/assets/auto-deploy-app/templates/ingress.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6f39db2cecfe8d61a2e31bf2fae929966cf0d51c --- /dev/null +++ b/assets/auto-deploy-app/templates/ingress.yaml @@ -0,0 +1,68 @@ +{{- if and (.Values.service.enabled) (eq .Values.application.track "stable") (or (.Values.ingress.enabled) (not (hasKey .Values.ingress "enabled"))) -}} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ template "fullname" . }} + labels: + app: {{ template "appname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version| replace "+" "_" }}" + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + annotations: +{{- if .Values.ingress.annotations }} +{{ toYaml .Values.ingress.annotations | indent 4 }} +{{- end }} +{{- with .Values.ingress.modSecurity }} +{{- if .enabled }} + nginx.ingress.kubernetes.io/modsecurity-transaction-id: "$server_name-$request_id" + nginx.ingress.kubernetes.io/modsecurity-snippet: | + SecRuleEngine {{ .secRuleEngine | default "DetectionOnly" | title }} +{{- range $rule := .secRules }} +{{ (include "secrule" $rule) | indent 6 }} +{{- end }} +{{- end }} +{{- end }} +{{- if .Values.prometheus.metrics }} + nginx.ingress.kubernetes.io/server-snippet: |- + location /metrics { + deny all; + } + +{{- end }} +spec: +{{- if .Values.ingress.tls.enabled }} + tls: + - hosts: +{{- if .Values.service.commonName }} + - {{ template "hostname" .Values.service.commonName }} +{{- end }} + - {{ template "hostname" .Values.service.url }} +{{- if .Values.service.additionalHosts }} +{{- range $host := .Values.service.additionalHosts }} + - {{ $host }} +{{- end -}} +{{- end }} + secretName: {{ .Values.ingress.tls.secretName | default (printf "%s-tls" (include "fullname" .)) }} +{{- end }} + rules: + - host: {{ template "hostname" .Values.service.url }} + http: + &httpRule + paths: + - path: / + backend: + serviceName: {{ template "fullname" . }} + servicePort: {{ .Values.service.externalPort }} +{{- if .Values.service.commonName }} + - host: {{ template "hostname" .Values.service.commonName }} + http: + <<: *httpRule +{{- end -}} +{{- if .Values.service.additionalHosts }} +{{- range $host := .Values.service.additionalHosts }} + - host: {{ $host }} + http: + <<: *httpRule +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/assets/auto-deploy-app/templates/network-policy.yaml b/assets/auto-deploy-app/templates/network-policy.yaml new file mode 100644 index 0000000000000000000000000000000000000000..79c0ed662514d8deb99bb19382105f8f445ce288 --- /dev/null +++ b/assets/auto-deploy-app/templates/network-policy.yaml @@ -0,0 +1,13 @@ +{{- if .Values.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ template "fullname" . }} + labels: + app: {{ template "appname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: +{{ toYaml .Values.networkPolicy.spec | indent 2 }} +{{- end -}} diff --git a/assets/auto-deploy-app/templates/pdb.yaml b/assets/auto-deploy-app/templates/pdb.yaml new file mode 100644 index 0000000000000000000000000000000000000000..69bb2ed738e404493b6a450eb4244872409113a1 --- /dev/null +++ b/assets/auto-deploy-app/templates/pdb.yaml @@ -0,0 +1,22 @@ +{{- if .Values.podDisruptionBudget.enabled }} +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: {{ template "fullname" . }} + labels: + app: {{ template "appname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: +{{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} +{{- end }} +{{- if .Values.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} +{{- end }} + selector: + matchLabels: + app: {{ template "appname" . }} + release: {{ .Release.Name }} +{{- end }} diff --git a/assets/auto-deploy-app/templates/postgres-instance.yaml b/assets/auto-deploy-app/templates/postgres-instance.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3fbeb73068a61a18772b390e5ed2c5a932b1fdda --- /dev/null +++ b/assets/auto-deploy-app/templates/postgres-instance.yaml @@ -0,0 +1,14 @@ +{{- if .Values.postgresql.managed -}} +apiVersion: database.crossplane.io/v1alpha1 +kind: PostgreSQLInstance +metadata: + name: {{ template "appname" . }} +spec: + engineVersion: "9.6" + writeConnectionSecretToRef: + name: app-postgres +{{- if .Values.postgresql.managedClassSelector }} + classSelector: +{{ toYaml .Values.postgresql.managedClassSelector | indent 4 }} +{{- end }} +{{- end -}} diff --git a/assets/auto-deploy-app/templates/service.yaml b/assets/auto-deploy-app/templates/service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..676406be11de5feedd801876627055405ac51670 --- /dev/null +++ b/assets/auto-deploy-app/templates/service.yaml @@ -0,0 +1,29 @@ +{{- if and (.Values.service.enabled) (eq .Values.application.track "stable") -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "fullname" . }} + annotations: +{{- if .Values.service.annotations }} +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} +{{- if .Values.prometheus.metrics }} + prometheus.io/scrape: "true" + prometheus.io/port: "{{ .Values.service.internalPort }}" +{{- end }} + labels: + app: {{ template "appname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.externalPort }} + targetPort: {{ .Values.service.internalPort }} + protocol: TCP + name: {{ .Values.service.name }} + selector: + app: {{ template "appname" . }} + tier: "{{ .Values.application.tier }}" +{{- end -}} diff --git a/assets/auto-deploy-app/templates/worker-deployment.yaml b/assets/auto-deploy-app/templates/worker-deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..050e234195e1064dc1ace110946719a1444aded2 --- /dev/null +++ b/assets/auto-deploy-app/templates/worker-deployment.yaml @@ -0,0 +1,103 @@ +{{- if and (not .Values.application.initializeCommand) .Values.workers -}} +apiVersion: v1 +kind: List +items: +{{- range $workerName, $workerConfig := .Values.workers }} +- apiVersion: {{ default "extensions/v1beta1" $.Values.deploymentApiVersion }} + kind: Deployment + metadata: + name: {{ template "trackableappname" $ }}-{{ $workerName }} + annotations: + {{ if $.Values.gitlab.app }}app.gitlab.com/app: {{ $.Values.gitlab.app | quote }}{{ end }} + {{ if $.Values.gitlab.env }}app.gitlab.com/env: {{ $.Values.gitlab.env | quote }}{{ end }} + labels: + track: "{{ $.Values.application.track }}" + tier: worker + chart: "{{ $.Chart.Name }}-{{ $.Chart.Version | replace "+" "_" }}" + release: {{ $.Release.Name }} + heritage: {{ $.Release.Service }} + spec: + {{- if or $.Values.enableSelector (eq (default "extensions/v1beta1" $.Values.deploymentApiVersion) "apps/v1") }} + selector: + matchLabels: + track: "{{ $.Values.application.track }}" + tier: worker + release: {{ $.Release.Name }} + {{- end }} + replicas: {{ $workerConfig.replicaCount }} + {{- if $workerConfig.strategyType }} + strategy: + type: {{ $workerConfig.strategyType | quote }} + {{- end }} + template: + metadata: + annotations: + checksum/application-secrets: "{{ $.Values.application.secretChecksum }}" + {{ if $.Values.gitlab.app }}app.gitlab.com/app: {{ $.Values.gitlab.app | quote }}{{ end }} + {{ if $.Values.gitlab.env }}app.gitlab.com/env: {{ $.Values.gitlab.env | quote }}{{ end }} + {{- if $.Values.podAnnotations }} + {{ toYaml $.Values.podAnnotations | indent 10 }} + {{- end }} + labels: + track: "{{ $.Values.application.track }}" + tier: worker + release: {{ $.Release.Name }} + spec: + imagePullSecrets: + {{ toYaml $.Values.image.secrets | indent 12 }} + terminationGracePeriodSeconds: {{ $workerConfig.terminationGracePeriodSeconds }} + containers: + - name: {{ $.Chart.Name }}-{{ $workerName }} + image: {{ template "imagename" $ }} + command: + {{- range $workerConfig.command }} + - {{ . }} + {{- end }} + imagePullPolicy: {{ $.Values.image.pullPolicy }} + {{- if $.Values.application.secretName }} + envFrom: + - secretRef: + name: {{ $.Values.application.secretName }} + {{- end }} + env: + - name: DATABASE_URL + value: {{ $.Values.application.database_url | quote }} + - name: GITLAB_ENVIRONMENT_NAME + value: {{ $.Values.gitlab.envName | quote }} + livenessProbe: +{{- if eq $.Values.livenessProbe.probeType "httpGet" }} + httpGet: + path: {{ $.Values.livenessProbe.path }} + scheme: {{ $.Values.livenessProbe.scheme }} + port: {{ $.Values.service.internalPort }} +{{- else if eq $.Values.livenessProbe.probeType "tcpSocket" }} + tcpSocket: + port: {{ $.Values.service.internalPort }} +{{- end }} + initialDelaySeconds: {{ $.Values.livenessProbe.initialDelaySeconds }} + timeoutSeconds: {{ $.Values.livenessProbe.timeoutSeconds }} + readinessProbe: +{{- if eq $.Values.readinessProbe.probeType "httpGet" }} + httpGet: + path: {{ $.Values.readinessProbe.path }} + scheme: {{ $.Values.readinessProbe.scheme }} + port: {{ $.Values.service.internalPort }} +{{- else if eq $.Values.readinessProbe.probeType "tcpSocket" }} + tcpSocket: + port: {{ $.Values.service.internalPort }} +{{- end }} + initialDelaySeconds: {{ $.Values.readinessProbe.initialDelaySeconds }} + timeoutSeconds: {{ $.Values.readinessProbe.timeoutSeconds }} + {{- if $workerConfig.preStopCommand }} + lifecycle: + preStop: + exec: + command: + {{- range $workerConfig.preStopCommand }} + - {{ . }} + {{- end }} + {{- end }} + resources: +{{ toYaml $.Values.resources | indent 12 }} +{{- end -}} +{{- end -}} diff --git a/assets/auto-deploy-app/test/go.mod b/assets/auto-deploy-app/test/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..4754f016d43ac382520197869527dc51191fb013 --- /dev/null +++ b/assets/auto-deploy-app/test/go.mod @@ -0,0 +1,10 @@ +module gitlab.com/gitlab-org/charts/auto-deploy-app/test + +go 1.13 + +require ( + github.com/gruntwork-io/terratest v0.23.0 + github.com/stretchr/testify v1.4.0 + k8s.io/api v0.0.0-20181110191121-a33c8200050f + k8s.io/apimachinery v0.0.0-20190704094520-6f131bee5e2c +) diff --git a/assets/auto-deploy-app/test/go.sum b/assets/auto-deploy-app/test/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..8b5b570079168ea0282e9f6e0a576479efc4fd86 --- /dev/null +++ b/assets/auto-deploy-app/test/go.sum @@ -0,0 +1,218 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +github.com/Azure/azure-sdk-for-go v32.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.1/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.6.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/azure/auth v0.3.0/go.mod h1:CI4BQYBct8NS7BXNBBX+RchsFsUu5+oz+OSyR/ZIi7U= +github.com/Azure/go-autorest/autorest/azure/cli v0.3.0/go.mod h1:rNYMNAefZMRowqCV0cVhr/YDW5dD7afFq9nXAXL4ykE= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/aws/aws-sdk-go v1.23.8 h1:G/azJoBN0pnhB3B+0eeC4yyVFYIIad6bbzg6wwtImqk= +github.com/aws/aws-sdk-go v1.23.8/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s= +github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1 h1:yY9rWGoXv1U5pl4gxqlULARMQD7x0QG85lqEXTWysik= +github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 h1:skJKxRtNmevLqnayafdLe2AsenqRupVmzZSqrvb5caU= +github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c h1:jWtZjFEUE/Bz0IeIhqCnyZ3HG6KRXSntXe4SjtuTH7c= +github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= +github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gruntwork-io/gruntwork-cli v0.5.1 h1:mVmVsFubUSLSCO8bGigI63HXzvzkC0uWXzm4dd9pXRg= +github.com/gruntwork-io/gruntwork-cli v0.5.1/go.mod h1:IBX21bESC1/LGoV7jhXKUnTQTZgQ6dYRsoj/VqxUSZQ= +github.com/gruntwork-io/terratest v0.23.0 h1:JmGeqO0r5zRLAV55T67NEmPZArz9lN3RKd0moAKhIT4= +github.com/gruntwork-io/terratest v0.23.0/go.mod h1:+fVff0FQYuRzCF3LKpKF9ac+4w384LDcwLZt7O/KmEE= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= +github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/oracle/oci-go-sdk v7.1.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= +github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191110163157-d32e6e3b99c4 h1:Hynbrlo6LbYI3H1IqXpkVDOcX/3HiPdhVEuyj5a59RM= +golang.org/x/sys v0.0.0-20191110163157-d32e6e3b99c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.9.1-0.20190821000710-329ecc3c9c34/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.0.0-20181110191121-a33c8200050f h1:BH667AnNr487/iTtY35X+m6c2S8HL02Rft1PFK93kmw= +k8s.io/api v0.0.0-20181110191121-a33c8200050f/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/apimachinery v0.0.0-20190704094520-6f131bee5e2c h1:vdEIiO5B0/3EVwZboF6qyYn5kVDdvCbaGSzr7Rcx18A= +k8s.io/apimachinery v0.0.0-20190704094520-6f131bee5e2c/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/client-go v0.0.0-20190704095228-386e588352a4 h1:hqylj4/yit+/eO496/Yhgy2YxxumFpSY94YDFX6lBoU= +k8s.io/client-go v0.0.0-20190704095228-386e588352a4/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= diff --git a/assets/auto-deploy-app/test/template_test.go b/assets/auto-deploy-app/test/template_test.go new file mode 100644 index 0000000000000000000000000000000000000000..71ca24ed17bb142e53dc260d508ab698b1691584 --- /dev/null +++ b/assets/auto-deploy-app/test/template_test.go @@ -0,0 +1,845 @@ +package main + +import ( + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/k8s" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + appsV1 "k8s.io/api/apps/v1" + coreV1 "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" + netV1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + chartName = "auto-deploy-app-1.0.0" + helmChartPath = ".." +) + +func TestDeploymentTemplate(t *testing.T) { + for _, tc := range []struct { + CaseName string + Release string + Values map[string]string + + ExpectedName string + ExpectedRelease string + ExpectedStrategyType extensions.DeploymentStrategyType + ExpectedSelector *metav1.LabelSelector + }{ + { + CaseName: "happy", + Release: "production", + Values: map[string]string{ + "releaseOverride": "productionOverridden", + }, + ExpectedName: "productionOverridden", + ExpectedRelease: "production", + ExpectedStrategyType: extensions.DeploymentStrategyType(""), + }, + { + CaseName: "long release name", + Release: strings.Repeat("r", 80), + ExpectedName: strings.Repeat("r", 63), + ExpectedRelease: strings.Repeat("r", 80), + ExpectedStrategyType: extensions.DeploymentStrategyType(""), + }, + { + CaseName: "strategyType", + Release: "production", + Values: map[string]string{ + "strategyType": "Recreate", + }, + ExpectedName: "production", + ExpectedRelease: "production", + ExpectedStrategyType: extensions.RecreateDeploymentStrategyType, + }, + { + CaseName: "enableSelector", + Release: "production", + Values: map[string]string{ + "enableSelector": "true", + }, + ExpectedName: "production", + ExpectedRelease: "production", + ExpectedStrategyType: extensions.DeploymentStrategyType(""), + ExpectedSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "production", + "release": "production", + "tier": "web", + "track": "stable", + }, + }, + }, + } { + t.Run(tc.CaseName, func(t *testing.T) { + namespaceName := "minimal-ruby-app-" + strings.ToLower(random.UniqueId()) + + values := map[string]string{ + "gitlab.app": "auto-devops-examples/minimal-ruby-app", + "gitlab.env": "prod", + } + + mergeStringMap(values, tc.Values) + + options := &helm.Options{ + SetValues: values, + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + } + + output := helm.RenderTemplate(t, options, helmChartPath, tc.Release, []string{"templates/deployment.yaml"}) + + var deployment extensions.Deployment + helm.UnmarshalK8SYaml(t, output, &deployment) + + require.Equal(t, tc.ExpectedName, deployment.Name) + require.Equal(t, tc.ExpectedStrategyType, deployment.Spec.Strategy.Type) + + require.Equal(t, map[string]string{ + "app.gitlab.com/app": "auto-devops-examples/minimal-ruby-app", + "app.gitlab.com/env": "prod", + }, deployment.Annotations) + require.Equal(t, map[string]string{ + "app": tc.ExpectedName, + "chart": chartName, + "heritage": "Tiller", + "release": tc.ExpectedRelease, + "tier": "web", + "track": "stable", + }, deployment.Labels) + + require.Equal(t, tc.ExpectedSelector, deployment.Spec.Selector) + + require.Equal(t, map[string]string{ + "app.gitlab.com/app": "auto-devops-examples/minimal-ruby-app", + "app.gitlab.com/env": "prod", + "checksum/application-secrets": "", + }, deployment.Spec.Template.Annotations) + require.Equal(t, map[string]string{ + "app": tc.ExpectedName, + "release": tc.ExpectedRelease, + "tier": "web", + "track": "stable", + }, deployment.Spec.Template.Labels) + }) + } + + for _, tc := range []struct { + CaseName string + Release string + Values map[string]string + ExpectedImageRepository string + }{ + { + CaseName: "skaffold", + Release: "production", + Values: map[string]string{ + "image.repository": "skaffold", + "image.tag": "", + }, + ExpectedImageRepository: "skaffold", + }, + { + CaseName: "skaffold", + Release: "production", + Values: map[string]string{ + "image.repository": "skaffold", + "image.tag": "stable", + }, + ExpectedImageRepository: "skaffold:stable", + }, + } { + t.Run(tc.CaseName, func(t *testing.T) { + namespaceName := "minimal-ruby-app-" + strings.ToLower(random.UniqueId()) + + values := map[string]string{ + "gitlab.app": "auto-devops-examples/minimal-ruby-app", + "gitlab.env": "prod", + } + + mergeStringMap(values, tc.Values) + + options := &helm.Options{ + SetValues: values, + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + } + + output := helm.RenderTemplate(t, options, helmChartPath, tc.Release, []string{"templates/deployment.yaml"}) + + var deployment appsV1.Deployment + helm.UnmarshalK8SYaml(t, output, &deployment) + + require.Equal(t, tc.ExpectedImageRepository, deployment.Spec.Template.Spec.Containers[0].Image) + }) + } + + for _, tc := range []struct { + CaseName string + Release string + Values map[string]string + + ExpectedName string + ExpectedRelease string + ExpectedStrategyType appsV1.DeploymentStrategyType + ExpectedSelector *metav1.LabelSelector + }{ + { + CaseName: "appsv1", + Release: "production", + Values: map[string]string{ + "deploymentApiVersion": "apps/v1", + }, + ExpectedName: "production", + ExpectedRelease: "production", + ExpectedStrategyType: appsV1.DeploymentStrategyType(""), + ExpectedSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "production", + "release": "production", + "tier": "web", + "track": "stable", + }, + }, + }, + } { + t.Run(tc.CaseName, func(t *testing.T) { + namespaceName := "minimal-ruby-app-" + strings.ToLower(random.UniqueId()) + + values := map[string]string{ + "gitlab.app": "auto-devops-examples/minimal-ruby-app", + "gitlab.env": "prod", + } + + mergeStringMap(values, tc.Values) + + options := &helm.Options{ + SetValues: values, + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + } + + output := helm.RenderTemplate(t, options, helmChartPath, tc.Release, []string{"templates/deployment.yaml"}) + + var deployment appsV1.Deployment + helm.UnmarshalK8SYaml(t, output, &deployment) + + require.Equal(t, tc.ExpectedName, deployment.Name) + require.Equal(t, tc.ExpectedStrategyType, deployment.Spec.Strategy.Type) + require.Equal(t, map[string]string{ + "app.gitlab.com/app": "auto-devops-examples/minimal-ruby-app", + "app.gitlab.com/env": "prod", + }, deployment.Annotations) + require.Equal(t, map[string]string{ + "app": tc.ExpectedName, + "chart": chartName, + "heritage": "Tiller", + "release": tc.ExpectedRelease, + "tier": "web", + "track": "stable", + }, deployment.Labels) + + require.Equal(t, tc.ExpectedSelector, deployment.Spec.Selector) + + require.Equal(t, map[string]string{ + "app.gitlab.com/app": "auto-devops-examples/minimal-ruby-app", + "app.gitlab.com/env": "prod", + "checksum/application-secrets": "", + }, deployment.Spec.Template.Annotations) + require.Equal(t, map[string]string{ + "app": tc.ExpectedName, + "release": tc.ExpectedRelease, + "tier": "web", + "track": "stable", + }, deployment.Spec.Template.Labels) + }) + } +} + +func TestWorkerDeploymentTemplate(t *testing.T) { + for _, tc := range []struct { + CaseName string + Release string + Values map[string]string + + ExpectedName string + ExpectedRelease string + ExpectedDeployments []workerDeploymentTestCase + }{ + { + CaseName: "happy", + Release: "production", + Values: map[string]string{ + "releaseOverride": "productionOverridden", + "workers.worker1.command[0]": "echo", + "workers.worker1.command[1]": "worker1", + "workers.worker2.command[0]": "echo", + "workers.worker2.command[1]": "worker2", + }, + ExpectedName: "productionOverridden", + ExpectedRelease: "production", + ExpectedDeployments: []workerDeploymentTestCase{ + { + ExpectedName: "productionOverridden-worker1", + ExpectedCmd: []string{"echo", "worker1"}, + ExpectedStrategyType: extensions.DeploymentStrategyType(""), + }, + { + ExpectedName: "productionOverridden-worker2", + ExpectedCmd: []string{"echo", "worker2"}, + ExpectedStrategyType: extensions.DeploymentStrategyType(""), + }, + }, + }, + { + CaseName: "long release name", + Release: strings.Repeat("r", 80), + Values: map[string]string{ + "workers.worker1.command[0]": "echo", + "workers.worker1.command[1]": "worker1", + }, + ExpectedName: strings.Repeat("r", 63), + ExpectedRelease: strings.Repeat("r", 80), + ExpectedDeployments: []workerDeploymentTestCase{ + { + ExpectedName: strings.Repeat("r", 63) + "-worker1", + ExpectedCmd: []string{"echo", "worker1"}, + ExpectedStrategyType: extensions.DeploymentStrategyType(""), + }, + }, + }, + { + CaseName: "strategyType", + Release: "production", + Values: map[string]string{ + "workers.worker1.command[0]": "echo", + "workers.worker1.command[1]": "worker1", + "workers.worker1.strategyType": "Recreate", + }, + ExpectedName: "production", + ExpectedRelease: "production", + ExpectedDeployments: []workerDeploymentTestCase{ + { + ExpectedName: "production" + "-worker1", + ExpectedCmd: []string{"echo", "worker1"}, + ExpectedStrategyType: extensions.RecreateDeploymentStrategyType, + }, + }, + }, + { + CaseName: "enableSelector", + Release: "production", + Values: map[string]string{ + "enableSelector": "true", + "workers.worker1.command[0]": "echo", + "workers.worker1.command[1]": "worker1", + "workers.worker2.command[0]": "echo", + "workers.worker2.command[1]": "worker2", + }, + ExpectedName: "production", + ExpectedRelease: "production", + ExpectedDeployments: []workerDeploymentTestCase{ + { + ExpectedName: "production-worker1", + ExpectedCmd: []string{"echo", "worker1"}, + ExpectedStrategyType: extensions.DeploymentStrategyType(""), + ExpectedSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "release": "production", + "tier": "worker", + "track": "stable", + }, + }, + }, + { + ExpectedName: "production-worker2", + ExpectedCmd: []string{"echo", "worker2"}, + ExpectedStrategyType: extensions.DeploymentStrategyType(""), + ExpectedSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "release": "production", + "tier": "worker", + "track": "stable", + }, + }, + }, + }, + }, + } { + t.Run(tc.CaseName, func(t *testing.T) { + namespaceName := "minimal-ruby-app-" + strings.ToLower(random.UniqueId()) + + values := map[string]string{ + "gitlab.app": "auto-devops-examples/minimal-ruby-app", + "gitlab.env": "prod", + } + + mergeStringMap(values, tc.Values) + + options := &helm.Options{ + SetValues: values, + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + } + + output := helm.RenderTemplate(t, options, helmChartPath, tc.Release, []string{"templates/worker-deployment.yaml"}) + + var deployments deploymentList + helm.UnmarshalK8SYaml(t, output, &deployments) + + require.Len(t, deployments.Items, len(tc.ExpectedDeployments)) + for i, expectedDeployment := range tc.ExpectedDeployments { + deployment := deployments.Items[i] + + require.Equal(t, expectedDeployment.ExpectedName, deployment.Name) + require.Equal(t, expectedDeployment.ExpectedStrategyType, deployment.Spec.Strategy.Type) + + require.Equal(t, map[string]string{ + "app.gitlab.com/app": "auto-devops-examples/minimal-ruby-app", + "app.gitlab.com/env": "prod", + }, deployment.Annotations) + require.Equal(t, map[string]string{ + "chart": chartName, + "heritage": "Tiller", + "release": tc.ExpectedRelease, + "tier": "worker", + "track": "stable", + }, deployment.Labels) + + require.Equal(t, expectedDeployment.ExpectedSelector, deployment.Spec.Selector) + + require.Equal(t, map[string]string{ + "app.gitlab.com/app": "auto-devops-examples/minimal-ruby-app", + "app.gitlab.com/env": "prod", + "checksum/application-secrets": "", + }, deployment.Spec.Template.Annotations) + require.Equal(t, map[string]string{ + "release": tc.ExpectedRelease, + "tier": "worker", + "track": "stable", + }, deployment.Spec.Template.Labels) + + require.Len(t, deployment.Spec.Template.Spec.Containers, 1) + require.Equal(t, expectedDeployment.ExpectedCmd, deployment.Spec.Template.Spec.Containers[0].Command) + } + }) + } + + for _, tc := range []struct { + CaseName string + Release string + Values map[string]string + + ExpectedName string + ExpectedRelease string + ExpectedDeployments []workerDeploymentAppsV1TestCase + }{ + { + CaseName: "appsv1", + Release: "production", + Values: map[string]string{ + "deploymentApiVersion": "apps/v1", + "workers.worker1.command[0]": "echo", + "workers.worker1.command[1]": "worker1", + "workers.worker2.command[0]": "echo", + "workers.worker2.command[1]": "worker2", + }, + ExpectedName: "production", + ExpectedRelease: "production", + ExpectedDeployments: []workerDeploymentAppsV1TestCase{ + { + ExpectedName: "production-worker1", + ExpectedCmd: []string{"echo", "worker1"}, + ExpectedStrategyType: appsV1.DeploymentStrategyType(""), + ExpectedSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "release": "production", + "tier": "worker", + "track": "stable", + }, + }, + }, + { + ExpectedName: "production-worker2", + ExpectedCmd: []string{"echo", "worker2"}, + ExpectedStrategyType: appsV1.DeploymentStrategyType(""), + ExpectedSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "release": "production", + "tier": "worker", + "track": "stable", + }, + }, + }, + }, + }, + } { + t.Run(tc.CaseName, func(t *testing.T) { + namespaceName := "minimal-ruby-app-" + strings.ToLower(random.UniqueId()) + + values := map[string]string{ + "gitlab.app": "auto-devops-examples/minimal-ruby-app", + "gitlab.env": "prod", + } + + mergeStringMap(values, tc.Values) + + options := &helm.Options{ + SetValues: values, + KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), + } + + output := helm.RenderTemplate(t, options, helmChartPath, tc.Release, []string{"templates/worker-deployment.yaml"}) + + var deployments deploymentAppsV1List + helm.UnmarshalK8SYaml(t, output, &deployments) + + require.Len(t, deployments.Items, len(tc.ExpectedDeployments)) + for i, expectedDeployment := range tc.ExpectedDeployments { + deployment := deployments.Items[i] + + require.Equal(t, expectedDeployment.ExpectedName, deployment.Name) + require.Equal(t, expectedDeployment.ExpectedStrategyType, deployment.Spec.Strategy.Type) + + require.Equal(t, map[string]string{ + "app.gitlab.com/app": "auto-devops-examples/minimal-ruby-app", + "app.gitlab.com/env": "prod", + }, deployment.Annotations) + require.Equal(t, map[string]string{ + "chart": chartName, + "heritage": "Tiller", + "release": tc.ExpectedRelease, + "tier": "worker", + "track": "stable", + }, deployment.Labels) + + require.Equal(t, expectedDeployment.ExpectedSelector, deployment.Spec.Selector) + + require.Equal(t, map[string]string{ + "app.gitlab.com/app": "auto-devops-examples/minimal-ruby-app", + "app.gitlab.com/env": "prod", + "checksum/application-secrets": "", + }, deployment.Spec.Template.Annotations) + require.Equal(t, map[string]string{ + "release": tc.ExpectedRelease, + "tier": "worker", + "track": "stable", + }, deployment.Spec.Template.Labels) + + require.Len(t, deployment.Spec.Template.Spec.Containers, 1) + require.Equal(t, expectedDeployment.ExpectedCmd, deployment.Spec.Template.Spec.Containers[0].Command) + } + }) + } +} + +func TestNetworkPolicyDeployment(t *testing.T) { + releaseName := "network-policy-test" + templates := []string{"templates/network-policy.yaml"} + expectedLabels := map[string]string{ + "app": releaseName, + "chart": chartName, + "release": releaseName, + "heritage": "Tiller", + } + + tcs := []struct { + name string + valueFiles []string + values map[string]string + + meta metav1.ObjectMeta + podSelector metav1.LabelSelector + policyTypes []netV1.PolicyType + ingress []netV1.NetworkPolicyIngressRule + egress []netV1.NetworkPolicyEgressRule + }{ + { + name: "defaults", + }, + { + name: "with default policy", + values: map[string]string{"networkPolicy.enabled": "true"}, + meta: metav1.ObjectMeta{Name: releaseName + "-auto-deploy", Labels: expectedLabels}, + podSelector: metav1.LabelSelector{MatchLabels: map[string]string{}}, + ingress: []netV1.NetworkPolicyIngressRule{ + { + From: []netV1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{}}}, + {NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app.gitlab.com/managed_by": "gitlab"}, + }}, + }, + }, + }, + }, + { + name: "with custom policy", + valueFiles: []string{"./testdata/custom-policy.yaml"}, + meta: metav1.ObjectMeta{Name: releaseName + "-auto-deploy", Labels: expectedLabels}, + podSelector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, + ingress: []netV1.NetworkPolicyIngressRule{ + { + From: []netV1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{}}}, + {NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"name": "foo"}, + }}, + }, + }, + }, + }, + { + name: "with full spec policy", + valueFiles: []string{"./testdata/full-spec-policy.yaml"}, + meta: metav1.ObjectMeta{Name: releaseName + "-auto-deploy", Labels: expectedLabels}, + podSelector: metav1.LabelSelector{MatchLabels: map[string]string{}}, + policyTypes: []netV1.PolicyType{"Ingress", "Egress"}, + ingress: []netV1.NetworkPolicyIngressRule{ + { + From: []netV1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{MatchLabels: map[string]string{}}}, + }, + }, + }, + egress: []netV1.NetworkPolicyEgressRule{ + { + To: []netV1.NetworkPolicyPeer{ + {NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"name": "gitlab-managed-apps"}, + }}, + }, + }, + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + opts := &helm.Options{ + ValuesFiles: tc.valueFiles, + SetValues: tc.values, + } + output := helm.RenderTemplate(t, opts, helmChartPath, releaseName, templates) + + policy := new(netV1.NetworkPolicy) + helm.UnmarshalK8SYaml(t, output, policy) + + require.Equal(t, tc.meta, policy.ObjectMeta) + require.Equal(t, tc.podSelector, policy.Spec.PodSelector) + require.Equal(t, tc.policyTypes, policy.Spec.PolicyTypes) + require.Equal(t, tc.ingress, policy.Spec.Ingress) + require.Equal(t, tc.egress, policy.Spec.Egress) + }) + } +} + +func TestIngressTemplate_ModSecurity(t *testing.T) { + templates := []string{"templates/ingress.yaml"} + modSecuritySnippet := "SecRuleEngine DetectionOnly\n" + modSecuritySnippetWithSecRules := modSecuritySnippet + `SecRule REQUEST_HEADERS:User-Agent \"scanner\" \"log,deny,id:107,status:403,msg:\'Scanner Identified\'\" +SecRule REQUEST_HEADERS:Content-Type \"text/plain\" \"log,deny,id:\'20010\',status:403,msg:\'Text plain not allowed\'\" +` + defaultAnnotations := map[string]string{ + "kubernetes.io/ingress.class": "nginx", + "kubernetes.io/tls-acme": "true", + } + defaultModSecurityAnnotations := map[string]string{ + "nginx.ingress.kubernetes.io/modsecurity-transaction-id": "$server_name-$request_id", + } + modSecurityAnnotations := make(map[string]string) + secRulesAnnotations := make(map[string]string) + mergeStringMap(modSecurityAnnotations, defaultAnnotations) + mergeStringMap(modSecurityAnnotations, defaultModSecurityAnnotations) + mergeStringMap(secRulesAnnotations, defaultAnnotations) + mergeStringMap(secRulesAnnotations, defaultModSecurityAnnotations) + modSecurityAnnotations["nginx.ingress.kubernetes.io/modsecurity-snippet"] = modSecuritySnippet + secRulesAnnotations["nginx.ingress.kubernetes.io/modsecurity-snippet"] = modSecuritySnippetWithSecRules + + tcs := []struct { + name string + valueFiles []string + values map[string]string + meta metav1.ObjectMeta + }{ + { + name: "defaults", + meta: metav1.ObjectMeta{Annotations: defaultAnnotations}, + }, + { + name: "with modSecurity enabled without custom secRules", + values: map[string]string{"ingress.modSecurity.enabled": "true"}, + meta: metav1.ObjectMeta{Annotations: modSecurityAnnotations}, + }, + { + name: "with custom secRules", + valueFiles: []string{"./testdata/modsecurity-ingress.yaml"}, + meta: metav1.ObjectMeta{Annotations: secRulesAnnotations}, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + opts := &helm.Options{ + ValuesFiles: tc.valueFiles, + SetValues: tc.values, + } + output := helm.RenderTemplate(t, opts, helmChartPath, "", templates) + + ingress := new(extensions.Ingress) + helm.UnmarshalK8SYaml(t, output, ingress) + + require.Equal(t, tc.meta.Annotations, ingress.ObjectMeta.Annotations) + }) + } +} + +func TestIngressTemplate_Disable(t *testing.T) { + templates := []string{"templates/ingress.yaml"} + releaseName := "ingress-disable-test" + tcs := []struct { + name string + values map[string]string + + expectedrelease string + }{ + { + name: "defaults", + expectedrelease: releaseName + "-auto-deploy", + }, + { + name: "with ingress.enabled key undefined, but service is enabled", + values: map[string]string{"ingress.enabled": "null", "service.enabled": "true"}, + expectedrelease: releaseName + "-auto-deploy", + }, + { + name: "with service enabled and track non-stable", + values: map[string]string{"service.enabled": "true", "application.track": "non-stable"}, + expectedrelease: "", + }, + { + name: "with service disabled and track stable", + values: map[string]string{"service.enabled": "false", "application.track": "stable"}, + expectedrelease: "", + }, + { + name: "with service disabled and track non-stable", + values: map[string]string{"service.enabled": "false", "application.track": "non-stable"}, + expectedrelease: "", + }, + { + name: "with ingress disabled", + values: map[string]string{"ingress.enabled": "false"}, + expectedrelease: "", + }, + { + name: "with ingress enabled and track non-stable", + values: map[string]string{"ingress.enabled": "true", "application.track": "non-stable"}, + expectedrelease: "", + }, + { + name: "with ingress enabled and service disabled", + values: map[string]string{"ingress.enabled": "true", "service.enabled": "false"}, + expectedrelease: "", + }, + { + name: "with ingress disabled and service enabled and track stable", + values: map[string]string{"ingress.enabled": "false", "service.enabled": "true", "application.track": "stable"}, + expectedrelease: "", + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + opts := &helm.Options{ + SetValues: tc.values, + } + output := helm.RenderTemplate(t, opts, helmChartPath, releaseName, templates) + + ingress := new(extensions.Ingress) + + helm.UnmarshalK8SYaml(t, output, ingress) + require.Equal(t, tc.expectedrelease, ingress.ObjectMeta.Name) + }) + } +} + +func TestServiceTemplate_Disable(t *testing.T) { + templates := []string{"templates/service.yaml"} + releaseName := "service-disable-test" + tcs := []struct { + name string + values map[string]string + + expectedrelease string + }{ + { + name: "defaults", + expectedrelease: releaseName + "-auto-deploy", + }, + { + name: "with service enabled and track non-stable", + values: map[string]string{"service.enabled": "true", "application.track": "non-stable"}, + expectedrelease: "", + }, + { + name: "with service disabled and track stable", + values: map[string]string{"service.enabled": "false", "application.track": "stable"}, + expectedrelease: "", + }, + { + name: "with service disabled and track non-stable", + values: map[string]string{"service.enabled": "false", "application.track": "non-stable"}, + expectedrelease: "", + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + opts := &helm.Options{ + SetValues: tc.values, + } + output := helm.RenderTemplate(t, opts, helmChartPath, releaseName, templates) + + service := new(coreV1.Service) + + helm.UnmarshalK8SYaml(t, output, service) + + require.Equal(t, tc.expectedrelease, service.ObjectMeta.Name) + }) + } +} + +type workerDeploymentTestCase struct { + ExpectedName string + ExpectedCmd []string + ExpectedStrategyType extensions.DeploymentStrategyType + ExpectedSelector *metav1.LabelSelector +} + +type workerDeploymentAppsV1TestCase struct { + ExpectedName string + ExpectedCmd []string + ExpectedStrategyType appsV1.DeploymentStrategyType + ExpectedSelector *metav1.LabelSelector +} + +type deploymentList struct { + metav1.TypeMeta `json:",inline"` + + Items []extensions.Deployment `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +type deploymentAppsV1List struct { + metav1.TypeMeta `json:",inline"` + + Items []appsV1.Deployment `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func mergeStringMap(dst, src map[string]string) { + for k, v := range src { + dst[k] = v + } +} diff --git a/assets/auto-deploy-app/test/testdata/custom-policy.yaml b/assets/auto-deploy-app/test/testdata/custom-policy.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8cc487aa725b2bb91a704caf5e39c3a3882be055 --- /dev/null +++ b/assets/auto-deploy-app/test/testdata/custom-policy.yaml @@ -0,0 +1,13 @@ +networkPolicy: + enabled: true + spec: + podSelector: + matchLabels: + foo: bar + ingress: + - from: + - podSelector: + matchLabels: {} + - namespaceSelector: + matchLabels: + name: foo diff --git a/assets/auto-deploy-app/test/testdata/full-spec-policy.yaml b/assets/auto-deploy-app/test/testdata/full-spec-policy.yaml new file mode 100644 index 0000000000000000000000000000000000000000..25254b5d2c51802230fd383f206e1848a9517879 --- /dev/null +++ b/assets/auto-deploy-app/test/testdata/full-spec-policy.yaml @@ -0,0 +1,17 @@ +networkPolicy: + enabled: true + spec: + podSelector: + matchLabels: {} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: {} + egress: + - to: + - namespaceSelector: + matchLabels: + name: gitlab-managed-apps diff --git a/assets/auto-deploy-app/test/testdata/modsecurity-ingress.yaml b/assets/auto-deploy-app/test/testdata/modsecurity-ingress.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5b8b469c1ece14cd1fdbdda8b503b5a38026141f --- /dev/null +++ b/assets/auto-deploy-app/test/testdata/modsecurity-ingress.yaml @@ -0,0 +1,10 @@ +ingress: + modSecurity: + enabled: true + secRules: + - variable: "REQUEST_HEADERS:User-Agent" + operator: "scanner" + action: "log,deny,id:107,status:403,msg:'Scanner Identified'" + - variable: "REQUEST_HEADERS:Content-Type" + operator: "text/plain" + action: "log,deny,id:'20010',status:403,msg:'Text plain not allowed'" \ No newline at end of file diff --git a/assets/auto-deploy-app/values.yaml b/assets/auto-deploy-app/values.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b7b44df77f6c48c420767fd2cd446cfe6aea0e44 --- /dev/null +++ b/assets/auto-deploy-app/values.yaml @@ -0,0 +1,123 @@ +# Default values for chart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +replicaCount: 1 +strategyType: +enableSelector: +deploymentApiVersion: extensions/v1beta1 +image: + repository: gitlab.example.com/group/project + tag: stable + pullPolicy: IfNotPresent + secrets: + - name: gitlab-registry +podAnnotations: {} +application: + track: stable + tier: web + migrateCommand: + initializeCommand: + secretName: + secretChecksum: +hpa: + enabled: false + minReplicas: 1 + maxReplicas: 5 + targetCPUUtilizationPercentage: 80 +gitlab: + app: + env: + envName: + envURL: +service: + enabled: true + annotations: {} + name: web + type: ClusterIP + url: http://my.host.com/ + additionalHosts: + commonName: + externalPort: 5000 + internalPort: 5000 +ingress: + enabled: true + tls: + enabled: true + secretName: "" + annotations: + kubernetes.io/tls-acme: "true" + kubernetes.io/ingress.class: "nginx" + modSecurity: + enabled: false + secRuleEngine: "DetectionOnly" + # secRules: + # - variable: "" + # operator: "" + # action: "" +prometheus: + metrics: false +livenessProbe: + path: "/" + initialDelaySeconds: 15 + timeoutSeconds: 15 + scheme: "HTTP" + probeType: "httpGet" +readinessProbe: + path: "/" + initialDelaySeconds: 5 + timeoutSeconds: 3 + scheme: "HTTP" + probeType: "httpGet" +postgresql: + enabled: true + managed: false + managedClassSelector: + # matchLabels: + # stack: gitlab (This is an example. The labels should match the labels on the CloudSQLInstanceClass) + +resources: +# limits: +# cpu: 100m +# memory: 128Mi + requests: +# cpu: 100m +# memory: 128Mi + +## Configure PodDisruptionBudget +## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ +# +podDisruptionBudget: + enabled: false + # minAvailable: 1 + maxUnavailable: 1 + +## Configure NetworkPolicy +## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +# +networkPolicy: + enabled: false + spec: + podSelector: + matchLabels: {} + ingress: + - from: + - podSelector: + matchLabels: {} + - namespaceSelector: + matchLabels: + app.gitlab.com/managed_by: gitlab + +workers: {} + # worker: + # replicaCount: 1 + # terminationGracePeriodSeconds: 60 + # command: + # - /bin/herokuish + # - procfile + # - start + # - worker + # preStopCommand: + # - /bin/herokuish + # - procfile + # - start + # - stop_worker diff --git a/src/bin/auto-deploy b/src/bin/auto-deploy index b347c2c82dff52106ce27748d703f71880f438fb..3d5307079c0e8338f229509570b8e9d225360366 100755 --- a/src/bin/auto-deploy +++ b/src/bin/auto-deploy @@ -13,6 +13,9 @@ if [[ "$AUTO_DEVOPS_POSTGRES_CHANNEL" == "2" ]]; then elif [[ "$AUTO_DEVOPS_POSTGRES_CHANNEL" == "1" ]]; then export POSTGRES_VERSION="${POSTGRES_VERSION:-"9.6.2"}" fi +export BIN_DIR="/build/bin" +export ASSETS_DIR='/assets' +export ASSETS_CHART_DIR="${ASSETS_DIR}/auto-deploy-app" function check_kube_domain() { if [[ -z "$KUBE_INGRESS_BASE_DOMAIN" ]]; then @@ -28,28 +31,31 @@ function check_kube_domain() { } function download_chart() { - local auto_chart - local auto_chart_name - if [[ ! -d chart ]]; then + helm init --client-only + + if [[ -d chart ]]; then + echo "Download is skipped. The bundled chart in user's repository will be used." + elif [[ -n "${AUTO_DEVOPS_CHART}${AUTO_DEVOPS_CHART_REPOSITORY_NAME}${AUTO_DEVOPS_CHART_REPOSITORY}" ]]; then + echo "Downloading the chart from the chart repository..." + local auto_chart + local auto_chart_name + auto_chart=${AUTO_DEVOPS_CHART:-gitlab/auto-deploy-app} # shellcheck disable=SC2086 # double quote variables to prevent globbing auto_chart_name=$(basename $auto_chart) auto_chart_name=${auto_chart_name%.tgz} auto_chart_name=${auto_chart_name%.tar.gz} - else - auto_chart="chart" - auto_chart_name="chart" - fi - helm init --client-only - # shellcheck disable=SC2086 # double quote variables to prevent globbing - # shellcheck disable=SC2140 # ambiguous quoting warning - helm repo add ${AUTO_DEVOPS_CHART_REPOSITORY_NAME:-gitlab} ${AUTO_DEVOPS_CHART_REPOSITORY:-https://charts.gitlab.io} ${AUTO_DEVOPS_CHART_REPOSITORY_USERNAME:+"--username" "$AUTO_DEVOPS_CHART_REPOSITORY_USERNAME"} ${AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD:+"--password" "$AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD"} - if [[ ! -d "$auto_chart" ]]; then - helm fetch ${auto_chart} --untar - fi - if [ "$auto_chart_name" != "chart" ]; then - mv ${auto_chart_name} chart + # shellcheck disable=SC2086 # double quote variables to prevent globbing + # shellcheck disable=SC2140 # ambiguous quoting warning + helm repo add ${AUTO_DEVOPS_CHART_REPOSITORY_NAME:-gitlab} ${AUTO_DEVOPS_CHART_REPOSITORY:-https://charts.gitlab.io} ${AUTO_DEVOPS_CHART_REPOSITORY_USERNAME:+"--username" "$AUTO_DEVOPS_CHART_REPOSITORY_USERNAME"} ${AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD:+"--password" "$AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD"} + helm fetch "${auto_chart}" --untar + if [ "$auto_chart_name" != "chart" ]; then + mv "${auto_chart_name}" chart + fi + else + echo "Download is skipped. The bundled chart in auto-deploy-image will be used." + cp -R $ASSETS_CHART_DIR chart fi helm dependency update chart/ @@ -203,6 +209,8 @@ channel 1 database.' old_postgres_enabled="$POSTGRES_ENABLED" fi + ${BIN_DIR}/validate-chart-version "$(helm list --output json)" "chart" "$name" + local database_url database_url=$(auto_database_url) @@ -435,7 +443,7 @@ function create_application_secret() { k8s_secrets_file=$(mktemp) - /build/bin/auto-deploy-application-secrets-yaml "$k8s_secrets_file" + ${BIN_DIR}/auto-deploy-application-secrets-yaml "$k8s_secrets_file" kubectl replace -f "$k8s_secrets_file" -n "$KUBE_NAMESPACE" --force @@ -507,7 +515,6 @@ function get_replicas() { echo 1 fi } - ## ## End Helper functions diff --git a/src/bin/helpers/gitlab/chart.rb b/src/bin/helpers/gitlab/chart.rb new file mode 100644 index 0000000000000000000000000000000000000000..47d2b1a8ceaf42fbcf31f106b07f9a2650e1e93e --- /dev/null +++ b/src/bin/helpers/gitlab/chart.rb @@ -0,0 +1,71 @@ +require 'yaml' +require 'json' + +module Gitlab + # This class represents auto-deploy-app chart managed by GitLab. + class Chart + attr_reader :major, :minor, :patch + + GITLAB_MANAGED_CHART_NAME = 'auto-deploy-app'.freeze + + class << self + # Load a chart from `helm list` output + # + # @param [String] data JSON formatted `helm list` output + # @param [String] release_name The release created by a chart in question + def load_from_helm_ls(data, release_name) + # In Helm 2, `helm ls --output json` returns an empty string when there are no releases + return if data.empty? + + release = JSON.parse(data)['Releases'].find { |r| r['Name'] == release_name } + + return if release.nil? + + name, major, minor, patch = release['Chart'].scan(/\A(.+)-(\d+)\.(\d+)\.(\d+)/).first + + return unless gitlab_managed_chart?(name, major, minor, patch) + + self.new(major, minor, patch) + end + + # Load a chart from Chart.yaml + # + # @param [String] chart_dir The path to the chart directory + def load_from_chart_yml(chart_dir) + chart_hash = YAML.load_file(File.join(chart_dir, 'Chart.yaml')) + + name = chart_hash['name'] + major, minor, patch = chart_hash['version'].scan(/\A(\d+)\.(\d+)\.(\d)+/).first + + return unless gitlab_managed_chart?(name, major, minor, patch) + + self.new(major, minor, patch) + end + + def gitlab_managed_chart?(name, major, minor, patch) + name == GITLAB_MANAGED_CHART_NAME && major && minor && patch + end + end + + def initialize(major, minor, patch) + @major = major.to_i + @minor = minor.to_i + @patch = patch.to_i + end + + def to_s + "v#{major}.#{minor}.#{patch}" + end + + def compatible?(previous_chart) + # v0 and v1 charts are compatible + return true if major == 1 && previous_chart.major == 0 + + major == previous_chart.major + end + + def allowed_to_force_deploy? + ENV["AUTO_DEVOPS_FORCE_DEPLOY_V#{major}"] + end + end +end diff --git a/src/bin/validate-chart-version b/src/bin/validate-chart-version new file mode 100755 index 0000000000000000000000000000000000000000..e4a2638df63086b7d330253422f8432c8c04e9b5 --- /dev/null +++ b/src/bin/validate-chart-version @@ -0,0 +1,43 @@ +#!/usr/bin/ruby + +require_relative 'helpers/gitlab/chart' + +helm_release_list_json = ARGV[0] +current_chart_dir = ARGV[1] +release_name = ARGV[2] + +puts "Validating chart version..." + +print "Fetching the previously deployed chart version..." +previous_chart = Gitlab::Chart.load_from_helm_ls(helm_release_list_json, release_name) +puts " #{previous_chart}" + +print "Fetching the deploying chart version..." +current_chart = Gitlab::Chart.load_from_chart_yml(current_chart_dir) +puts " #{current_chart}" + +return unless previous_chart && current_chart + +if current_chart.compatible?(previous_chart) + puts "The current chart is compatible with the previously deployed chart" +elsif current_chart.allowed_to_force_deploy? + puts "The current chart is not compatible with the previously deployed chart, however, allowed to force deploy." +else + puts <<~EOS + ************************************************************************************* + [WARNING] + Detected a major version difference between the the chart that is currently deploying (#{current_chart.to_s}), and the previously deployed chart (#{previous_chart.to_s}). + A new major version might not be backward compatible with the current release (#{release_name}). The deployment could fail or be stuck in an unrecoverable status. + Please follow the appropriate instructions: + + - To proceed with the new chart version, follow the manual upgrade guide + https://docs.gitlab.com/ee/topics/autodevops/upgrading_chart.html#upgrade-guide and redeploy. + + - To continue using the previously deployed chart, see the following instructions: + https://docs.gitlab.com/ee/topics/autodevops/upgrading_chart.html#keep-using-a-specific-version-of-chart and redeploy. + + For more information, please read https://docs.gitlab.com/ee/topics/autodevops/upgrading_chart.html. + ************************************************************************************* + EOS + exit 1 +end diff --git a/test/rspec/helpers/gitlab/chart_spec.rb b/test/rspec/helpers/gitlab/chart_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0f82ce443f434358a4b76cc3233e67e885c16874 --- /dev/null +++ b/test/rspec/helpers/gitlab/chart_spec.rb @@ -0,0 +1,158 @@ +require_relative '../../../../src/bin/helpers/gitlab/chart.rb' +require 'tmpdir' + +describe Gitlab::Chart do + describe '.load_from_helm_ls' do + subject { described_class.load_from_helm_ls(data, release_name) } + + let(:release_name) { 'production' } + + let(:data) do + <<~EOS + { + "Next": "", + "Releases": [ + { + "Name": "production", + "Revision": 1, + "Updated": "Wed Jul 1 08:07:07 2020", + "Status": "DEPLOYED", + "Chart": "auto-deploy-app-1.2.3", + "AppVersion": "", + "Namespace": "new-sentimentality-19561312-production" + }, + { + "Name": "production-canary", + "Revision": 2, + "Updated": "Wed Jul 1 11:45:16 2020", + "Status": "DEPLOYED", + "Chart": "auto-deploy-app-4.5.6", + "AppVersion": "", + "Namespace": "new-sentimentality-19561312-production" + }, + { + "Name": "production-postgresql", + "Revision": 9, + "Updated": "Mon Jul 13 11:37:20 2020", + "Status": "DEPLOYED", + "Chart": "postgresql-8.2.1", + "AppVersion": "11.6.0", + "Namespace": "new-sentimentality-19561312-production" + } + ] + } + EOS + end + + it 'correctly loads the chart' do + expect(subject.major).to eq(1) + expect(subject.minor).to eq(2) + expect(subject.patch).to eq(3) + end + + context 'when release name is canary' do + let(:release_name) { 'production-canary' } + + it 'correctly loads the chart' do + expect(subject.major).to eq(4) + expect(subject.minor).to eq(5) + expect(subject.patch).to eq(6) + end + end + + context 'when release name does not exist' do + let(:release_name) { 'production-unknown' } + + it 'returns nil' do + expect(subject).to be_nil + end + end + + context 'when chart is not gitlab managed chart' do + let(:release_name) { 'production-postgresql' } + + it 'returns nil' do + expect(subject).to be_nil + end + end + + context 'when data is empty' do + let(:data) { '' } + + it 'returns nil' do + expect(subject).to be_nil + end + end + + context 'when data is nil' do + let(:data) { nil } + + it 'raises an error' do + expect { subject }.to raise_error(NoMethodError) + end + end + + context 'when data is not formatted in json' do + let(:data) { 'test' } + + it 'raises an error' do + expect { subject }.to raise_error(JSON::ParserError) + end + end + end + + describe '.load_from_chart_yml' do + let(:chart_yaml) do + <<~EOS + apiVersion: v1 + description: GitLab's Auto-deploy Helm Chart + name: auto-deploy-app + version: 1.0.0-beta.0 + icon: https://gitlab.com/gitlab-com/gitlab-artwork/raw/master/logo/logo-square.png + EOS + end + + it 'correctly loads the chart' do + in_chart_dir do |dir| + chart = described_class.load_from_chart_yml(dir) + + expect(chart.major).to eq(1) + expect(chart.minor).to eq(0) + expect(chart.patch).to eq(0) + end + end + + context 'when chart is not gitlab managed chart' do + let(:chart_yaml) do + <<~EOS + apiVersion: v1 + description: GitLab's Auto-deploy Helm Chart + name: custom-chart + version: 1.0.0-beta.0 + icon: https://gitlab.com/gitlab-com/gitlab-artwork/raw/master/logo/logo-square.png + EOS + end + + it 'returns nil' do + in_chart_dir do |dir| + chart = described_class.load_from_chart_yml(dir) + + expect(chart).to be_nil + end + end + end + + context 'when chart yaml is not found' do + it 'raises an error' do + expect { described_class.load_from_chart_yml('test') }.to raise_error(Errno::ENOENT) + end + end + + def in_chart_dir + Dir.mktmpdir do |dir| + File.write("#{dir}/Chart.yaml", chart_yaml) + yield dir + end + end + end +end diff --git a/test/verify-chart-version b/test/verify-chart-version new file mode 100755 index 0000000000000000000000000000000000000000..8f0b259474473b7cf50edd04b5f665f6ec3aa980 --- /dev/null +++ b/test/verify-chart-version @@ -0,0 +1,12 @@ +#!/usr/bin/ruby + +require_relative '../src/bin/helpers/gitlab/chart' + +expected_major_version = ARGV[0].to_i + +chart = Gitlab::Chart.load_from_chart_yml('chart') + +unless expected_major_version == chart.major + raise "The chart version is different from the expected version. " \ + "expected: #{expected_major_version} got: #{chart.major}" +end