Skip to content

Commit 5bc1456

Browse files
committed
Terraform workflow for preview envs
1 parent 248381b commit 5bc1456

File tree

12 files changed

+364
-0
lines changed

12 files changed

+364
-0
lines changed

.github/CODEOWNERS

+3
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@
9797
/.werft/*installer-tests* @gitpod-io/engineering-self-hosted
9898
/.werft/jobs/build/self-hosted-* @gitpod-io/engineering-self-hosted
9999

100+
/preview/infrastructure/harvester @gitpod-io/platform
101+
/workflow @gitpod-io/platform
102+
100103
#
101104
# Automation
102105
# The following files are updated automatically so we don't want to have a specific code-owner

preview/.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# terraform
2+
*.hcl
3+
*.tfstate
4+
*.tfstate.backup
5+
*.plan
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
resource "kubernetes_namespace" "example" {
2+
provider = k8s.harvester
3+
metadata {
4+
name = "preview-${var.preview_name}"
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
terraform {
2+
3+
backend "gcs" {
4+
bucket = "3f4745df-preview-tf-state"
5+
prefix = "preview"
6+
}
7+
8+
required_version = ">= 1.2"
9+
required_providers {
10+
harvester = {
11+
source = "harvester/harvester"
12+
version = ">=0.5.1"
13+
}
14+
k8s = {
15+
source = "hashicorp/kubernetes"
16+
version = ">= 2.0"
17+
}
18+
}
19+
}
20+
21+
# https://registry.terraform.io/providers/harvester/harvester/latest/docs
22+
provider "harvester" {
23+
alias = "harvester"
24+
kubeconfig = file(var.harvester_kube_path)
25+
}
26+
27+
# https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs
28+
provider "k8s" {
29+
alias = "dev"
30+
config_path = var.dev_kube_path
31+
}
32+
33+
# https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs
34+
provider "k8s" {
35+
alias = "harvester"
36+
config_path = var.harvester_kube_path
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
variable "preview_name" {
2+
type = string
3+
description = "The preview environment's name"
4+
}
5+
6+
variable "harvester_kube_path" {
7+
type = string
8+
description = "The path to the Harvester Cluster kubeconfig"
9+
default = "~/.kube/harvester"
10+
}
11+
12+
variable "dev_kube_path" {
13+
type = string
14+
description = "The path to the Dev Cluster kubeconfig"
15+
default = "~/.kube/dev"
16+
}

workflow/lib/common.sh

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/bin/bash
2+
3+
# this script is meant to be sourced
4+
5+
SCRIPT_PATH=$(dirname "${BASH_SOURCE[0]}")
6+
7+
# predefined exit codes for checks
8+
export ERROR_WRONG_WORKSPACE=30
9+
export ERROR_CHANGE_DIR=31
10+
export ERROR_NO_WORKSPACE=32
11+
export ERROR_NO_DIR=33
12+
export ERROR_NO_PLAN=34
13+
14+
function import() {
15+
local file="${SCRIPT_PATH}/${1}"
16+
if [ -f "${file}" ]; then
17+
# shellcheck disable=SC1090
18+
source "${file}"
19+
else
20+
echo "Error: Cannot find library at: ${file}"
21+
exit 1
22+
fi
23+
}
24+
25+
# define some colors for our helper log function
26+
BLUE='\033[0;34m'
27+
RED='\033[0;31m'
28+
GREEN='\033[0;32m'
29+
# NC=no color
30+
NC='\033[0m'
31+
32+
function log_error() {
33+
local text=$1
34+
echo -e "${RED}ERROR: ${NC}${text}" 1>&2
35+
}
36+
37+
function log_success() {
38+
local text=$1
39+
echo -e "${GREEN}SUCCESS: ${NC}${text}"
40+
}
41+
42+
function log_info() {
43+
local text=$1
44+
echo -e "${BLUE}INFO: ${NC}${text}"
45+
}
46+
47+
# Checks if we have the correct context or exits with an error
48+
function check_kubectx() {
49+
local expected=$1
50+
if [[ $(kubectl config current-context) != "${expected}" ]]; then
51+
log_error "Wrong context. Wanted [${expected}], got [$(kubectl config current-context)]"
52+
exit "${ERROR_WRONG_KUBECTX}"
53+
fi
54+
}
55+
56+
function ask() {
57+
while true; do
58+
read -p "$* [y/n]: " yn
59+
case $yn in
60+
[Yy]*) return 0 ;;
61+
[Nn]*) echo "Aborted" ; return 1 ;;
62+
esac
63+
done
64+
}

workflow/lib/terraform.sh

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!/bin/bash
2+
3+
# this script is meant to be sourced
4+
5+
SCRIPT_PATH=$(dirname "${BASH_SOURCE[0]}")
6+
7+
# shellcheck source=./common.sh
8+
source "${SCRIPT_PATH}/common.sh"
9+
10+
export TF_IN_AUTOMATION=true
11+
if [ -n "${DESTROY-}" ]; then
12+
export TF_CLI_ARGS_plan="-destroy"
13+
fi
14+
15+
function check_workspace() {
16+
local workspace=$1
17+
if [[ $(terraform workspace show) != "${workspace}" ]]; then
18+
log_error "Expected to be in [${workspace}]. We are in [$(terraform workspace show)]"
19+
return "${ERROR_WRONG_WORKSPACE}"
20+
fi
21+
}
22+
23+
function set_workspace() {
24+
local workspace=$1
25+
if terraform workspace list | grep -q "${workspace}"; then
26+
terraform workspace select "${workspace}"
27+
else
28+
terraform workspace new "${workspace}"
29+
fi
30+
}
31+
32+
function delete_workspace() {
33+
local workspace=$1
34+
if [[ $(terraform workspace show) == "${workspace}" ]]; then
35+
terraform workspace select default
36+
fi
37+
38+
exists=0
39+
terraform workspace list | grep -q "${workspace}" || exists=$?
40+
if [[ "${exists}" == 0 ]]; then
41+
terraform workspace delete "${workspace}"
42+
fi
43+
}
44+
45+
function terraform_init() {
46+
local target_dir=${1:-$TARGET_DIR}
47+
if [ -z "${target_dir-}" ]; then
48+
log_error "Must provide TARGET_DIR for init"
49+
return "${ERROR_NO_DIR}"
50+
fi
51+
pushd "${target_dir}" || return "${ERROR_CHANGE_DIR}"
52+
53+
terraform init
54+
if [ -n "${WORKSPACE-}" ]; then
55+
set_workspace "${WORKSPACE}"
56+
fi
57+
58+
popd || return "${ERROR_CHANGE_DIR}"
59+
}
60+
61+
function terraform_plan() {
62+
local target_dir=${1:-$TARGET_DIR}
63+
if [ -z "${target_dir-}" ]; then
64+
log_error "Must provide TARGET_DIR for plan"
65+
return "${ERROR_NO_DIR}"
66+
fi
67+
68+
local static_plan
69+
static_plan="$(realpath "${TARGET_DIR}")/$(basename "${TARGET_DIR}").plan"
70+
local plan_location=${PLAN_LOCATION:-$static_plan}
71+
72+
pushd "${target_dir}" || return "${ERROR_CHANGE_DIR}"
73+
74+
# check if we should be in a workspace, and bail otherwise
75+
if [ -n "${WORKSPACE-}" ]; then
76+
check_workspace "${WORKSPACE}"
77+
fi
78+
79+
# -detailed-exitcode will be 0=success no changes/1=failure/2=success changes
80+
# therefore we capture the output so our function doesn't cause a script to terminate if the caller has `set -e`
81+
EXIT_CODE=0
82+
# Also we redirect stdout to /dev/null to hide all the output we don't care about
83+
# and we show only the actual plan
84+
terraform plan -detailed-exitcode -out="${plan_location}" >/dev/null || EXIT_CODE=$?
85+
86+
if [[ ${EXIT_CODE} = 2 ]]; then
87+
terraform show "${plan_location}"
88+
fi
89+
90+
popd || exit "${ERROR_CHANGE_DIR}"
91+
92+
return "${EXIT_CODE}"
93+
}
94+
95+
function terraform_apply() {
96+
local target_dir=${1:-$TARGET_DIR}
97+
if [ -z "${target_dir-}" ]; then
98+
log_error "Must provide TARGET_DIR for apply"
99+
return "${ERROR_NO_DIR}"
100+
fi
101+
102+
local static_plan
103+
static_plan="$(realpath "${TARGET_DIR}")/$(basename "${TARGET_DIR}").plan"
104+
local plan_location=${PLAN_LOCATION:-$static_plan}
105+
106+
pushd "${target_dir}" || return "${ERROR_CHANGE_DIR}"
107+
108+
# check if we should be in a workspace, and bail otherwise
109+
if [ -n "${WORKSPACE-}" ]; then
110+
check_workspace "${WORKSPACE}"
111+
fi
112+
113+
if [ -z "${plan_location-}" ]; then
114+
log_error "Must provide PLAN_LOCATION for apply"
115+
return "${ERROR_NO_PLAN}"
116+
fi
117+
118+
# check if we should be in a workspace, and bail otherwise
119+
if [ -n "${WORKSPACE-}" ]; then
120+
check_workspace "${WORKSPACE}"
121+
fi
122+
123+
terraform apply "${plan_location}"
124+
125+
popd || return "${ERROR_CHANGE_DIR}"
126+
}

workflow/preview/deploy-harvester.sh

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/bin/bash
2+
3+
# shellcheck disable=SC2034
4+
5+
set -euo pipefail
6+
7+
SCRIPT_PATH=$(realpath "$(dirname "$0")")
8+
9+
# shellcheck source=../lib/common.sh
10+
source "$(realpath "${SCRIPT_PATH}/../lib/common.sh")"
11+
12+
# terraform function
13+
import "terraform.sh"
14+
15+
PROJECT_ROOT=$(realpath "${SCRIPT_PATH}/../../")
16+
17+
if [ -z "${WERFT_HOST-}" ]; then
18+
export TF_INPUT=0
19+
fi
20+
21+
WORKSPACE="${TF_VAR_preview_name:-}"
22+
TARGET_DIR="${PROJECT_ROOT}/preview/infrastructure/harvester"
23+
# Setting the TF_DATA_DIR is advisable if we set the PLAN_LOCATION in a different place than the dir with the tf
24+
TF_DATA_DIR="${TARGET_DIR}"
25+
26+
# Illustration purposes, but this will set the plan location to $TARGET_DIR/harvester.plan if PLAN_LOCATION is not set
27+
static_plan="$(realpath "${TARGET_DIR}")/$(basename "${TARGET_DIR}").plan"
28+
PLAN_LOCATION="${PLAN_LOCATION:-$static_plan}"
29+
30+
# export all variables
31+
shopt -os allexport
32+
33+
terraform_init
34+
35+
PLAN_EXIT_CODE=0
36+
terraform_plan || PLAN_EXIT_CODE=$?
37+
38+
# If there are changes
39+
if [[ ${PLAN_EXIT_CODE} == 2 ]]; then
40+
# If we're NOT in werft, ask if we want to apply the plan
41+
if [ -z "${WERFT_HOST-}" ]; then
42+
ask "Do you want to apply the plan?"
43+
fi
44+
terraform_apply
45+
fi
46+
47+
if [ -n "${DESTROY-}" ] && [ -n "${WORKSPACE}" ]; then
48+
pushd "${TARGET_DIR}"
49+
delete_workspace "${WORKSPACE}"
50+
popd
51+
fi

workflow/terraform/terraform-apply.sh

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
SCRIPT_PATH=$(realpath "$(dirname "$0")")
6+
7+
# shellcheck source=../lib/common.sh
8+
source "$(realpath "${SCRIPT_PATH}/../lib/common.sh")"
9+
10+
# terraform function
11+
import "terraform.sh"
12+
13+
terraform_apply

workflow/terraform/terraform-init.sh

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
SCRIPT_PATH=$(realpath "$(dirname "$0")")
6+
7+
# shellcheck source=../lib/common.sh
8+
source "$(realpath "${SCRIPT_PATH}/../lib/common.sh")"
9+
10+
# terraform function
11+
import "terraform.sh"
12+
13+
terraform_init

workflow/terraform/terraform-plan.sh

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
SCRIPT_PATH=$(realpath "$(dirname "$0")")
6+
7+
# shellcheck source=../lib/common.sh
8+
source "$(realpath "${SCRIPT_PATH}/../lib/common.sh")"
9+
10+
# terraform function
11+
import "terraform.sh"
12+
13+
terraform_plan
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
SCRIPT_PATH=$(realpath "$(dirname "$0")")
6+
7+
# shellcheck source=../lib/common.sh
8+
source "$(realpath "${SCRIPT_PATH}/../lib/common.sh")"
9+
10+
import "terraform.sh"
11+
12+
if [ -z "${WORKSPACE-}" ]; then
13+
log_error "Must provide WORKSPACE"
14+
exit "${ERROR_NO_WORKSPACE}"
15+
fi
16+
17+
set_workspace "${WORKSPACE}"

0 commit comments

Comments
 (0)