Last active
August 26, 2020 09:48
-
-
Save wbyoung/81ff1c12818564d15f362a4781fe6f44 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
set -e | |
JQ="jq --raw-output --exit-status" | |
if [ -x "$(command -v kustomize)" ]; then | |
KUSTOMIZE="kustomize build" | |
else | |
KUSTOMIZE="kubectl kustomize" | |
fi | |
escape-json() { | |
python -c "import sys; import json; sys.stdout.write(json.dumps(sys.stdin.read().strip())[1:-1])" | |
} | |
escape-subst() { | |
# adapted to read from stdin from: https://stackoverflow.com/a/29613573/98069 | |
IFS= read -d '' -r < <(sed -e ':a' -e '$!{N;ba' -e '}' -e 's/[&/\]/\\&/g; s/\n/\\&/g') | |
printf %s "${REPLY%$'\n'}" | |
} | |
# @env project_id | |
bootstrap-gcloud() { | |
if [ ! -x "$(command -v gcloud)" ]; then | |
echo Installing gcloud command... | |
curl -sSL https://sdk.cloud.google.com | bash -s -- --disable-prompts > /dev/null | |
export PATH="$PATH:/root/google-cloud-sdk/bin" | |
fi | |
if [ -n "$GCP_ACCOUNT_KEY" ]; then | |
echo "$GCP_ACCOUNT_KEY" > /tmp/service-account-key.json | |
gcloud auth activate-service-account \ | |
--key-file=/tmp/service-account-key.json | |
fi | |
gcloud auth configure-docker --quiet --project=$project_id | |
} | |
# @arg remote_image | |
# @env project_id | |
# @env commit_sha | |
# @returns via stderr | |
image-tag() { | |
local remote_image="$1" | |
echo "gcr.io/$project_id/${remote_image%%:*}:${commit_sha:0:9}" >&2 | |
} | |
# @arg local_image | |
# @arg tag | |
# @env project_id | |
push-image() { | |
local local_image="$1" | |
local tag="$2" | |
echo 'Pushing container image...' | |
docker tag "$local_image" "$tag" | |
docker push "$tag" | |
} | |
# @arg manifest | |
# @env commit_sha | |
# @env config_keys | |
# @env config_value | |
# @returns via stderr | |
build-manifest() { | |
local manifest="$1" | |
local definition=$( | |
$KUSTOMIZE $manifest | | |
sed \ | |
-e "s/{{COMMIT_SHA}}/${commit_sha:0:9}/g" \ | |
) | |
for i in "${!config_keys[@]}"; do | |
local key="${config_keys[$i]}" | |
local value="${config_values[$i]}" | |
local escaped_value=$(echo "$value" | escape-subst) | |
definition=$( | |
echo "$definition" | sed -e "s/{{$key}}/$escaped_value/g" | |
) | |
done | |
echo "$definition" >&2 | |
} | |
usage() { | |
if [ $# -gt 1 ]; then | |
echo $@ | |
echo | |
fi | |
echo 'gke-deploy' | |
echo ' --project-id <value>' | |
echo ' --zone <value>' | |
echo ' --pre-deploy <value>' | |
echo ' --image <local> <remote>' | |
echo ' --manifest <value>' | |
echo ' [--config <key> <value>]' | |
echo | |
echo 'OPTIONS' | |
echo | |
echo ' --project-id (string)' | |
echo ' The GCP account ID' | |
echo | |
echo ' --zone (string)' | |
echo ' The GCP zone' | |
echo | |
echo ' --pre-deploy <name> <container> <manifest> (string, string)' | |
echo ' The pod name, container name & path to a Kustomize manifest with ' | |
echo ' only that Kubernetes pod in it. The pod will be run before the ' | |
echo ' main deployment.' | |
echo | |
echo ' --image <local> <remote> (string, string>)' | |
echo ' The name of an image to push (can be specified multiple times)' | |
echo | |
echo ' --manifest (string)' | |
echo ' The path to the Kustomize manifest with the Kubernetes ' | |
echo ' configuration' | |
echo | |
echo ' [--config <key> <value>]' | |
echo ' Add a substitution value for the manifest, i.e.' | |
echo | |
echo ' gke-deploy --config MY_VAR true' | |
echo | |
echo ' Would replace occurrences of {{MY_VAR}} with true in the manifest. ' | |
echo ' This option can be repeated for multiple config variables.' | |
echo | |
} | |
gke-deploy() { | |
local config_keys=() | |
local config_values=() | |
local local_images=() | |
local remote_images=() | |
while [ $# -ne 0 ]; do | |
case "$1" in | |
--project-id) | |
shift; local project_id="$1" | |
shift | |
;; | |
--zone) | |
shift; local zone="$1" | |
shift | |
;; | |
--pre-deploy) | |
shift; local pre_name="$1" | |
shift; local pre_container="$1" | |
shift; local pre_manifest="$1" | |
shift | |
;; | |
--image) | |
shift; local_images+=("$1") | |
shift; remote_images+=("$1") | |
shift | |
;; | |
-m|--manifest) | |
shift; local manifest="$1" | |
shift | |
;; | |
-C|--cluster) | |
shift; local cluster="$1" | |
shift | |
;; | |
-c|--config) | |
shift; config_keys+=("$1") | |
shift; config_values+=("$1") | |
shift | |
;; | |
-h|--help) | |
usage | |
exit 0 | |
;; | |
*) | |
usage unknown option "$1" | |
exit 1 | |
;; | |
esac | |
done | |
[ -n "$project_id" ] || { usage missing --project-id; exit 1; } | |
[ -n "$zone" ] || { usage missing --zone; exit 1; } | |
[ -n "$local_images" ] || { usage missing --image; exit 1; } | |
[ -n "$remote_images" ] || { usage missing --image; exit 1; } | |
[ -n "$manifest" ] || { usage missing --manifest; exit 1; } | |
[ -n "$cluster" ] || { usage missing --cluster; exit 1; } | |
for local_image in "${local_images[@]}"; do | |
[ -n "$local_image" ] || { usage missing --image local value; exit 1; } | |
done | |
for remote_image in "${remote_images[@]}"; do | |
[ -n "$remote_image" ] || { usage missing --image remote value; exit 1; } | |
done | |
for key in "${config_keys[@]}"; do | |
[ -n "$key" ] || { usage missing --config key; exit 1; } | |
done | |
for value in "${config_values[@]}"; do | |
[ -n "$value" ] || { usage missing --config value; exit 1; } | |
done | |
bootstrap-gcloud | |
gcloud container clusters get-credentials $cluster \ | |
--project=$project_id \ | |
--zone=$zone | |
local commit_sha=$(git rev-parse HEAD) | |
for i in "${!local_images[@]}"; do | |
local local_image="${local_images[$i]}" | |
local remote_image="${remote_images[$i]}" | |
local tag=$(image-tag $remote_image 3>&2 2>&1 1>&3) | |
push-image "$local_image" "$tag" | |
done | |
local apply_manifest=$(build-manifest "$manifest" 3>&2 2>&1 1>&3) | |
if [ -n "$pre_name" ] && [ -n "$pre_container" ] && [ -n "$pre_manifest" ]; then | |
local pre_complete=false | |
local pre_success=false | |
local pre_apply_manifest=$( | |
build-manifest "$pre_manifest" 3>&2 2>&1 1>&3 | |
) | |
echo "Running pre-deployment job $pre_name" | |
echo "$pre_apply_manifest" | |
echo "$pre_apply_manifest" | kubectl apply -f - | |
# wait for target container in pod to finish | |
while true; do | |
local pre_status=$(kubectl get pod migrate -ojson) | |
local pre_phase=$(echo "$pre_status" | $JQ .status.phase) | |
local state_query=".status.containerStatuses[] | \ | |
select(.name == \"$pre_container\") | \ | |
.state | keys[0]" | |
local code_query=".status.containerStatuses[] | \ | |
select(.name == \"$pre_container\") | \ | |
.state.terminated.exitCode" | |
local pre_container_state=$(echo "$pre_status" | $JQ "$state_query") | |
local pre_termination_code=$(echo "$pre_status" | $JQ "$code_query") | |
local message="" | |
message+="pod \"$pre_name\" $pre_phase, " | |
message+="container \"$pre_container\" $pre_container_state" | |
[ "$pre_phase" != "Pending" ] && \ | |
[ "$pre_phase" != "Running" ] && \ | |
pre_complete=true | |
[ "$pre_container_state" = "terminated" ] && \ | |
pre_complete=true | |
[ "$pre_container_state" = "terminated" ] && \ | |
[ "$pre_termination_code" = "0" ] && \ | |
pre_success=true | |
[ "$pre_complete" == "true" ] && message+=", (exit $pre_termination_code)" | |
echo "$message" | |
[ "$pre_complete" == "true" ] && break | |
sleep 1 | |
done | |
kubectl logs --timestamps -c $pre_container pods/$pre_name | |
if [ "$pre_success" != "true" ]; then | |
echo "Pre-deploy task failed; pod may still exist." | |
exit 1 | |
fi | |
kubectl delete pod $pre_name | |
fi | |
echo "$apply_manifest" | |
echo "$apply_manifest" | kubectl apply -f - | |
} | |
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then | |
gke-deploy "$@" | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment