Skip to content

Commit 713de09

Browse files
committed
adding Github (actions) support
1 parent 4f5acaf commit 713de09

File tree

14 files changed

+564
-1
lines changed

14 files changed

+564
-1
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export GITHUB_TOKEN="..."
2+
export TF_VAR_location="..."
3+
export TF_VAR_prefix="..."
4+
5+
# Backend state configuration. Uncomment after configuring backend state.
6+
# export ARM_ACCESS_KEY="..."
7+
# export ARM_ACCOUNT_NAME="..."
8+
# export ARM_CONTAINER_NAME="..."
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Created by https://www.toptal.com/developers/gitignore/api/terraform
2+
# Edit at https://www.toptal.com/developers/gitignore?templates=terraform
3+
4+
### Terraform ###
5+
# Local .terraform directories
6+
**/.terraform/*
7+
8+
# .tfstate files
9+
*.tfstate
10+
*.tfstate.*
11+
12+
# Crash log files
13+
crash.log
14+
15+
# Ignore any .tfvars files that are generated automatically for each Terraform run. Most
16+
# .tfvars files are managed as part of configuration and so should be included in
17+
# version control.
18+
#
19+
# example.tfvars
20+
21+
# Ignore override files as they are usually used to override resources locally and so
22+
# are not checked in
23+
override.tf
24+
override.tf.json
25+
*_override.tf
26+
*_override.tf.json
27+
28+
# Include override files you do wish to add to version control using negated pattern
29+
# !example_override.tf
30+
31+
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
32+
# example: *tfplan*
33+
34+
# End of https://www.toptal.com/developers/gitignore/api/terraform
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# Bootstrap for Terraform deployments through Gitlab into Azure
2+
3+
This directory contains [Terraform](https://www.terraform.io/) templates that can bootstrap Azure and Github resources in a way that enables running robust CICD of [Terraform](https://www.terraform.io/) templates using [Github CICD](https://github.com/features/actions). After applying this template, automated CI of terraform deployments should **just work**.
4+
5+
> **Note**: This template is intended to be used alongside the CICD pipeline for infrastructure using Github.
6+
7+
At a high level, this template aims to:
8+
9+
* Deploy Azure Dependencies required for automated CICD of Terraform deployments
10+
* Configure variables in Github required for automated CICD of Terraform deployments
11+
* Configure dependencies for each a multistage (`dev`, `integration`, `prod`, etc...) Terraform deployment
12+
13+
> **Note**: This template only sets up the **dependencies** needed to do a production ready infrastructure deployment, such as backend state, deployment credentials, Azure Contianer Reigstry and Github variables.
14+
15+
There are many things deployed by this template, including:
16+
17+
* Backend state storage account
18+
* Backend state containers for this deployment
19+
* ACR for storing docker images
20+
* Github variables needed for all deployments
21+
* For each deployment environment
22+
* Backend state container
23+
* Service principal used for deployments to that environment
24+
* Resource group
25+
* Role based security
26+
27+
## Identities/Credentials Configured
28+
29+
This template will generate some credentials, which are enumarated blow:
30+
31+
| Description | Reason | Notes |
32+
| --- | --- | --- |
33+
| ACR Push/Pull | Needed by the pipeline that builds the base image used by all of the infrastructure CICD in Github | N/A |
34+
| Environment Deploy | Needed by each environment to execute a deployment of resources into Azure | One generated for each environment |
35+
36+
## Usage
37+
38+
There are a few use cases for the code in this repository. The sections below outline the usage for each of those cases
39+
40+
### First Time Setup
41+
42+
Among the many resources provisioned by this template is the [Backend Configuration](https://www.terraform.io/docs/backends/index.html) that hosts the [Terraform State](https://www.terraform.io/docs/state/index.html) for this template, as well as the state for each deployment.
43+
44+
Because of this, we cannot have the backend state configured for the initial deployment of this template. These steps will take you through the following:
45+
46+
* Initial deployment of this template
47+
* Enable the backend state for this deployment
48+
49+
#### Requirements
50+
51+
* `terraform` will need to be installed. Version `v0.12.28` or newer is recommended
52+
* A shell environment, preferrably bash
53+
* A Github personal access token. Instructions for generating one can be found [here](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token). The token will need the `workflow` permission.
54+
* An Azure subscription
55+
56+
#### Deployment Steps
57+
58+
**Disable backend state**
59+
60+
For the first deployment, the contents of `backend.tf` will need to be commented out. Don't worry -- we'll uncomment this later.
61+
62+
**Configure your environment**
63+
```bash
64+
# Required to configure variables in Github
65+
export GITHUB_TOKEN="..."
66+
67+
# The location in which to provision Azure resources
68+
export TF_VAR_location="..."
69+
70+
# The prefix used for naming resources in Azure
71+
export TF_VAR_prefix="..."
72+
73+
# Log into the Azure CLI
74+
az login
75+
76+
# Set your default subscription - this will dictate where resources will be provisioned
77+
az account set --subscription "<your subscription ID>"
78+
```
79+
80+
**Run the deployment**
81+
82+
> **Note**: If you see a log about `Initializing the backend...`, make sure that you followed the steps to disable the backend state.
83+
84+
```bash
85+
# Initialize the Terraform environment
86+
terraform init
87+
88+
# See what the deployment will do. No changes will be applied, but you can review the changes that will be applied in the next step
89+
terraform plan
90+
91+
# Deploy the changes
92+
terraform apply
93+
```
94+
95+
**Enable backend state**
96+
97+
Enabling backend state will store the deployment state in Azure. This will allow others to run the deployment without you needing to worry about the state configuration.
98+
99+
Start by uncommenting the contents of `backend.tf`, then run the following:
100+
101+
```bash
102+
export ARM_ACCESS_KEY=$(terraform output backend-state-account-key)
103+
export ARM_ACCOUNT_NAME=$(terraform output backend-state-account-name)
104+
export ARM_CONTAINER_NAME=$(terraform output backend-state-bootstrap-container-name)
105+
106+
# Initialize the deployment with the backend
107+
terraform init -backend-config "storage_account_name=${ARM_ACCOUNT_NAME}" -backend-config "container_name=${ARM_CONTAINER_NAME}"
108+
```
109+
110+
You should see something along the lines of the following, to which you want to answer `yes`:
111+
112+
```bash
113+
Do you want to copy existing state to the new backend?
114+
```
115+
116+
If things work, you will see the following message and the state file should end up in Azure:
117+
118+
```bash
119+
Successfully configured the backend "azurerm"! Terraform will automatically
120+
use this backend unless the backend configuration changes.
121+
```
122+
123+
### Deploying the Infrastructure
124+
125+
Now that Azure and Github have been configured to support the Terraform deployment, you will need to do the following to actually deploy the environment.
126+
127+
**Trigger IAC Pipeline**
128+
129+
You are now ready to kick off a deployment of the IAC pipeline! You can do this through the Github actions UI.
130+
131+
### Rotate Service Principal Passwords
132+
133+
If the need arises to rotate the credentials for any of the generated service principals, the following command can be used to quickly rotate the credentials and also update all configuration in Github:
134+
135+
```bash
136+
# configure environment (.envrc.template)
137+
138+
az login
139+
az account set --subscription "<your subscription ID>"
140+
141+
terraform init -backend-config "storage_account_name=${ARM_ACCOUNT_NAME}" -backend-config "container_name=${ARM_CONTAINER_NAME}"
142+
143+
# `taint` all passwords - this triggers Terraform to regenerate these and update all dependent configuration
144+
terraform state list | grep random_password | xargs -L 1 terraform taint
145+
terraform plan
146+
147+
# Note: this command might fail due to the rapid create/delete on Azure resources. If it fails, re-running it
148+
# should solve the issue
149+
terraform apply
150+
```
151+
152+
Done!
153+
154+
155+
### Adding a new environment
156+
157+
Now that Azure and Github have been configured to deploy resources through Terraform, you can easily configure Azure and Github to support new application stages (environments) by using the `environment` module.
158+
159+
> **Note**: This will only set up Azure and Github to support a new environment. The environment will need to be deployed using the infrastructure deployments project (not covered here).
160+
161+
This guide will take you through configuring Azure and Github to support a new `pre-prod` environment.
162+
163+
**Add a new environment**
164+
165+
You will need to open `azure.tf` to configure a new environment. A new environment can be configured by adding the following to the bottom of the file:
166+
167+
```hcl
168+
module "preprod" {
169+
source = "./environment"
170+
acr_id = azurerm_container_registry.acr.id
171+
environment_name = "preprod"
172+
location = var.location
173+
subscription_id = data.azurerm_client_config.current.subscription_id
174+
backend_storage_account_name = azurerm_storage_account.ci.name
175+
prefix = var.prefix
176+
}
177+
```
178+
179+
You will then need to execute the following:
180+
181+
```bash
182+
# configure environment (.envrc.template)
183+
184+
az login
185+
az account set --subscription "<your subscription ID>"
186+
187+
terraform init -backend-config "storage_account_name=${ARM_ACCOUNT_NAME}" -backend-config "container_name=${ARM_CONTAINER_NAME}"
188+
terraform plan
189+
terraform apply
190+
```
191+
192+
Done!
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
resource "azurerm_container_registry" "acr" {
2+
name = format("acr%s", random_string.rand.result)
3+
resource_group_name = azurerm_resource_group.ci.name
4+
location = azurerm_resource_group.ci.location
5+
sku = "Basic"
6+
}
7+
8+
resource "azuread_application" "acr" {
9+
name = format("acr-push-%s", random_string.rand.result)
10+
}
11+
12+
resource "azuread_service_principal" "acr" {
13+
application_id = azuread_application.acr.application_id
14+
}
15+
16+
resource "random_password" "acr" {
17+
length = 35
18+
upper = true
19+
lower = true
20+
special = false
21+
}
22+
23+
resource "azuread_service_principal_password" "acr" {
24+
service_principal_id = azuread_service_principal.acr.id
25+
value = random_password.acr.result
26+
end_date_relative = "2400h"
27+
}
28+
29+
resource "azurerm_role_assignment" "acr_push" {
30+
scope = azurerm_container_registry.acr.id
31+
role_definition_name = "AcrPush"
32+
principal_id = azuread_service_principal.acr.id
33+
}
34+
35+
resource "azurerm_role_assignment" "acr_pull" {
36+
scope = azurerm_container_registry.acr.id
37+
role_definition_name = "AcrPull"
38+
principal_id = azuread_service_principal.acr.id
39+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
resource "random_string" "rand" {
2+
length = 4
3+
special = false
4+
number = false
5+
upper = false
6+
}
7+
8+
resource "azurerm_resource_group" "ci" {
9+
name = format("rg-%s-ci", var.prefix)
10+
location = var.location
11+
}
12+
13+
resource "azurerm_storage_account" "ci" {
14+
name = format("backendstate%s", random_string.rand.result)
15+
resource_group_name = azurerm_resource_group.ci.name
16+
location = azurerm_resource_group.ci.location
17+
18+
min_tls_version = "TLS1_2"
19+
account_tier = "Standard"
20+
account_replication_type = "LRS"
21+
}
22+
23+
resource "azurerm_storage_container" "tfstate" {
24+
name = "tfstate-terraform-bootstrap"
25+
storage_account_name = azurerm_storage_account.ci.name
26+
container_access_type = "private"
27+
}
28+
29+
data "azurerm_client_config" "current" {}
30+
31+
module "dev" {
32+
source = "./environment"
33+
acr_id = azurerm_container_registry.acr.id
34+
environment_name = "dev"
35+
location = var.location
36+
subscription_id = data.azurerm_client_config.current.subscription_id
37+
backend_storage_account_name = azurerm_storage_account.ci.name
38+
prefix = var.prefix
39+
}
40+
41+
module "integration" {
42+
source = "./environment"
43+
acr_id = azurerm_container_registry.acr.id
44+
environment_name = "integration"
45+
location = var.location
46+
subscription_id = data.azurerm_client_config.current.subscription_id
47+
backend_storage_account_name = azurerm_storage_account.ci.name
48+
prefix = var.prefix
49+
}
50+
51+
module "prod" {
52+
source = "./environment"
53+
acr_id = azurerm_container_registry.acr.id
54+
environment_name = "prod"
55+
location = var.location
56+
subscription_id = data.azurerm_client_config.current.subscription_id
57+
backend_storage_account_name = azurerm_storage_account.ci.name
58+
prefix = var.prefix
59+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
terraform {
2+
backend "azurerm" {
3+
key = "tf-bootstrap.tfstate"
4+
}
5+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
locals {
2+
full_name = format("%s-%s", var.prefix, var.environment_name)
3+
}
4+
5+
resource "azurerm_resource_group" "rg" {
6+
location = var.location
7+
name = "rg-${local.full_name}"
8+
tags = {
9+
environment = var.environment_name
10+
}
11+
}
12+
13+
resource "azuread_application" "app" {
14+
name = "sp-${local.full_name}"
15+
}
16+
17+
resource "azuread_service_principal" "sp" {
18+
application_id = azuread_application.app.application_id
19+
}
20+
21+
resource "random_password" "sp" {
22+
length = 35
23+
upper = true
24+
lower = true
25+
special = false
26+
}
27+
28+
resource "azuread_service_principal_password" "sp" {
29+
service_principal_id = azuread_service_principal.sp.id
30+
value = random_password.sp.result
31+
end_date_relative = "2400h"
32+
}
33+
34+
resource "azurerm_role_assignment" "rg-owner" {
35+
scope = azurerm_resource_group.rg.id
36+
role_definition_name = "Owner"
37+
principal_id = azuread_service_principal.sp.id
38+
}
39+
40+
resource "azurerm_storage_container" "tfstate" {
41+
name = format("tfstate-%s", var.environment_name)
42+
storage_account_name = var.backend_storage_account_name
43+
container_access_type = "private"
44+
}

0 commit comments

Comments
 (0)