diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 54cdae77206c0a4de689ff246c7c362edd4bb269..5bea11f07268edad138302f93d387cd0d444de3f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -37,74 +37,6 @@ stages: build: services: -# Prepares the secret files that we cannot or don't want to share with public -prepare_secrets_master: - stage: prepare - image: alpine:latest - script: - - cp $ENVFILE .env - - cp $SUMAS config/sumas.json - - cp $SUMASEN config/sumasEn.json - - cp $BLACKLISTURL config/blacklistUrl.txt - - cp $BLACKLISTDOMAINS config/blacklistDomains.txt - - cp $ADBLACKLISTURL config/adBlacklistUrl.txt - - cp $ADBLACKLISTDOMAINS config/adBlacklistDomains.txt - - cp $SPAM config/spam.txt - - cp $USERSSEEDER database/seeds/UsersSeeder.php - - cp database/useragents.sqlite.example database/useragents.sqlite - - sed -i 's/^APP_ENV=.*/APP_ENV=production/g' .env - - sed -i 's/^REDIS_PASSWORD=.*/REDIS_PASSWORD=null/g' .env - artifacts: - paths: - - .env - - config/sumas.json - - config/sumasEn.json - - config/blacklistUrl.txt - - config/blacklistDomains.txt - - config/adBlacklistUrl.txt - - config/adBlacklistDomains.txt - - config/spam.txt - - database/seeds/UsersSeeder.php - - database/useragents.sqlite - only: - refs: - - master - -prepare_secrets_development: - stage: prepare - image: alpine:latest - script: - - cp $ENVFILE .env - - cp $SUMAS config/sumas.json - - cp $SUMASEN config/sumasEn.json - - cp $BLACKLISTURL config/blacklistUrl.txt - - cp $BLACKLISTDOMAINS config/blacklistDomains.txt - - cp $ADBLACKLISTURL config/adBlacklistUrl.txt - - cp $ADBLACKLISTDOMAINS config/adBlacklistDomains.txt - - cp $SPAM config/spam.txt - - cp $USERSSEEDER database/seeds/UsersSeeder.php - - cp database/useragents.sqlite.example database/useragents.sqlite - - sed -i 's/^APP_ENV=.*/APP_ENV=development/g' .env - - sed -i 's/^REDIS_PASSWORD=.*/REDIS_PASSWORD=null/g' .env - artifacts: - paths: - - .env - - config/sumas.json - - config/sumasEn.json - - config/blacklistUrl.txt - - config/blacklistDomains.txt - - config/adBlacklistUrl.txt - - config/adBlacklistDomains.txt - - config/spam.txt - - database/seeds/UsersSeeder.php - - database/useragents.sqlite - only: - - branches - - tags - except: - refs: - - master - prepare_node: stage: prepare image: node:10 @@ -143,20 +75,29 @@ review: variables: HELM_UPGRADE_VALUES_FILE: .gitlab/review-apps-values.yaml ROLLOUT_RESOURCE_TYPE: deployment - except: - refs: - - master - - development - variables: - - $REVIEW_DISABLED + rules: + - if: '$CI_KUBERNETES_ACTIVE == null || $CI_KUBERNETES_ACTIVE == ""' + when: never + - if: '$CI_COMMIT_BRANCH == "master"' + when: never + - if: '$CI_COMMIT_BRANCH == "development"' + when: never + - if: '$REVIEW_DISABLED' + when: never + - if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH' stop_review: - except: - refs: - - master - - development - variables: - - $REVIEW_DISABLED + rules: + - if: '$CI_KUBERNETES_ACTIVE == null || $CI_KUBERNETES_ACTIVE == ""' + when: never + - if: '$CI_COMMIT_BRANCH == "master"' + when: never + - if: '$CI_COMMIT_BRANCH == "development"' + when: never + - if: '$REVIEW_DISABLED' + when: never + - if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH' + .development: &development_template extends: .auto-deploy @@ -211,6 +152,11 @@ integrationtest: script: # Install Dev Dependencies - composer install + - cp .env.example .env + - echo "WEBDRIVER_USER=\"$WEBDRIVER_KEY\"" >> .env + - echo "WEBDRIVER_URL=\"$WEBDRIVER_URL\"" >> .env + - echo "WEBDRIVER_KEY=\"$WEBDRIVER_USER\"" >> .env + - php artisan key:generate - URL=$(cat environment_url.txt | tr -d '\n') - sed -i "s#^APP_URL=.*#APP_URL=$URL#g" .env - sed -i "s#^BRANCH_NAME=.*#BRANCH_NAME=$CI_COMMIT_REF_NAME#g" .env diff --git a/.gitlab/development-values.yaml b/.gitlab/development-values.yaml index 1a756bdf674a82cb4ea5b816851e6bd00bb1dfd8..87dfff8a0dfd9ed3b3dc84415a1d2f9025a5c76b 100644 --- a/.gitlab/development-values.yaml +++ b/.gitlab/development-values.yaml @@ -12,9 +12,10 @@ podAnnotations: prometheus.io/scrape: "true" prometheus.io/path: /metrics prometheus.io/port: "80" +deploymentApiVersion: apps/v1 ingress: annotations: - certmanager.k8s.io/cluster-issuer: letsencrypt-prod + cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/configuration-snippet: | if ($host = "www.metager3.de") { return 301 https://metager3.de$request_uri; diff --git a/.gitlab/production-values.yaml b/.gitlab/production-values.yaml index e4f3fb44ae2aa6b16a8dc958c4b85a35c7fe702c..6957184b5045a8e99002899647d690348255d13c 100644 --- a/.gitlab/production-values.yaml +++ b/.gitlab/production-values.yaml @@ -19,9 +19,10 @@ podAnnotations: prometheus.io/scrape: "true" prometheus.io/path: /metrics prometheus.io/port: "80" +deploymentApiVersion: apps/v1 ingress: annotations: - certmanager.k8s.io/cluster-issuer: letsencrypt-prod + cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/configuration-snippet: | if ($host = "www.metager.de") { return 301 https://metager.de$request_uri; diff --git a/.gitlab/review-apps-values.yaml b/.gitlab/review-apps-values.yaml index f13b3784d6a276f15cb0e0a72001ea976580c908..81eb62cecff6ed6b1b29aa3321cb1f2b7e911237 100644 --- a/.gitlab/review-apps-values.yaml +++ b/.gitlab/review-apps-values.yaml @@ -11,3 +11,4 @@ service: commonName: "" externalPort: 80 internalPort: 80 +deploymentApiVersion: apps/v1 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 7bfbc1866a1f3b39b5a057fbaac0c42e7fa298c9..a74f916b948735b21b1bc4ae838d4f2a515dae5d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -69,7 +69,11 @@ COPY --chown=root:nginx . /html WORKDIR /html EXPOSE 80 -CMD chown -R root:nginx storage/logs/metager bootstrap/cache && \ +CMD cp /root/.env .env && \ + sed -i 's/^REDIS_PASSWORD=.*/REDIS_PASSWORD=null/g' .env && \ + if [ "$GITLAB_ENVIRONMENT_NAME" = "production" ]; then sed -i 's/^APP_ENV=.*/APP_ENV=production/g' .env; else sed -i 's/^APP_ENV=.*/APP_ENV=development/g' .env; fi && \ + cp database/useragents.sqlite.example database/useragents.sqlite && \ + chown -R root:nginx storage/logs/metager bootstrap/cache && \ chmod -R g+w storage/logs/metager bootstrap/cache && \ crond -L /dev/stdout && \ php-fpm7 diff --git a/app/Http/Controllers/MailController.php b/app/Http/Controllers/MailController.php index 9ff957dede09aec132a570cfdd8f5b6ee108e70f..86afc3ceb49cb6df4e9d3a8f1267bcf2fa17dae0 100644 --- a/app/Http/Controllers/MailController.php +++ b/app/Http/Controllers/MailController.php @@ -8,9 +8,10 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use LaravelLocalization; use Mail; +use Log; use Validator; -use \PHP_IBAN\IBAN; -use \PHP_IBAN\IBANCountry; +use \IBAN; +use \IBANCountry; class MailController extends Controller { @@ -148,6 +149,7 @@ class MailController extends Controller $messageType = "success"; $messageToUser = "Herzlichen Dank!! Wir haben Ihre Spendenbenachrichtigung erhalten."; } catch (\Swift_TransportException $e) { + Log::error($e->getMessage()); $messageType = "error"; $messageToUser = 'Beim Senden Ihrer Spendenbenachrichtigung ist ein Fehler auf unserer Seite aufgetreten. Bitte schicken Sie eine E-Mail an: office@suma-ev.de, damit wir uns darum kümmern können.'; } diff --git a/app/Http/Controllers/MetaGerSearch.php b/app/Http/Controllers/MetaGerSearch.php index 4d594cb5b85cdfd1dc1735fdb0320fb200b551d5..55589b09cd032fcb5454049a6deb038bf6ff6d43 100644 --- a/app/Http/Controllers/MetaGerSearch.php +++ b/app/Http/Controllers/MetaGerSearch.php @@ -7,6 +7,7 @@ use App\MetaGer; use Cache; use Illuminate\Http\Request; use LaravelLocalization; +use Log; use View; class MetaGerSearch extends Controller @@ -63,7 +64,7 @@ class MetaGerSearch extends Controller # Search query can be empty after parsing the formdata # we will cancel the search in that case and show an error to the user - if(empty($metager->getQ())){ + if (empty($metager->getQ())) { return $metager->createView(); } @@ -109,7 +110,11 @@ class MetaGerSearch extends Controller } } - Cache::put("loader_" . $metager->getSearchUid(), $metager->getEngines(), 60 * 60); + try { + Cache::put("loader_" . $metager->getSearchUid(), $metager->getEngines(), 60 * 60); + } catch (\Exception $e) { + Log::error($e->getMessage()); + } if (!empty($timings)) { $timings["Filled resultloader Cache"] = microtime(true) - $time; } @@ -117,7 +122,11 @@ class MetaGerSearch extends Controller # Die Ausgabe erstellen: $resultpage = $metager->createView(); if ($spamEntry !== null) { - Cache::put('spam.' . $metager->getFokus() . "." . md5($spamEntry), $resultpage->render(), 604800); + try { + Cache::put('spam.' . $metager->getFokus() . "." . md5($spamEntry), $resultpage->render(), 604800); + } catch (\Exception $e) { + Log::error($e->getMessage()); + } } if (!empty($timings)) { @@ -133,7 +142,7 @@ class MetaGerSearch extends Controller $counter->incBy(sizeof($metager->getResults())); $counter = $registry->getOrRegisterCounter('metager', 'query_counter', 'counts total number of search queries', []); $counter->inc(); - + return $resultpage; } @@ -225,7 +234,7 @@ class MetaGerSearch extends Controller $result["finished"] = $finished; - if($newResults > 0){ + if ($newResults > 0) { $registry = \Prometheus\CollectorRegistry::getDefault(); $counter = $registry->getOrRegisterCounter('metager', 'result_counter', 'counts total number of returned results', []); $counter->incBy($newResults); @@ -290,7 +299,7 @@ class MetaGerSearch extends Controller { $search = $request->input('search', ''); $quotes = $request->input('quotes', 'on'); - if(empty($search)){ + if (empty($search)) { abort(404); } diff --git a/app/Http/Middleware/HumanVerification.php b/app/Http/Middleware/HumanVerification.php index a39debc384ccfb8a86bbc3f7a3c99311431f4054..5599671c6102f335ee32d1c2e3b9f8b8eefa2453 100644 --- a/app/Http/Middleware/HumanVerification.php +++ b/app/Http/Middleware/HumanVerification.php @@ -7,6 +7,7 @@ use Captcha; use Closure; use Cookie; use Illuminate\Http\Response; +use Log; use URL; class HumanVerification @@ -66,7 +67,6 @@ class HumanVerification } else { $user = $users[$uid]; } - # Lock out everyone in a Bot network # Find out how many requests this IP has made $sum = 0; @@ -128,16 +128,23 @@ class HumanVerification if ($user["unusedResultPages"] === 50 || ($user["unusedResultPages"] > 50 && $user["unusedResultPages"] % 25 === 0)) { $user["locked"] = true; } - } + \App\PrometheusExporter::HumanVerificationSuccessfull(); + } catch (\Exception $e) { + Log::error($e->getMessage()); + \App\PrometheusExporter::HumanVerificationError(); } finally { - if ($update) { + if ($update && $user != null) { if ($user["whitelist"]) { $user["expiration"] = now()->addWeeks(2); } else { $user["expiration"] = now()->addHours(72); } - $this->setUser($prefix, $user); + try { + $this->setUser($prefix, $user); + } catch (\Exception $e) { + Log::error($e->getMessage()); + } } } @@ -148,7 +155,6 @@ class HumanVerification public function setUser($prefix, $user) { - // Lock must be acquired within 2 seconds $userList = Cache::get($prefix . "." . $user["id"], []); $userList[$user["uid"]] = $user; Cache::put($prefix . "." . $user["id"], $userList, 2 * 7 * 24 * 60 * 60); diff --git a/app/Models/Quicktips/Quicktips.php b/app/Models/Quicktips/Quicktips.php index ba558afcf9dd0f498c2ae3bf15dec2686f2784f2..1210568fe193be1f0f8295598e6249d8627d0fba 100644 --- a/app/Models/Quicktips/Quicktips.php +++ b/app/Models/Quicktips/Quicktips.php @@ -32,12 +32,23 @@ class Quicktips $url = $this->quicktipUrl . "?search=" . $this->normalize_search($search) . "&locale=" . $locale . ""es=" . $quotes; $this->hash = md5($url); - if (!Cache::has($this->hash)) { + $results = null; + + try { + if (!Cache::has($this->hash)) { + $results = file_get_contents($url); + Cache::put($this->hash, $results, Quicktips::CACHE_DURATION); + } else { + $results = Cache::get($this->hash); + } + } catch (\Exception $e) { + Log::error($e->getMessage()); + } + + if ($results === null) { $results = file_get_contents($url); - Cache::put($this->hash, $results, Quicktips::CACHE_DURATION); - } else { - $results = Cache::get($this->hash); } + $this->results = $this->loadResults($results); } diff --git a/app/Models/Searchengine.php b/app/Models/Searchengine.php index eba7c83bd86e4a2104b17fd5a165de4c43fc3570..9cafcf0c61aa5b5d1542b6054ae25ec13127b327 100644 --- a/app/Models/Searchengine.php +++ b/app/Models/Searchengine.php @@ -5,6 +5,7 @@ namespace App\Models; use App\MetaGer; use Cache; use Illuminate\Support\Facades\Redis; +use Log; abstract class Searchengine { @@ -94,7 +95,7 @@ abstract class Searchengine $tmpPara = true; $engineParameterKey = $filter->sumas->{$name}->{"get-parameter"}; $engineParameterValue = $filter->sumas->{$name}->values->{$inputParameter}; - if(stripos($engineParameterValue, "dyn-") === 0){ + if (stripos($engineParameterValue, "dyn-") === 0) { $functionname = substr($engineParameterValue, stripos($engineParameterValue, "dyn-") + 4); $engineParameterValue = \App\DynamicEngineParameters::$functionname(); } @@ -207,7 +208,11 @@ abstract class Searchengine } if ($body !== null) { - Cache::put($this->hash, $body, $this->cacheDuration * 60); + try { + Cache::put($this->hash, $body, $this->cacheDuration * 60); + } catch (\Exception $e) { + Log::error($e->getMessage()); + } $this->loadResults($body); $this->getNext($metager, $body); $this->markNew(); diff --git a/app/PrometheusExporter.php b/app/PrometheusExporter.php index 1332e1cf2f5176fbcd91e6b8c704ac7cc692fa9c..dd70db4e0a2c3a240aab2675bd5f718486faed46 100644 --- a/app/PrometheusExporter.php +++ b/app/PrometheusExporter.php @@ -2,24 +2,41 @@ namespace App; -class PrometheusExporter { +class PrometheusExporter +{ - public static function CaptchaShown() { + public static function CaptchaShown() + { $registry = \Prometheus\CollectorRegistry::getDefault(); $counter = $registry->getOrRegisterCounter('metager', 'captcha_shown', 'counts how often the captcha was shown', []); $counter->inc(); } - public static function CaptchaCorrect() { + public static function CaptchaCorrect() + { $registry = \Prometheus\CollectorRegistry::getDefault(); $counter = $registry->getOrRegisterCounter('metager', 'captcha_correct', 'counts how often the captcha was solved correctly', []); $counter->inc(); } - public static function CaptchaAnswered() { + public static function CaptchaAnswered() + { $registry = \Prometheus\CollectorRegistry::getDefault(); $counter = $registry->getOrRegisterCounter('metager', 'captcha_answered', 'counts how often the captcha was answered', []); $counter->inc(); } -} \ No newline at end of file + public static function HumanVerificationSuccessfull() + { + $registry = \Prometheus\CollectorRegistry::getDefault(); + $counter = $registry->getOrRegisterCounter('metager', 'humanverification_success', 'counts how often humanverification middleware was successfull', []); + $counter->inc(); + } + + public static function HumanVerificationError() + { + $registry = \Prometheus\CollectorRegistry::getDefault(); + $counter = $registry->getOrRegisterCounter('metager', 'humanverification_error', 'counts how often humanverification middleware had an error', []); + $counter->inc(); + } +} diff --git a/chart/.gitignore b/chart/.gitignore deleted file mode 100644 index e6b3ed643d846953e94118ea579d3ee30eda8c9e..0000000000000000000000000000000000000000 --- a/chart/.gitignore +++ /dev/null @@ -1 +0,0 @@ -charts/* diff --git a/chart/.gitlab-ci.yml b/chart/.gitlab-ci.yml deleted file mode 100644 index a20ff2750f793aa68a7aec7fb624f0c6aef7874e..0000000000000000000000000000000000000000 --- a/chart/.gitlab-ci.yml +++ /dev/null @@ -1,17 +0,0 @@ -image: "registry.gitlab.com/gitlab-org/gitlab-build-images:alpine-helm" - -stages: - - test - - release - -lint: - stage: test - script: - - helm lint . - -release-chart: - stage: release - script: - - curl --fail --request POST --form "token=$CI_JOB_TOKEN" --form ref=master https://gitlab.com/api/v4/projects/2860651/trigger/pipeline - only: - - master@gitlab-org/charts/auto-deploy-app diff --git a/chart/.helmignore b/chart/.helmignore deleted file mode 100644 index f0c13194444163d1cba5c67d9e79231a62bc8f44..0000000000000000000000000000000000000000 --- a/chart/.helmignore +++ /dev/null @@ -1,21 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*~ -# Various IDEs -.project -.idea/ -*.tmproj diff --git a/chart/CONTRIBUTING.md b/chart/CONTRIBUTING.md index 589986f81fb88cbe8fb80647b6eb2bc1e1cfd89a..2354acbbc56c5570432f50fa7327eac208bba6e1 100644 --- a/chart/CONTRIBUTING.md +++ b/chart/CONTRIBUTING.md @@ -23,4 +23,31 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._ 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 committment to an open and welcoming environment. +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/chart/Chart.yaml b/chart/Chart.yaml index 66468482be5abf74f69cc3071985aaca34e77b52..87e9a4cf8d0b3c539b3a34fd333fce0335ec5058 100644 --- a/chart/Chart.yaml +++ b/chart/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v1 description: GitLab's Auto-deploy Helm Chart name: auto-deploy-app -version: 0.4.0 +version: 1.0.3 icon: https://gitlab.com/gitlab-com/gitlab-artwork/raw/master/logo/logo-square.png diff --git a/chart/README.md b/chart/README.md index a56d2c0167abf8722fb5353f9b24f203a421c101..095df11e46134d8d0a0602ea6a774e41b0375c8b 100644 --- a/chart/README.md +++ b/chart/README.md @@ -1,5 +1,14 @@ # 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 @@ -9,6 +18,9 @@ | 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` | @@ -17,7 +29,7 @@ | 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 shall command within an application Container as a Helm post-install Hook. Intended to run database initialization 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` | @@ -37,21 +49,25 @@ | 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) | `{}` | @@ -59,3 +75,10 @@ | 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/chart/templates/NOTES.txt b/chart/templates/NOTES.txt index 0ef42bbda93bc99a87ed5b4cd0ba6c86497be815..5491ce9318e4fd2578c6c86de79f2bd268d00739 100644 --- a/chart/templates/NOTES.txt +++ b/chart/templates/NOTES.txt @@ -1,5 +1,12 @@ -{{- if .Values.service.enabled -}} -Application should be accessible at: {{ .Values.service.url }} +{{- if and .Values.ingress.enabled .Values.service.enabled -}} +Application should be accessible at + + {{ .Values.service.url }} {{- else -}} -Application will be accessible at: {{ .Values.service.url }} when you deploy stable track. +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/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl index 913145e6ae42383d3f2810a035fc750dd1b0c7b4..add519dae35c3fa7a8f8dd4c67c1bd45b2641594 100644 --- a/chart/templates/_helpers.tpl +++ b/chart/templates/_helpers.tpl @@ -20,6 +20,14 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this {{- 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 "-" -}} @@ -29,5 +37,14 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this Get a hostname from URL */}} {{- define "hostname" -}} -{{- . | trimPrefix "http://" | trimPrefix "https://" | trimSuffix "/" | quote -}} +{{- . | trimPrefix "http://" | trimPrefix "https://" | trimSuffix "/" | trim | 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/chart/templates/db-initialize-job.yaml b/chart/templates/db-initialize-job.yaml index 12fd5112744e2ae3504a71c1bdd5edabd2685657..535731a51a2093396cb212f1b865234dd93ec395 100644 --- a/chart/templates/db-initialize-job.yaml +++ b/chart/templates/db-initialize-job.yaml @@ -24,7 +24,7 @@ spec: {{ toYaml .Values.image.secrets | indent 10 }} containers: - name: {{ .Chart.Name }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + image: {{ template "imagename" . }} command: ["/bin/sh"] args: ["-c", "{{ .Values.application.initializeCommand }}"] imagePullPolicy: {{ .Values.image.pullPolicy }} @@ -36,4 +36,8 @@ spec: 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/chart/templates/db-migrate-hook.yaml b/chart/templates/db-migrate-hook.yaml index b2d5aa7f613ad27fd728b48dcc0f83f23bdf9d56..78b871fea1421f5051acd415954d204c0316d1cf 100644 --- a/chart/templates/db-migrate-hook.yaml +++ b/chart/templates/db-migrate-hook.yaml @@ -24,7 +24,7 @@ spec: {{ toYaml .Values.image.secrets | indent 10 }} containers: - name: {{ .Chart.Name }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + image: {{ template "imagename" . }} command: ["/bin/sh"] args: ["-c", "{{ .Values.application.migrateCommand }}"] imagePullPolicy: {{ .Values.image.pullPolicy }} @@ -36,4 +36,8 @@ spec: 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/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index b790fdcba3e3bb955c5c11bbdb36f56de234240a..25bf29e5e87ae243868f6199f8477b10c6f308db 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -1,5 +1,5 @@ {{- if not .Values.application.initializeCommand -}} -apiVersion: extensions/v1beta1 +apiVersion: {{ default "extensions/v1beta1" .Values.deploymentApiVersion }} kind: Deployment metadata: name: {{ template "trackableappname" . }} @@ -14,7 +14,19 @@ metadata: 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: @@ -30,25 +42,28 @@ spec: tier: "{{ .Values.application.tier }}" release: {{ .Release.Name }} spec: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: worker - operator: NotIn - values: - - temp imagePullSecrets: {{ toYaml .Values.image.secrets | indent 10 }} - securityContext: - fsGroup: 2000 volumes: - name: mglogs-persistent-storage persistentVolumeClaim: claimName: mg-logs + - name: env-files + secret: + secretName: metager-env + - name: sumas + secret: + secretName: metager-sumas + - name: sumas-en + secret: + secretName: metager-sumas-en + - name: blacklist + secret: + secretName: metager-blacklist + - name: blacklist-ad + secret: + secretName: metager-ad-blacklist containers: - # Main PHP-FPM Container - name: {{ .Chart.Name }}-phpfpm image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} @@ -81,6 +96,42 @@ spec: - name: mglogs-persistent-storage mountPath: /html/storage/logs/metager readOnly: false + - name: env-files + mountPath: /root/.env + subPath: .env + readOnly: true + - name: env-files + mountPath: /html/database/seeds/UsersSeeder.php + subPath: UsersSeeder.php + readOnly: true + - name: env-files + mountPath: /html/config/spam.txt + subPath: spam.txt + readOnly: true + - name: sumas + mountPath: /html/config/sumas.json + subPath: sumas.json + readOnly: true + - name: sumas-en + mountPath: /html/config/sumasEn.json + subPath: sumasEn.json + readOnly: true + - name: blacklist + mountPath: /html/config/blacklistUrl.txt + subPath: blacklistUrl.txt + readOnly: true + - name: blacklist + mountPath: /html/config/blacklistDomains.txt + subPath: blacklistDomains.txt + readOnly: true + - name: blacklist-ad + mountPath: /html/config/adBlacklistUrl.txt + subPath: adBlacklistUrl.txt + readOnly: true + - name: blacklist-ad + mountPath: /html/config/adBlacklistDomains.txt + subPath: adBlacklistDomains.txt + readOnly: true resources: requests: cpu: 500m diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml index ec7cba476deee3d8e013c663c8da077cffda1d9e..b315cdbbd8db857965369e30427064371dc7df55 100644 --- a/chart/templates/ingress.yaml +++ b/chart/templates/ingress.yaml @@ -1,4 +1,4 @@ -{{- if .Values.service.enabled -}} +{{- 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: @@ -12,10 +12,15 @@ metadata: {{- if .Values.ingress.annotations }} {{ toYaml .Values.ingress.annotations | indent 4 }} {{- end }} -{{- if and .Values.ingress.modSecurity .Values.ingress.modSecurity.enabled }} +{{- with .Values.ingress.modSecurity }} +{{- if .enabled }} + nginx.ingress.kubernetes.io/modsecurity-transaction-id: "$server_name-$request_id" nginx.ingress.kubernetes.io/modsecurity-snippet: | - SecRuleEngine {{ .Values.ingress.modSecurity.secRuleEngine | default "DetectionOnly" | title }} - + 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: |- @@ -34,7 +39,7 @@ spec: - {{ template "hostname" .Values.service.url }} {{- if .Values.service.additionalHosts }} {{- range $host := .Values.service.additionalHosts }} - - {{ $host }} + - {{ template "hostname" $host }} {{- end -}} {{- end }} secretName: {{ .Values.ingress.tls.secretName | default (printf "%s-tls" (include "fullname" .)) }} @@ -48,10 +53,6 @@ spec: backend: serviceName: {{ template "fullname" . }} servicePort: {{ .Values.service.externalPort }} - - path: /wsb - backend: - serviceName: wsb - servicePort: 80 {{- if .Values.service.commonName }} - host: {{ template "hostname" .Values.service.commonName }} http: @@ -59,7 +60,7 @@ spec: {{- end -}} {{- if .Values.service.additionalHosts }} {{- range $host := .Values.service.additionalHosts }} - - host: {{ $host }} + - host: {{ template "hostname" $host }} http: <<: *httpRule {{- end -}} diff --git a/chart/templates/network-policy.yaml b/chart/templates/network-policy.yaml new file mode 100644 index 0000000000000000000000000000000000000000..79c0ed662514d8deb99bb19382105f8f445ce288 --- /dev/null +++ b/chart/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/chart/templates/service.yaml b/chart/templates/service.yaml index 47c59e2c25f19ed1440bd6b142746c932f2c3f92..676406be11de5feedd801876627055405ac51670 100644 --- a/chart/templates/service.yaml +++ b/chart/templates/service.yaml @@ -1,4 +1,4 @@ -{{- if .Values.service.enabled -}} +{{- if and (.Values.service.enabled) (eq .Values.application.track "stable") -}} apiVersion: v1 kind: Service metadata: diff --git a/chart/templates/worker-deployment.yaml b/chart/templates/worker-deployment.yaml index c59e78efa578deebe1742eea34b71e3271bc1312..050e234195e1064dc1ace110946719a1444aded2 100644 --- a/chart/templates/worker-deployment.yaml +++ b/chart/templates/worker-deployment.yaml @@ -3,7 +3,7 @@ apiVersion: v1 kind: List items: {{- range $workerName, $workerConfig := .Values.workers }} -- apiVersion: extensions/v1beta1 +- apiVersion: {{ default "extensions/v1beta1" $.Values.deploymentApiVersion }} kind: Deployment metadata: name: {{ template "trackableappname" $ }}-{{ $workerName }} @@ -17,7 +17,18 @@ items: 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: @@ -37,7 +48,7 @@ items: terminationGracePeriodSeconds: {{ $workerConfig.terminationGracePeriodSeconds }} containers: - name: {{ $.Chart.Name }}-{{ $workerName }} - image: "{{ $.Values.image.repository }}:{{ $.Values.image.tag }}" + image: {{ template "imagename" $ }} command: {{- range $workerConfig.command }} - {{ . }} @@ -52,7 +63,7 @@ items: - name: DATABASE_URL value: {{ $.Values.application.database_url | quote }} - name: GITLAB_ENVIRONMENT_NAME - value: {{ $.Values.gitlab.envName }} + value: {{ $.Values.gitlab.envName | quote }} livenessProbe: {{- if eq $.Values.livenessProbe.probeType "httpGet" }} httpGet: @@ -72,7 +83,7 @@ items: scheme: {{ $.Values.readinessProbe.scheme }} port: {{ $.Values.service.internalPort }} {{- else if eq $.Values.readinessProbe.probeType "tcpSocket" }} - tcpSocket: + tcpSocket: port: {{ $.Values.service.internalPort }} {{- end }} initialDelaySeconds: {{ $.Values.readinessProbe.initialDelaySeconds }} @@ -87,6 +98,6 @@ items: {{- end }} {{- end }} resources: - {{ toYaml $.Values.resources | indent 14 }} +{{ toYaml $.Values.resources | indent 12 }} {{- end -}} {{- end -}} diff --git a/chart/test/go.mod b/chart/test/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..4754f016d43ac382520197869527dc51191fb013 --- /dev/null +++ b/chart/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/chart/test/go.sum b/chart/test/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..8b5b570079168ea0282e9f6e0a576479efc4fd86 --- /dev/null +++ b/chart/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/chart/test/template_test.go b/chart/test/template_test.go new file mode 100644 index 0000000000000000000000000000000000000000..aa69a959abd869b5e9ada5d4f2b472aa85db3ea2 --- /dev/null +++ b/chart/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.3" + 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/chart/test/testdata/custom-policy.yaml b/chart/test/testdata/custom-policy.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8cc487aa725b2bb91a704caf5e39c3a3882be055 --- /dev/null +++ b/chart/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/chart/test/testdata/full-spec-policy.yaml b/chart/test/testdata/full-spec-policy.yaml new file mode 100644 index 0000000000000000000000000000000000000000..25254b5d2c51802230fd383f206e1848a9517879 --- /dev/null +++ b/chart/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/chart/test/testdata/modsecurity-ingress.yaml b/chart/test/testdata/modsecurity-ingress.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5b8b469c1ece14cd1fdbdda8b503b5a38026141f --- /dev/null +++ b/chart/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/chart/values.yaml b/chart/values.yaml index 0641dce2ad6efb74a0e22bf123e485308987a060..b7b44df77f6c48c420767fd2cd446cfe6aea0e44 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -2,10 +2,13 @@ # 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: Always + pullPolicy: IfNotPresent secrets: - name: gitlab-registry podAnnotations: {} @@ -17,9 +20,9 @@ application: secretName: secretChecksum: hpa: - enabled: true - minReplicas: 2 - maxReplicas: 20 + enabled: false + minReplicas: 1 + maxReplicas: 5 targetCPUUtilizationPercentage: 80 gitlab: app: @@ -37,6 +40,7 @@ service: externalPort: 5000 internalPort: 5000 ingress: + enabled: true tls: enabled: true secretName: "" @@ -46,34 +50,38 @@ ingress: modSecurity: enabled: false secRuleEngine: "DetectionOnly" + # secRules: + # - variable: "" + # operator: "" + # action: "" prometheus: metrics: false livenessProbe: path: "/" - initialDelaySeconds: 20 + initialDelaySeconds: 15 timeoutSeconds: 15 scheme: "HTTP" probeType: "httpGet" readinessProbe: path: "/" - initialDelaySeconds: 15 - timeoutSeconds: 15 + initialDelaySeconds: 5 + timeoutSeconds: 3 scheme: "HTTP" probeType: "httpGet" postgresql: - enabled: false + enabled: true managed: false managedClassSelector: # matchLabels: # stack: gitlab (This is an example. The labels should match the labels on the CloudSQLInstanceClass) resources: - limits: - cpu: 1 - memory: 1Gi +# limits: +# cpu: 100m +# memory: 128Mi requests: - cpu: 1 - memory: 1Gi +# cpu: 100m +# memory: 128Mi ## Configure PodDisruptionBudget ## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ @@ -83,7 +91,23 @@ podDisruptionBudget: # minAvailable: 1 maxUnavailable: 1 -workers: +## 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 diff --git a/resources/lang/de/impressum.php b/resources/lang/de/impressum.php index c5a9b7ae1857b1070a10aa5d2bf5fe2d47743c63..f41ffa6f7ade39a6df31b748d3d29a55c729bfc1 100644 --- a/resources/lang/de/impressum.php +++ b/resources/lang/de/impressum.php @@ -15,7 +15,7 @@ Deutschland/Germany', Tel.: ++49-(0)511-34000070 EMail: <a href="mailto:office@suma-ev.de">office@suma-ev.de</a><a href="/kontakt/"> - Public-PGP-Key</a> <a href="/kontakt/">Verschlüsselndes Kontaktformular</a>', - 'info.4' => 'Vorstand: Dominik Hebeler, Carsten Riel, <a href="https://www.ostfalia.de/cms/de/pws/jensen/index.html">Prof.Dr. Nils Jensen</a>', + 'info.4' => 'Vorstand: Dominik Hebeler, Carsten Riel', 'info.6' => 'Jugendschutzbeauftragte: Manuela Branz <a href="mailto:jugendschutz@metager.de">jugendschutz@metager.de</a>', 'info.8' => '"SUMA-EV - Verein für freien Wissenszugang" ist ein gemeinnütziger Verein, eingetragen in das Vereinsregister beim Amtsgericht Hannover diff --git a/resources/lang/en/impressum.php b/resources/lang/en/impressum.php index ba88f17dd67bb27fdb40f07c25d8a140e6e53126..31c36493521a5fedf42082b37bf0c915abd6b0f5 100644 --- a/resources/lang/en/impressum.php +++ b/resources/lang/en/impressum.php @@ -14,7 +14,7 @@ Deutschland/Germany', Tel.: ++49-(0)511-34000070 EMail: <a href="mailto:office@suma-ev.de">office@suma-ev.de</a><a href="/kontakt/"> - Public-PGP-Key</a> <a href="/kontakt/">encrypted contact form</a>', - 'info.4' => 'Board: Dominik Hebeler, Carsten Riel, <a href="https://www.ostfalia.de/cms/de/pws/jensen/index.html">Prof.Dr. Nils Jensen</a>', + 'info.4' => 'Board: Dominik Hebeler, Carsten Riel', 'info.6' => 'Youth Protection Commissioner: Manuela Branz <a href="mailto:jugendschutz@metager.de">jugendschutz@metager.de</a>', 'info.8' => '"SUMA-EV - Verein für freien Wissenszugang" is a charitable association, registered in the register of associations at the Amtsgericht Hannover under VR200033. diff --git a/resources/lang/es/impressum.php b/resources/lang/es/impressum.php index 2d7e5a036c5d425beef9d6b7fe2b508d9af09723..9035a55ac5fed77351a0e2ab908e14a8a082243d 100644 --- a/resources/lang/es/impressum.php +++ b/resources/lang/es/impressum.php @@ -6,7 +6,7 @@ return [ "info.1" => "Articulo de Wikipedia de <a href=\"http://de.wikipedia.org/wiki/Suma_e.V.\" target=\"_blank\" rel=\"noopener\">SUMA-EV</a>", "info.2" => " SUMA-EV Röselerstr. 3 D-30159 Hannover Deutschland/Germany", "info.3" => "Contacto: Tel.: ++49-(0)511-34000070 EMail: <a href=\"mailto:office@suma-ev.de\">office@suma-ev.de</a><a href=\"/kontakt/\"> - Public-PGP-Key</a> <a href=\"/kontakt/\">Formulario encriptado</a>", - "info.4" => "Junta directiva: Dominik Hebeler, Carsten Riel, <a href=\"https://www2.ostfalia.de/cms/de/pws/jensenn/index.html\">Prof.Dr. Nils Jensen</a>", + "info.4" => "Junta directiva: Dominik Hebeler, Carsten Riel", "info.6" => "Encargado de protección de menores: Manuela Branz <a href=\"mailto:jugendschutz@metager.de\">jugendschutz@metager.de</a>", "info.8" => "\"SUMA-EV - Verein für freien Wissenszugang\" es una asociación sin fines de lucro, registrado en el registro de asociaciones del Amtsgericht Hannover bajo numero VR200033. Número de identificación a efectos del IVA: DE 300 464 091 La \"Gottfried Wilhelm Leibniz Universität Hannover\" es una entidad del derecho publico.", "info.9" => "Exención de responsabilidad",