Skip to content

Commit f364419

Browse files
committed
Terraform workflow for preview envs
1 parent 44b1dca commit f364419

File tree

12 files changed

+371
-0
lines changed

12 files changed

+371
-0
lines changed

.github/CODEOWNERS

+3
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@
100100
/.werft/*installer-tests* @gitpod-io/engineering-self-hosted
101101
/.werft/jobs/build/self-hosted-* @gitpod-io/engineering-self-hosted
102102

103+
/dev/preview/infrastructure/harvester @gitpod-io/platform
104+
/dev/preview/workflow @gitpod-io/platform
105+
103106
#
104107
# Automation
105108
# The following files are updated automatically so we don't want to have a specific code-owner

dev/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,34 @@
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+
provider "harvester" {
22+
alias = "harvester"
23+
kubeconfig = file(var.harvester_kube_path)
24+
}
25+
26+
provider "k8s" {
27+
alias = "dev"
28+
config_path = var.dev_kube_path
29+
}
30+
31+
provider "k8s" {
32+
alias = "harvester"
33+
config_path = var.harvester_kube_path
34+
}
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+
}

dev/preview/workflow/lib/common.sh

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
# shellcheck disable=SC2162
59+
read -p "$* [y/n]: " yn
60+
case $yn in
61+
[Yy]*) return 0 ;;
62+
[Nn]*) echo "Aborted" ; return 1 ;;
63+
esac
64+
done
65+
}

dev/preview/workflow/lib/terraform.sh

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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+
if [ -n "${DESTROY-}" ]; then
11+
export TF_CLI_ARGS_plan="-destroy"
12+
fi
13+
14+
function check_workspace() {
15+
local workspace=$1
16+
if [[ $(terraform workspace show) != "${workspace}" ]]; then
17+
log_error "Expected to be in [${workspace}]. We are in [$(terraform workspace show)]"
18+
return "${ERROR_WRONG_WORKSPACE}"
19+
fi
20+
}
21+
22+
function set_workspace() {
23+
local workspace=$1
24+
if terraform workspace list | grep -q "${workspace}"; then
25+
terraform workspace select "${workspace}"
26+
else
27+
terraform workspace new "${workspace}"
28+
fi
29+
}
30+
31+
function delete_workspace() {
32+
local workspace=$1
33+
if [[ $(terraform workspace show) == "${workspace}" ]]; then
34+
terraform workspace select default
35+
fi
36+
37+
exists=0
38+
terraform workspace list | grep -q "${workspace}" || exists=$?
39+
if [[ "${exists}" == 0 ]]; then
40+
terraform workspace delete "${workspace}"
41+
fi
42+
}
43+
44+
function terraform_init() {
45+
local target_dir=${1:-$TARGET_DIR}
46+
if [ -z "${target_dir-}" ]; then
47+
log_error "Must provide TARGET_DIR for init"
48+
return "${ERROR_NO_DIR}"
49+
fi
50+
pushd "${target_dir}" || return "${ERROR_CHANGE_DIR}"
51+
52+
terraform init
53+
if [ -n "${WORKSPACE-}" ]; then
54+
set_workspace "${WORKSPACE}"
55+
fi
56+
57+
popd || return "${ERROR_CHANGE_DIR}"
58+
}
59+
60+
function terraform_plan() {
61+
local target_dir=${1:-$TARGET_DIR}
62+
if [ -z "${target_dir-}" ]; then
63+
log_error "Must provide TARGET_DIR for plan"
64+
return "${ERROR_NO_DIR}"
65+
fi
66+
67+
local static_plan
68+
static_plan="$(realpath "${TARGET_DIR}")/$(basename "${TARGET_DIR}").plan"
69+
local plan_location=${PLAN_LOCATION:-$static_plan}
70+
71+
pushd "${target_dir}" || return "${ERROR_CHANGE_DIR}"
72+
73+
# check if we should be in a workspace, and bail otherwise
74+
if [ -n "${WORKSPACE-}" ]; then
75+
check_workspace "${WORKSPACE}"
76+
fi
77+
78+
# -detailed-exitcode will be 0=success no changes/1=failure/2=success changes
79+
# therefore we capture the output so our function doesn't cause a script to terminate if the caller has `set -e`
80+
EXIT_CODE=0
81+
terraform plan -detailed-exitcode -out="${plan_location}" || EXIT_CODE=$?
82+
83+
if [[ ${EXIT_CODE} = 2 ]]; then
84+
terraform show "${plan_location}"
85+
fi
86+
87+
popd || exit "${ERROR_CHANGE_DIR}"
88+
89+
return "${EXIT_CODE}"
90+
}
91+
92+
function terraform_apply() {
93+
local target_dir=${1:-$TARGET_DIR}
94+
if [ -z "${target_dir-}" ]; then
95+
log_error "Must provide TARGET_DIR for apply"
96+
return "${ERROR_NO_DIR}"
97+
fi
98+
99+
local static_plan
100+
static_plan="$(realpath "${TARGET_DIR}")/$(basename "${TARGET_DIR}").plan"
101+
local plan_location=${PLAN_LOCATION:-$static_plan}
102+
103+
pushd "${target_dir}" || return "${ERROR_CHANGE_DIR}"
104+
105+
# check if we should be in a workspace, and bail otherwise
106+
if [ -n "${WORKSPACE-}" ]; then
107+
check_workspace "${WORKSPACE}"
108+
fi
109+
110+
if [ -z "${plan_location-}" ]; then
111+
log_error "Must provide PLAN_LOCATION for apply"
112+
return "${ERROR_NO_PLAN}"
113+
fi
114+
115+
# check if we should be in a workspace, and bail otherwise
116+
if [ -n "${WORKSPACE-}" ]; then
117+
check_workspace "${WORKSPACE}"
118+
fi
119+
120+
terraform apply "${plan_location}"
121+
122+
popd || return "${ERROR_CHANGE_DIR}"
123+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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 [[ -n ${WERFT_HOST+x} ]]; then
18+
TF_CLI_ARGS="-input=false"
19+
TF_IN_AUTOMATION=true
20+
fi
21+
22+
WORKSPACE="${TF_VAR_preview_name:-}"
23+
TARGET_DIR="${PROJECT_ROOT}/dev/preview/infrastructure/harvester"
24+
# Setting the TF_DATA_DIR is advisable if we set the PLAN_LOCATION in a different place than the dir with the tf
25+
TF_DATA_DIR="${TARGET_DIR}"
26+
27+
# Illustration purposes, but this will set the plan location to $TARGET_DIR/harvester.plan if PLAN_LOCATION is not set
28+
static_plan="$(realpath "${TARGET_DIR}")/$(basename "${TARGET_DIR}").plan"
29+
PLAN_LOCATION="${PLAN_LOCATION:-$static_plan}"
30+
31+
# export all variables
32+
shopt -os allexport
33+
34+
terraform_init
35+
36+
PLAN_EXIT_CODE=0
37+
terraform_plan || PLAN_EXIT_CODE=$?
38+
39+
# If there are changes
40+
if [[ ${PLAN_EXIT_CODE} == 2 ]]; then
41+
# If we're NOT in werft, ask if we want to apply the plan
42+
if [ -z ${WERFT_HOST+x} ]; then
43+
ask "Do you want to apply the plan?"
44+
fi
45+
terraform_apply
46+
fi
47+
48+
if [ -n "${DESTROY-}" ] && [ -n "${WORKSPACE}" ]; then
49+
pushd "${TARGET_DIR}"
50+
delete_workspace "${WORKSPACE}"
51+
popd
52+
fi
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
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
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
if [ -z "${TARGET_DIR-}" ]; then
18+
log_error "Must provide TARGET_DIR"
19+
exit "${ERR_NO_DIR}"
20+
fi
21+
22+
if [ -z "${DESTROY-}" ]; then
23+
set_workspace "${WORKSPACE}"
24+
else
25+
pushd "${TARGET_DIR}"
26+
delete_workspace "${WORKSPACE}"
27+
popd
28+
fi

0 commit comments

Comments
 (0)