review-apps.sh 11.8 KB
Newer Older
1 2 3
[[ "$TRACE" ]] && set -x
export TILLER_NAMESPACE="$KUBE_NAMESPACE"

4 5
function echoerr() { printf "\033[0;31m%s\n\033[0m" "$*" >&2; }

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
function check_kube_domain() {
  if [ -z ${REVIEW_APPS_DOMAIN+x} ]; then
    echo "In order to deploy or use Review Apps, REVIEW_APPS_DOMAIN variable must be set"
    echo "You can do it in Auto DevOps project settings or defining a variable at group or project level"
    echo "You can also manually add it in .gitlab-ci.yml"
    false
  else
    true
  fi
}

function download_gitlab_chart() {
  curl -o gitlab.tar.bz2 https://gitlab.com/charts/gitlab/-/archive/$GITLAB_HELM_CHART_REF/gitlab-$GITLAB_HELM_CHART_REF.tar.bz2
  tar -xjf gitlab.tar.bz2
  cd gitlab-$GITLAB_HELM_CHART_REF

  helm init --client-only
  helm repo add gitlab https://charts.gitlab.io
  helm dependency update
  helm dependency build
}

function ensure_namespace() {
  kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE"
}

function install_tiller() {
  echo "Checking Tiller..."
  helm init --upgrade
  kubectl rollout status -n "$TILLER_NAMESPACE" -w "deployment/tiller-deploy"
  if ! helm version --debug; then
    echo "Failed to init Tiller."
    return 1
  fi
  echo ""
}

function create_secret() {
  echo "Create secret..."

  kubectl create secret generic -n "$KUBE_NAMESPACE" \
    $CI_ENVIRONMENT_SLUG-gitlab-initial-root-password \
    --from-literal=password=$REVIEW_APPS_ROOT_PASSWORD \
    --dry-run -o json | kubectl apply -f -
}

I
Ian Baum 已提交
52
function deployExists() {
I
Ian Baum 已提交
53 54 55 56
  local namespace="${1}"
  local deploy="${2}"
  helm status --tiller-namespace "${namespace}" "${deploy}" >/dev/null 2>&1
  return $?
I
Ian Baum 已提交
57 58
}

59 60
function previousDeployFailed() {
  set +e
I
Ian Baum 已提交
61 62 63
  deploy="${1}"
  echo "Checking for previous deployment of ${deploy}"
  deployment_status=$(helm status ${deploy} >/dev/null 2>&1)
64 65 66 67
  status=$?
  # if `status` is `0`, deployment exists, has a status
  if [ $status -eq 0 ]; then
    echo "Previous deployment found, checking status"
I
Ian Baum 已提交
68
    deployment_status=$(helm status ${deploy} | grep ^STATUS | cut -d' ' -f2)
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
    echo "Previous deployment state: $deployment_status"
    if [[ "$deployment_status" == "FAILED" || "$deployment_status" == "PENDING_UPGRADE" || "$deployment_status" == "PENDING_INSTALL" ]]; then
      status=0;
    else
      status=1;
    fi
  else
    echo "Previous deployment NOT found."
  fi
  set -e
  return $status
}

function deploy() {
  track="${1-stable}"
  name="$CI_ENVIRONMENT_SLUG"

  if [[ "$track" != "stable" ]]; then
    name="$name-$track"
  fi

  replicas="1"
  service_enabled="false"
  postgres_enabled="$POSTGRES_ENABLED"
93 94 95 96 97 98 99 100 101 102

  IMAGE_REPOSITORY="registry.gitlab.com/gitlab-org/build/cng-mirror"
  IMAGE_VERSION="${CI_PROJECT_NAME#gitlab-}"
  gitlab_migrations_image_repository="${IMAGE_REPOSITORY}/gitlab-rails-${IMAGE_VERSION}"
  gitlab_sidekiq_image_repository="${IMAGE_REPOSITORY}/gitlab-sidekiq-${IMAGE_VERSION}"
  gitlab_unicorn_image_repository="${IMAGE_REPOSITORY}/gitlab-unicorn-${IMAGE_VERSION}"
  gitlab_task_runner_image_repository="${IMAGE_REPOSITORY}/gitlab-task-runner-${IMAGE_VERSION}"
  gitlab_gitaly_image_repository="${IMAGE_REPOSITORY}/gitaly"
  gitlab_shell_image_repository="${IMAGE_REPOSITORY}/gitlab-shell"
  gitlab_workhorse_image_repository="${IMAGE_REPOSITORY}/gitlab-workhorse-${IMAGE_VERSION}"
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122

  # canary uses stable db
  [[ "$track" == "canary" ]] && postgres_enabled="false"

  env_track=$( echo $track | tr -s  '[:lower:]'  '[:upper:]' )
  env_slug=$( echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s  '[:lower:]'  '[:upper:]' )

  if [[ "$track" == "stable" ]]; then
    # for stable track get number of replicas from `PRODUCTION_REPLICAS`
    eval new_replicas=\$${env_slug}_REPLICAS
    service_enabled="true"
  else
    # for all tracks get number of replicas from `CANARY_PRODUCTION_REPLICAS`
    eval new_replicas=\$${env_track}_${env_slug}_REPLICAS
  fi
  if [[ -n "$new_replicas" ]]; then
    replicas="$new_replicas"
  fi

  # Cleanup and previous installs, as FAILED and PENDING_UPGRADE will cause errors with `upgrade`
I
Ian Baum 已提交
123
  if [ "$CI_ENVIRONMENT_SLUG" != "production" ] && previousDeployFailed "$CI_ENVIRONMENT_SLUG" ; then
124 125 126 127
    echo "Deployment in bad state, cleaning up $CI_ENVIRONMENT_SLUG"
    delete
    cleanup
  fi
128 129 130

  create_secret

131 132 133 134 135 136 137
  helm repo add gitlab https://charts.gitlab.io/
  helm dep update .

HELM_CMD=$(cat << EOF
  helm upgrade --install \
    --wait \
    --timeout 600 \
138
    --set global.appConfig.enableUsagePing=false \
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
    --set releaseOverride="$CI_ENVIRONMENT_SLUG" \
    --set global.hosts.hostSuffix="$HOST_SUFFIX" \
    --set global.hosts.domain="$REVIEW_APPS_DOMAIN" \
    --set certmanager.install=false \
    --set global.ingress.configureCertmanager=false \
    --set global.ingress.tls.secretName=tls-cert \
    --set global.ingress.annotations."external-dns\.alpha\.kubernetes\.io/ttl"="10"
    --set gitlab.unicorn.resources.requests.cpu=200m \
    --set gitlab.sidekiq.resources.requests.cpu=100m \
    --set gitlab.gitlab-shell.resources.requests.cpu=100m \
    --set redis.resources.requests.cpu=100m \
    --set minio.resources.requests.cpu=100m \
    --set gitlab.migrations.image.repository="$gitlab_migrations_image_repository" \
    --set gitlab.migrations.image.tag="$CI_COMMIT_REF_NAME" \
    --set gitlab.sidekiq.image.repository="$gitlab_sidekiq_image_repository" \
    --set gitlab.sidekiq.image.tag="$CI_COMMIT_REF_NAME" \
    --set gitlab.unicorn.image.repository="$gitlab_unicorn_image_repository" \
    --set gitlab.unicorn.image.tag="$CI_COMMIT_REF_NAME" \
157 158
    --set gitlab.task-runner.image.repository="$gitlab_task_runner_image_repository" \
    --set gitlab.task-runner.image.tag="$CI_COMMIT_REF_NAME" \
159 160 161 162 163 164
    --set gitlab.gitaly.image.repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitaly" \
    --set gitlab.gitaly.image.tag="v$GITALY_VERSION" \
    --set gitlab.gitlab-shell.image.repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitlab-shell" \
    --set gitlab.gitlab-shell.image.tag="v$GITLAB_SHELL_VERSION" \
    --set gitlab.unicorn.workhorse.image="$gitlab_workhorse_image_repository" \
    --set gitlab.unicorn.workhorse.tag="$CI_COMMIT_REF_NAME" \
I
Ian Baum 已提交
165
    --set nginx-ingress.controller.config.ssl-ciphers="ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4" \
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
    --namespace="$KUBE_NAMESPACE" \
    --version="$CI_PIPELINE_ID-$CI_JOB_ID" \
    "$name" \
    .
EOF
)

  echo "Deploying with:"
  echo $HELM_CMD

  eval $HELM_CMD
}

function delete() {
  track="${1-stable}"
  name="$CI_ENVIRONMENT_SLUG"

183 184 185 186 187
  if [ -z "$CI_ENVIRONMENT_SLUG" ]; then
    echo "No release given, aborting the delete!"
    return
  fi

188 189 190 191
  if [[ "$track" != "stable" ]]; then
    name="$name-$track"
  fi

192 193 194 195 196
  if ! deployExists "${KUBE_NAMESPACE}" "${name}"; then
    echo "The release $name doesn't exist, aborting the cleanup!"
    return
  fi

197 198 199 200 201
  echo "Deleting release '$name'..."
  helm delete --purge "$name" || true
}

function cleanup() {
202 203 204 205 206 207 208 209 210 211
  if [ -z "$CI_ENVIRONMENT_SLUG" ]; then
    echo "No release given, aborting the delete!"
    return
  fi

  echo "Cleaning up '$CI_ENVIRONMENT_SLUG'..."
  kubectl -n "$KUBE_NAMESPACE" delete \
    ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa \
    -l release="$CI_ENVIRONMENT_SLUG" \
  || true
212
}
I
Ian Baum 已提交
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232

function install_external_dns() {
  local release_name="dns-gitlab-review-app"
  local domain=$(echo "${REVIEW_APPS_DOMAIN}" | awk -F. '{printf "%s.%s", $(NF-1), $NF}')

  if ! deployExists "${KUBE_NAMESPACE}" "${release_name}" || previousDeployFailed "${release_name}" ; then
    echo "Installing external-dns helm chart"
    helm repo update
    helm install stable/external-dns \
      -n "${release_name}" \
      --namespace "${KUBE_NAMESPACE}" \
      --set provider="aws" \
      --set aws.secretKey="${REVIEW_APPS_AWS_SECRET_KEY}" \
      --set aws.accessKey="${REVIEW_APPS_AWS_ACCESS_KEY}" \
      --set aws.zoneType="public" \
      --set domainFilters[0]="${domain}" \
      --set txtOwnerId="${KUBE_NAMESPACE}" \
      --set rbac.create="true"
  fi
}
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291

function get_pod() {
  local app_name="${1}"
  local status="${2-Running}"
  get_pod_cmd="kubectl get pods -n ${KUBE_NAMESPACE} --field-selector=status.phase=${status} -lapp=${app_name},release=${CI_ENVIRONMENT_SLUG} --no-headers -o=custom-columns=NAME:.metadata.name"
  echoerr "Running '${get_pod_cmd}'"

  while true; do
    local pod_name="$(eval $get_pod_cmd)"
    [[ "${pod_name}" == "" ]] || break

    echoerr "Waiting till '${app_name}' pod is ready";
    sleep 5;
  done

  echoerr "The pod name is '${pod_name}'."
  echo "${pod_name}"
}

function add_license() {
  if [ -z "${REVIEW_APPS_EE_LICENSE}" ]; then echo "License not found" && return; fi

  task_runner_pod=$(get_pod "task-runner");
  if [ -z "${task_runner_pod}" ]; then echo "Task runner pod not found" && return; fi

  echo "${REVIEW_APPS_EE_LICENSE}" > /tmp/license.gitlab
  kubectl -n "$KUBE_NAMESPACE" cp /tmp/license.gitlab ${task_runner_pod}:/tmp/license.gitlab
  rm /tmp/license.gitlab

  kubectl -n "$KUBE_NAMESPACE" exec -it ${task_runner_pod} -- /srv/gitlab/bin/rails runner -e production \
    '
    content = File.read("/tmp/license.gitlab").strip;
    FileUtils.rm_f("/tmp/license.gitlab");

    unless License.where(data:content).empty?
      puts "License already exists";
      Kernel.exit 0;
    end

    unless License.new(data: content).save
      puts "Could not add license";
      Kernel.exit 0;
    end

    puts "License added";
    '
}

function get_job_id() {
  local job_name="${1}"
  local query_string="${2:+&${2}}"

  local max_page=3
  local page=1

  while true; do
    local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/pipelines/${CI_PIPELINE_ID}/jobs?per_page=100&page=${page}${query_string}"
    echoerr "GET ${url}"

292 293
    local job_id=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq "map(select(.name == \"${job_name}\")) | map(.id) | last")
    [[ "${job_id}" == "null" && "${page}" -lt "$max_page" ]] || break
294

295
    let "page++"
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
  done

  if [[ "${job_id}" == "" ]]; then
    echoerr "The '${job_name}' job ID couldn't be retrieved!"
  else
    echoerr "The '${job_name}' job ID is ${job_id}"
    echo "${job_id}"
  fi
}

function play_job() {
  local job_name="${1}"
  local job_id=$(get_job_id "${job_name}" "scope=manual");
  if [ -z "${job_id}" ]; then return; fi

  local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}/play"
  echoerr "POST ${url}"

  local job_url=$(curl --silent --show-error --request POST --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq ".web_url")
  echo "Manual job '${job_name}' started at: ${job_url}"
}

function wait_for_job_to_be_done() {
  local job_name="${1}"
  local query_string="${2}"
  local job_id=$(get_job_id "${job_name}" "${query_string}");
  if [ -z "${job_id}" ]; then return; fi

  echoerr "Waiting for the '${job_name}' job to finish..."

  local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}"
  echo "GET ${url}"

  # In case the job hasn't finished yet. Keep trying until the job times out.
  local interval=30
331
  local elapsed_seconds=0
332 333 334 335 336
  while true; do
    local job_status=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq ".status" | sed -e s/\"//g)
    [[ "${job_status}" == "pending" || "${job_status}" == "running" ]] || break

    printf "."
337
    let "elapsed_seconds+=interval"
338 339 340
    sleep ${interval}
  done

341 342
  local elapsed_minutes=$((elapsed_seconds / 60))
  echoerr "Waited '${job_name}' for ${elapsed_minutes} minutes."
343 344 345 346 347 348 349 350 351

  if [[ "${job_status}" == "failed" ]]; then
    echo "The '${job_name}' failed."
  elif [[ "${job_status}" == "manual" ]]; then
    echo "The '${job_name}' is manual."
  else
    echo "The '${job_name}' passed."
  fi
}