Skip to content

Setting up a Creator resource within the Cloud

Setting Up a Terraform Creator

Required

Precheck

export ORG_ID=""
export BILLING_ACCOUNT_ID=""
export _ADMIN=${USER}-terraform-ops-admin
export _CREDS=~/.config/gcloud/${USER}=terraform-ops-admin.json

# pre-installation required for billing components
gcloud components update
gcloud components install beta
....
gcloud beta billing accounts list
### ------------------ OUTPUT --------------------------
# ACCOUNT_ID            NAME                OPEN  MASTER_ACCOUNT_ID
# 0153E3-9593A6-BE6675  My Billing Account  True

Find Organizations

$ gcloud organizations list
....
### ------------------ OUTPUT --------------------------
# DISPLAY_NAME                 ID  DIRECTORY_CUSTOMER_ID
# eblusolutions.com  109748593075              C02c779sc

Create Admin Project

# if admin project is NOT already created...
$ gcloud projects create ${_ADMIN} \
    --organization ${ORG_ID} \
    --set-as-default
....
$ gcloud beta billing projects link ${_ADMIN} \
    --billing-account ${BILLING_ACCOUNT_ID}

Create the service account for admin project

$ gcloud iam service-accounts create ${TF_ADMIN_SA_NAME} \ 
    --display-name "Terraform creator account"
$ gcloud iam service-accounts keys create ${_CREDS} \
    --iam-account terraform@${_ADMIN}.iam.gserviceaccount.com

Grant the SA permissions to VIEW Admin Project and manage Cloud Storage

$ gcloud projects add-iam-policy-binding ${_ADMIN} \
    --member serviceAccount:${TF_ADMIN_SA_NAME}@${_ADMIN}.iam.gserviceaccount.com \
    --role roles/viewer

Enable APIs for Terraform to function

gcloud services enable cloudresourcemanager.googleapis.com,
gcloud services enable cloudbilling.googleapis.com,
gcloud services enable iam.googleapis.com,
gcloud services enable compute.googleapis.com,
gcloud services enable serviceusage.googleapis.com

Add Organization/Folder Permissions

$ gcloud organizations add-iam-policy-binding ${ORG_ID} \
    --member serviceAccount:${TF_ADMIN_SA_NAME}@${_ADMIN}.iam.gserviceaccount.com \
    --role roles/billing.user

Remote State

Create a remote backend bucket in Google Cloud Storage for a backend.tf to manage terraform.tfstate

$ gsutil mb -p ${_ADMIN} gs://${_ADMIN}

  cat > backend.tf << EOF
  terraform {
      backend "gcs" {
          bucket    = "${_ADMIN}"
          prefix    = "creator/state"
      }
  }
  EOF

Enable versioning of the remote bucket:

$ gsutil versioning set on gs://${_ADMIN}



Azure

BUILDER

Steps to build the necessary resource to invoke a terraform creator

# Requirements
#   - Terraform installed on the builder machine or container env
#   - Azure CLI installed on the builder machine or container env
#
# Variables
#   - ARM_SUBSCRIPTION_ID
#   - ARM_TENANT_ID
#   - STORAGE_ACCOUNT_NAME
#   - RESOURCE_GROUP
#   - STORAGE_ACCOUNT_CONTAINER_NAME
set -o

##########################################
# AUTH
##########################################
# Uncomment if tenant is NOT known or utilizing <self> credentials
# az login -t $ARM_TENANT_ID
# az account set --subscription ee91ab82-b559-49b4-bfb4-3780cdf9a068

##########################################
# !! WARNING !!
##########################################
# Note: some data cannot be deleted by law, e.g. hippa, phi, financial data needed for tax audits etc etc.
# Because of the severity and legal consequences of losing such data, 
# it is a common cloud practice to apply management locks on a resource to prevent it from being deleted.
# --
# We will NOT grant the DELETE permissions because of the following:
# Automation is powerful!!
# With great power comes great responsibility, 
# which we don’t want to grant a headless (and therefore brainless) build agent.
# It’s important to understand that git (even with signed commits) gives technical traceability, 
# but at eBlu that might not satisfy requirements for legal audit-ability.

##########################################
# RESOURCE CREATION
##########################################
# If new resource is necessary
# creating the resource Group
# az group create -n $RESOURE_GROUP_NAME -l eastus2

##########################################
# STORAGE:
# FROM HERE THIS CAN BE EXECUTED DIRECTLY IF THE RESOURCE IS CONSTRUCTED AND LINKED TO BILLING
##########################################
#creating the storage account
az storage account create -n analyticsopsstate0921 -g eblusolutions-analytics-ops -l eastus2 --kind StorageV2 --sku Standard_LRS --https-only true --allow-blob-public-access false


#creating a tfstate container
az storage container create -n tfstate --account-name analyticsopsstate0921

##########################################
# KEYVAULT CREATION & CONFIG
##########################################
#creating the KeyVault
az keyvault create -n analytics-ops-kv -g eblusolutions-analytics-ops -l eastus2

#Creating a SAS Token for the storage account, storing in KeyVault
az storage container generate-sas --account-name analyticsopsstate0921 --expiry 2022-01-01 --name tfstate --permissions dlrw -o json | xargs az keyvault secret set --vault-name analytics-ops-kv --name tfSASToken --value

##########################################
# SPN CREATION & CONFIG
##########################################
#creating a Service Principal for AKS and Azure DevOps
#requires elevation
az ad sp create-for-rbac -n eblusolutions-analytics-ops --role "Contributor"

#creating an ssh key if you don't already have one
ssh-keygen  -f ~/.ssh/id_rsa_terraform

#store the public key in Azure KeyVault
az keyvault secret set --vault-name analytics-ops-kv --name AnalyticsLinuxSSHPubGen -f ~/.ssh/id_rsa_terraform.pub > /dev/null

#store the service principal id in Azure KeyVault
az keyvault secret set --vault-name analytics-ops-kv --name spn-id --value <SPD-ID> >/dev/null

#store the service principal secret in Azure KeyVault
az keyvault secret set --vault-name analytics-ops-kv --name spn-secret --value <SPD-SECRET> > /dev/null

##########################################
# BACKEND INIT
##########################################
# init tf backend
terraform init -backend-config="resource_group_name=eblusolutions-analytics-ops" -backend-config="storage_account_name=analyticsopsstate0921" -backend-config="container_name=tfstate"

# known
export BASE_RESOURCE_PREFIX=eblusolutions_analytics_ops
export ARM_TENANT_ID=3bf257a8-abbc-4bc6-915e-4c612569749c
export ARM_SUBSCRIPTION_ID=ee91ab82-b559-49b4-bfb4-3780cdf9a068
export STORAGE_ACCOUNT_NAME=analytics-ops-state
export RESOURCE_GROUP_NAME="eblusolutions-analytics-ops"
export STORAGE_ACCOUNT_CONTAINER_NAME=$BASE_RESOURCE_PREFIX

# 'name' property in the output is deprecated and will be removed in the future. Use 'appId' instead.
# {​​​​​
#   "appId": "xxxxxxx-xxxx-xxx-xxxx-xxxxxxxxxxxxx",
#   "displayName": "eblusolutions-analytics-ops",
#   "name": "xxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxx",
#   "password": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
#   "tenant": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx"
# }​​​​​