# This is stripped down version of the .gitlab-ci.yml found # here: https://gitlab.com/joshlambert/autodevops-deploy. # # It performs only the deploy stage. image: alpine:latest variables: # AUTO_DEVOPS_DOMAIN is the application deployment domain and should be set as a variable at the group or project level. AUTO_DEVOPS_DOMAIN: $AUTO_DEVOPS_DOMAIN POSTGRES_USER: user POSTGRES_PASSWORD: testing-password POSTGRES_ENABLED: 'false' POSTGRES_DB: $CI_ENVIRONMENT_SLUG KUBERNETES_VERSION: 1.11.6 HELM_VERSION: 2.12.2 DOCKER_DRIVER: overlay2 stages: - production # This job continuously deploys to staging/production on every push to `master`. production: stage: production script: - check_kube_domain - install_dependencies - download_chart - ensure_namespace - initialize_tiller - create_secret - deploy - persist_environment_url environment: name: production url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN artifacts: paths: [environment_url.txt] only: refs: - master kubernetes: active # --------------------------------------------------------------------------- .auto_devops: &auto_devops | # Auto DevOps variables and functions [[ "$TRACE" ]] && set -x auto_database_url=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${CI_ENVIRONMENT_SLUG}-postgres:5432/${POSTGRES_DB} export DATABASE_URL=${DATABASE_URL-$auto_database_url} export CI_APPLICATION_REPOSITORY=$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG export CI_APPLICATION_TAG=$CI_COMMIT_SHA export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID} export TILLER_NAMESPACE=$KUBE_NAMESPACE # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') function get_replicas() { track="${1:-stable}" percentage="${2:-100}" env_track=$( echo $track | tr -s '[:lower:]' '[:upper:]' ) env_slug=$( echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]' ) if [[ "$track" == "stable" ]] || [[ "$track" == "rollout" ]]; then # for stable track get number of replicas from `PRODUCTION_REPLICAS` eval new_replicas=\$${env_slug}_REPLICAS if [[ -z "$new_replicas" ]]; then new_replicas=$REPLICAS fi else # for all tracks get number of replicas from `CANARY_PRODUCTION_REPLICAS` eval new_replicas=\$${env_track}_${env_slug}_REPLICAS if [[ -z "$new_replicas" ]]; then eval new_replicas=\${env_track}_REPLICAS fi fi replicas="${new_replicas:-1}" replicas="$(($replicas * $percentage / 100))" # always return at least one replicas if [[ $replicas -gt 0 ]]; then echo "$replicas" else echo 1 fi } # Extracts variables prefixed with K8S_SECRET_ # and creates a Kubernetes secret. # # e.g. If we have the following environment variables: # K8S_SECRET_A=value1 # K8S_SECRET_B=multi\ word\ value # # Then we will create a secret with the following key-value pairs: # data: # A: dmFsdWUxCg== # B: bXVsdGkgd29yZCB2YWx1ZQo= function create_application_secret() { track="${1-stable}" export APPLICATION_SECRET_NAME=$(application_secret_name "$track") env | sed -n "s/^K8S_SECRET_\(.*\)$/\1/p" > k8s_prefixed_variables kubectl create secret \ -n "$KUBE_NAMESPACE" generic "$APPLICATION_SECRET_NAME" \ --from-env-file k8s_prefixed_variables -o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f - export APPLICATION_SECRET_CHECKSUM=$(cat k8s_prefixed_variables | sha256sum | cut -d ' ' -f 1) rm k8s_prefixed_variables } function deploy_name() { name="$CI_ENVIRONMENT_SLUG" track="${1-stable}" if [[ "$track" != "stable" ]]; then name="$name-$track" fi echo $name } function application_secret_name() { track="${1-stable}" name=$(deploy_name "$track") echo "${name}-secret" } function deploy() { track="${1-stable}" percentage="${2:-100}" name=$(deploy_name "$track") replicas="1" service_enabled="true" postgres_enabled="$POSTGRES_ENABLED" # if track is different than stable, # re-use all attached resources if [[ "$track" != "stable" ]]; then service_enabled="false" postgres_enabled="false" fi replicas=$(get_replicas "$track" "$percentage") if [[ "$CI_PROJECT_VISIBILITY" != "public" ]]; then secret_name='gitlab-registry' else secret_name='' fi create_application_secret "$track" env_slug=$(echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]') eval env_ADDITIONAL_HOSTS=\$${env_slug}_ADDITIONAL_HOSTS if [ -n "$env_ADDITIONAL_HOSTS" ]; then additional_hosts="{$env_ADDITIONAL_HOSTS}" elif [ -n "$ADDITIONAL_HOSTS" ]; then additional_hosts="{$ADDITIONAL_HOSTS}" fi if [[ -n "$DB_INITIALIZE" && -z "$(helm ls -q "^$name$")" ]]; then echo "Deploying first release with database initialization..." helm upgrade --install \ --wait \ --set service.enabled="$service_enabled" \ --set releaseOverride="$CI_ENVIRONMENT_SLUG" \ --set image.repository="registry.gitlab.com/joshlambert/ruby-gke/master" \ --set image.tag="63492726c2264a0277141d6a6573c3d22ecd7de3" \ --set image.pullPolicy=IfNotPresent \ --set image.secrets[0].name="$secret_name" \ --set application.track="$track" \ --set application.database_url="$DATABASE_URL" \ --set application.secretName="$APPLICATION_SECRET_NAME" \ --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \ --set service.url="$CI_ENVIRONMENT_URL" \ --set service.additionalHosts="$additional_hosts" \ --set replicaCount="$replicas" \ --set postgresql.enabled="$postgres_enabled" \ --set postgresql.nameOverride="postgres" \ --set postgresql.postgresUser="$POSTGRES_USER" \ --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \ --set postgresql.postgresDatabase="$POSTGRES_DB" \ --set application.initializeCommand="$DB_INITIALIZE" \ --set gitlab.app="$CI_PROJECT_PATH_SLUG" \ --set gitlab.env="$CI_ENVIRONMENT_SLUG" \ --namespace="$KUBE_NAMESPACE" \ "$name" \ chart/ echo "Deploying second release..." helm upgrade --reuse-values \ --wait \ --set application.initializeCommand="" \ --set application.migrateCommand="$DB_MIGRATE" \ --namespace="$KUBE_NAMESPACE" \ "$name" \ chart/ else echo "Deploying new release..." helm upgrade --install \ --wait \ --set service.enabled="$service_enabled" \ --set releaseOverride="$CI_ENVIRONMENT_SLUG" \ --set image.repository="registry.gitlab.com/joshlambert/ruby-gke/master" \ --set image.tag="63492726c2264a0277141d6a6573c3d22ecd7de3" \ --set image.pullPolicy=IfNotPresent \ --set image.secrets[0].name="$secret_name" \ --set application.track="$track" \ --set application.database_url="$DATABASE_URL" \ --set application.secretName="$APPLICATION_SECRET_NAME" \ --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \ --set service.url="$CI_ENVIRONMENT_URL" \ --set service.additionalHosts="$additional_hosts" \ --set replicaCount="$replicas" \ --set postgresql.enabled="$postgres_enabled" \ --set postgresql.nameOverride="postgres" \ --set postgresql.postgresUser="$POSTGRES_USER" \ --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \ --set postgresql.postgresDatabase="$POSTGRES_DB" \ --set application.migrateCommand="$DB_MIGRATE" \ --set gitlab.app="$CI_PROJECT_PATH_SLUG" \ --set gitlab.env="$CI_ENVIRONMENT_SLUG" \ --namespace="$KUBE_NAMESPACE" \ "$name" \ chart/ fi kubectl rollout status -n "$KUBE_NAMESPACE" -w "deployment/$name" } function install_dependencies() { apk add -U openssl curl tar gzip bash ca-certificates git wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.23-r3/glibc-2.23-r3.apk apk add glibc-2.23-r3.apk rm glibc-2.23-r3.apk curl "https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | tar zx mv linux-amd64/helm /usr/bin/ mv linux-amd64/tiller /usr/bin/ helm version --client tiller -version curl -L -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl" chmod +x /usr/bin/kubectl kubectl version --client } function download_chart() { if [[ ! -d chart ]]; then auto_chart=${AUTO_DEVOPS_CHART:-gitlab/auto-deploy-app} auto_chart_name=$(basename $auto_chart) auto_chart_name=${auto_chart_name%.tgz} else auto_chart="chart" auto_chart_name="chart" fi helm init --client-only helm repo add gitlab https://charts.gitlab.io if [[ ! -d "$auto_chart" ]]; then helm fetch ${auto_chart} --untar fi if [ "$auto_chart_name" != "chart" ]; then mv ${auto_chart_name} chart fi helm dependency update chart/ helm dependency build chart/ } function ensure_namespace() { kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE" } function check_kube_domain() { if [ -z ${AUTO_DEVOPS_DOMAIN+x} ]; then echo "In order to deploy or use Review Apps, AUTO_DEVOPS_DOMAIN variable must be set" echo "You can do it in Auto DevOps project settings or defining a secret variable at group or project level" echo "You can also manually add it in .gitlab-ci.yml" false else true fi } function initialize_tiller() { echo "Checking Tiller..." export HELM_HOST="localhost:44134" tiller -listen ${HELM_HOST} -alsologtostderr > /dev/null 2>&1 & echo "Tiller is listening on ${HELM_HOST}" if ! helm version --debug; then echo "Failed to init Tiller." return 1 fi echo "" } function create_secret() { echo "Create secret..." if [[ "$CI_PROJECT_VISIBILITY" == "public" ]]; then return fi kubectl create secret -n "$KUBE_NAMESPACE" \ docker-registry gitlab-registry \ --docker-server="$CI_REGISTRY" \ --docker-username="$CI_REGISTRY_USER" \ --docker-password="$CI_REGISTRY_PASSWORD" \ --docker-email="$GITLAB_USER_EMAIL" \ -o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f - } function persist_environment_url() { echo $CI_ENVIRONMENT_URL > environment_url.txt } before_script: - *auto_devops