Skip to content

Commit e6162db

Browse files
Stephen HendersonNicholas M. Iodice
andauthored
Provide and initial ADO bootstrap template to help users get stared with IaC pipelines (#386)
* init - details follow: - pulled in @nmiodice example - began docs / README.md - refactored example to follow other temp's more closely (break out ins & outs), and began massaging terminology to be consistent with other temps, AZ, AzDO, and the README.md * further alignment with existing doc & use of .env * ins, outs, and in-betweens defined * more polish and clean-up * more in inputs and resources * Attention to creating the AzDO project (and parameterizing it). * ignore .env * align var groups with the latest requirements from Cobalt * document outputs * document inputs * finish detailing in- & out-puts * Initial commit. * migrate git ignores into root-most level * remove 'fixme' and add optional bits to create AzDO projects, as desired * remove 'fixme' and add optional bits to create AzDO projects, as desired * merge to upstream repo * point to 'new' repo and add secret VGs * lint all the things! * stub new instructions to get the pipeline running the first time Co-authored-by: Nicholas M. Iodice <[email protected]>
1 parent c6b2ea1 commit e6162db

File tree

7 files changed

+449
-8
lines changed

7 files changed

+449
-8
lines changed

.gitignore

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,20 @@ _vendor-*/
1313
# Terraform
1414
.terraform/
1515
**/.terraform/
16-
**/terraform.tfstate.*
17-
**/terraform.tfstate
1816
**/.terraform.tfstate.lock.info
1917
**/*.plan
18+
*.tfstate
19+
*.tfstate.*
20+
21+
# Local .terraform directories
22+
**/.terraform/*
23+
24+
# Ignore override files as they are usually used to override resources locally and so
25+
# are not checked in
26+
override.tf
27+
override.tf.json
28+
*_override.tf
29+
*_override.tf.json
2030

2131
# User-specific files
2232
*.suo
@@ -349,15 +359,12 @@ ASALocalRun/
349359

350360
# ENV files
351361
**/.env
352-
.envrc
362+
**/.envrc
363+
353364

354365
# Exclude local build directory
355366
build/
356367
# For Mac OS user files
357368
**/.DS_Store
358-
# For Mac OS user files
359-
**/.DS_Store
360369

361-
# Dep files
362-
*.toml
363-
*.lock
370+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
AZDO_PERSONAL_ACCESS_TOKEN=
2+
AZDO_ORG_SERVICE_URL=
3+
ARM_CLIENT_ID=
4+
ARM_CLIENT_SECRET=
5+
ARM_SUBSCRIPTION_ID=
6+
ARM_TENANT_ID=
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
# Bootstrap for Cobalt IaC Pipelines on Azure DevOps
2+
3+
## Overview
4+
This provides a starting point for anyone interested in having an Azure DevOps
5+
Project that will 'run' a target Cobalt template whenever that template changes.
6+
7+
## Intended audience
8+
9+
This article is for an infrastructure worker that is brand new to Cobalt templating, and it's *Cobalt Infrastructure
10+
Template* (CIT) developer workflow, who wants to stand-up Azure DevOps and Azure resources to enact a CI/CD process
11+
over their infrastructure as code work.
12+
13+
### An example
14+
Imagine that you are building a new set of web applications which are destined
15+
to run on Azure. Those applications are going to use a few runtime resources, and you've
16+
wisely decided to use a Cobalt template to satisfy and centralize your Infrastructure as Code ("IaC")
17+
needs across all of those applications.
18+
19+
To get started, you want a central Git repository to store your IaC in,
20+
and following [solid engineering fundamentals](https://github.com/microsoft/code-with-engineering-playbook/tree/master/continuous-integration#continuous-integration),
21+
you've decided to wrap CI/CD, and staged deployments around your IaC to help
22+
protect the quality of your infrastructure changes.
23+
24+
This bootstrap (itself a Cobalt template) can be used to quickly, safely, and easily create
25+
the ADO resources (a Project, Git Repository, Build Definitions, Variable Groups,
26+
Service Connections, etc) that you'll need in that example.
27+
28+
### Terminology
29+
Given that we're using a Cobalt template to create resources that will then operate other
30+
Cobalt templates, discussing matters can become a bit confusing. To help with that, and
31+
to get specific about a few other terms we'll use, let's define a few terms and then use
32+
them consistently, within this document and this bootstrap template itself.
33+
34+
|Term|Meaning|
35+
|----|----|
36+
|ADO|Azure DevOps, AKA "AzDO," elsewhere|
37+
|Application(s)|The application that will rely on the resources managed by the Infrastructure Template|
38+
|Bootstrap Template|This Cobalt template, as described in the example, above|
39+
|IaC|Infrastructure as Code|
40+
|Infrastructure Template|Whichever Cobalt template that will manage the Application(s) runtime resources. _By default, the Bootstrap Template points to the 'Hello World' example template in the official Cobalt GitHub repository_|
41+
|Project|The ADO Project that the Bootstrap Template will add pipeline resources to|
42+
|IaC Repository|(empty) Git Repository in the Project where the Infrastructure Template is expected to be kept|
43+
44+
## What the Bootstrap Template needs, provisions, and outputs
45+
46+
To work properly, the Bootstrap Template needs some information about how you want things named, how many environments
47+
are desired (and their details), and little else. The template also creates many kinds of resources
48+
49+
### Decide with Project to use
50+
51+
Visit your ADO Organization site and select or create a project. This project will house the pipelines that
52+
will operate your IaC CI/CD pipelines. Note that project's name. We'll need to use it as an input, in the next section.
53+
54+
### Inputs
55+
56+
There are two places from where the template consumes input information.
57+
1. The environment variables that are set before the template is used-- usually these are `export` shell environment variables.
58+
2. The other is a `terraform.tfvars` file.
59+
60+
Let's quickly explore each of these.
61+
62+
#### Environment Variable
63+
64+
You'll need to define a `.env` file in the root of the project.
65+
66+
You can use our environment template file to start with: `cp .env.template .env`
67+
68+
Provide values for the environment values in `.env` which are required to allow
69+
Terraform to provision resources within your subscription, and your Azure DevOps Organization.
70+
71+
```bash
72+
ARM_SUBSCRIPTION_ID="<az-service-principal-subscription-id>"
73+
ARM_CLIENT_ID="<az-service-principal-client-id>"
74+
ARM_CLIENT_SECRET="<az-service-principal-auth-secret>"
75+
ARM_TENANT_ID="<az-service-principal-tenant>"
76+
ARM_ACCESS_KEY="<remote-state-storage-account-primary-key>"
77+
AZDO_PERSONAL_ACCESS_TOKEN="<see, below>"
78+
AZDO_ORG_SERVICE_URL="<typically: https://dev.azure.com/__YOUR_ORG__/"
79+
```
80+
81+
_Note: you can find out more information about the Azure DevOps personal access token [here](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page)._
82+
83+
#### Terraform Variables
84+
85+
The Environments and Azure Subscription details, for each, need to be provided in the `terraform.tfvars` file.
86+
It should take the form of:
87+
88+
```hcl-terraform
89+
environments = [{
90+
environment: "dev1",
91+
az_sub_id: "1aaaaaa1-bbbb-cccc-dddd-eeeeee"
92+
},{
93+
environment: "dev2",
94+
az_sub_id: "4aaaaaa4-bbbb-cccc-dddd-eeeeee"
95+
},{
96+
environment: "prod",
97+
az_sub_id: "1aaaaaa1-bbbb-cccc-dddd-eeeeee"
98+
}]
99+
```
100+
101+
In the structure above, you can indicate as many environments as needed. Each should have a unique `environment` value,
102+
and each will need an accurate `az_sub_id`. For each environment, `az_sub_id` is the Azure Subscription ID that the
103+
Infrastructure Template will eventually provision resources into.
104+
105+
You'll also need to specify the name of the Project (_The ADO Project that the Bootstrap Template will add pipeline resources to_).
106+
The Bootstrap Template can be used with an existing Azure DevOps Project ("the Project"), or it can create
107+
a new project. Take a look at the `azdo.tf` file, around lines 12-34 to see examples of how to use the template
108+
for either choice.
109+
110+
In either case, you'll need to provide the name of the Project to use with a line like this:
111+
```hcl-terraform
112+
project_name = "My CICD Project Name"
113+
```
114+
115+
### Provisioned Resources
116+
117+
The Bootstrap Template creates a few resources in the Azure Subscription(s) and the Azure DevOps Organization that you
118+
pointed to with the inputs, above. These resources establish the CI/CD pipelines in ADO, and map those pipelines to
119+
the appropriate Azure Subscriptions.
120+
121+
Here's a list of the resources that are provisioned including what a typical name might be, what kind of resource it is,
122+
and some extra detail about the purpose of the resource.
123+
124+
| Name (typical) | Resource Type | Description |
125+
| --- | --- | --- |
126+
| Infrastructure Pipeline Variables | azuredevops_variable_group | Common values that all pipelines share |
127+
| Infrastructure Pipeline Variables - *prod* | azuredevops_variable_group | Environment-specific values, per pipeline |
128+
| Infrastructure Pipeline Secrets - *prod* | azuredevops_variable_group | Environment-specific secret values, per pipeline |
129+
| Infrastructure Repository | azuredevops_git_repository | The Git repository where Infrastructure templates are stored and watched for changes |
130+
| Infrastructure CICD | azuredevops_build_definition | The Build Definition that watches for IaC Template changes, and then triggers the Infrastructure pipelines |
131+
| Infrastructure Deployment Service Connection | azuredevops_serviceendpoint_azurerm | The service connection that the Infrastructure Template will run under |
132+
| bootstrap-deploy-app | azuread_application | app |
133+
| n/a | azuread_service_principal | The Azure Service Principal that the Infrastructure Template will run under |
134+
| n/a | azurerm_role_assignment | The Azure Service Principal role assignment that the Infrastructure Template will run under |
135+
| bootstrap-iac-tf-workspaces | azurerm_resource_group | The Azure Resource Group which houses the remote state containers for Terraform |
136+
| iactf*prod* | azurerm_storage_account | An Azure Storage Account to house remote state containers, for a given environment |
137+
| tfstate | azurerm_storage_container | The Azure Blob Storage Container, within an environment-specific Azure Storage Account that will house remote state containers for Terraform |
138+
| *project_name* | The ADO Project that the Bootstrap Template will add pipeline resources to |
139+
140+
### Outputs
141+
142+
After the Bootstrap Template has been applied (via `terraforma apply`) you will see some output reflecting the
143+
144+
| name | description |
145+
| ---- | ----------- |
146+
| project_id | The ID of the Project that was provisioned |
147+
| project_name | The name of the Project that was provisioned |
148+
| repo_clone_url | The *https* (not the *ssh*) URL of the IaC Repository that was created |
149+
150+
## Running the Bootstrap Template
151+
152+
### Set-up
153+
154+
For the purposes of this Bootstrap Template, you'll need a few tools to be ready on your workstation.
155+
156+
Please see the quick start guide's list of prerequisites: *[quick-start guide prerequisites](https://github.com/microsoft/cobalt/blob/master/docs/2_QUICK_START_GUIDE.md#2.3-prerequisites).*
157+
158+
## Example Usage
159+
160+
For this example, we'll be using `bash` running on a unix, such as OSX, Ubuntu or Windows Subsystem for Linux.
161+
162+
1. Navigate to the directory where the `ado-bootstrap-iac-pipeline` template (where this `README.md` file is at).
163+
164+
1. Execute the following commands to set the environment variables that the template will require
165+
for Terraform, Azure and Azure DevOps. This will take the values in an .env file and export them in the local
166+
shell:
167+
168+
```bash
169+
# these commands `export` the environment variables needed to run this template into the local shell
170+
DOT_ENV=<path to your .env file -- see .env.template for more information>
171+
export $(cat $DOT_ENV | xargs)
172+
```
173+
174+
1. Execute the following commands to set up your terraform workspace.
175+
176+
_Note: the Bootstrap Template creates the Azure resources needed for handling Terraform remote state, but
177+
we're not using remote state configuration for this._
178+
179+
```bash
180+
# This command configures terraform to use a local workspace unique to you.
181+
terraform init
182+
```
183+
184+
5. Execute the following commands to orchestrate a deployment.
185+
186+
```bash
187+
# See what terraform will try to deploy without actually deploying
188+
terraform plan
189+
```
190+
191+
Review the output from the `plan` and make sure everything looks right.
192+
193+
For example, you might want the names of environments, or Resource Groups to be slightly different.
194+
195+
Also, if you happen to be running the Bootstrap Template
196+
a second time (after a previous `apply` operation), then you'll want to be careful that Terraform isn't removing or
197+
replacing any resources that you didn't intend it to.
198+
199+
When everything looks good, you can let Terraform create everything in the plan:
200+
201+
```bash
202+
# Execute a deployment
203+
terraform apply
204+
```
205+
206+
6. Contribute your Cobalt IaC repo to the newly provisioned Infrastructure Repository.
207+
208+
7. Trigger the Infrastructure CICD pipeline that was provisioned into the Project.
209+
210+
8. Authorize the Infrastructure CICD pipeline to access the variable groups that were provisioned by the into the Project.
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Make sure to set the following environment variables:
2+
# AZDO_PERSONAL_ACCESS_TOKEN
3+
# AZDO_ORG_SERVICE_URL
4+
provider "azuredevops" {
5+
version = ">= 0.0.1"
6+
}
7+
8+
provider "null" {
9+
version = "2.1.2"
10+
}
11+
12+
// use the following two block (a `resource` and a `local` block) to
13+
// use an existing Azure DevOps Project.
14+
//
15+
//data "azuredevops_projects" "p" {
16+
// project_name = var.project_name
17+
//}
18+
//
19+
//locals {
20+
// project_name = data.azuredevops_projects.p.projects.*.name[0]
21+
// project_id = data.azuredevops_projects.p.projects.*.project_id[0]
22+
//}
23+
24+
// use the following two block (a `resource` and a `local` block) to
25+
// create a new Azure DevOps Project.
26+
27+
resource "azuredevops_project" "p" {
28+
project_name = var.project_name
29+
}
30+
31+
locals {
32+
project_name = azuredevops_project.p.project_name
33+
project_id = azuredevops_project.p.id
34+
}
35+
36+
resource "azuredevops_variable_group" "core_vg" {
37+
project_id = local.project_id
38+
name = "Infrastructure Pipeline Variables"
39+
description = "Managed by Terraform"
40+
allow_access = true
41+
42+
variable {
43+
name = "AGENT_POOL"
44+
value = "Hosted Ubuntu 1604"
45+
}
46+
variable {
47+
name = "BUILD_ARTIFACT_NAME"
48+
value = "drop"
49+
}
50+
variable {
51+
name = "FORCE_RUN"
52+
value = "False"
53+
}
54+
variable {
55+
name = "SERVICE_CONNECTION_NAME"
56+
value = azuredevops_serviceendpoint_azurerm.endpointazure.service_endpoint_name
57+
}
58+
}
59+
60+
resource "azuredevops_variable_group" "stage_vg" {
61+
project_id = local.project_id
62+
count = length(var.environments)
63+
name = format("Infrastructure Pipeline Variables - %s", var.environments[count.index].environment)
64+
description = "Managed by Terraform"
65+
allow_access = false
66+
67+
variable {
68+
name = "ARM_SUBSCRIPTION_ID"
69+
value = var.environments[count.index].az_sub_id
70+
}
71+
variable {
72+
name = "REMOTE_STATE_ACCOUNT"
73+
value = format("tf%s", var.environments[count.index].environment)
74+
}
75+
variable {
76+
name = "REMOTE_STATE_CONTAINER"
77+
value = local.tf_state_container_name
78+
}
79+
}
80+
81+
resource "azuredevops_variable_group" "secrets_vg" {
82+
project_id = local.project_id
83+
count = length(var.environments)
84+
name = format("Infrastructure Pipeline Secrets - %s", var.environments[count.index].environment)
85+
description = "Managed by Terraform"
86+
allow_access = false
87+
88+
variable {
89+
name = "PLACEHOLDER"
90+
value = "placeholder"
91+
}
92+
}
93+
94+
resource "azuredevops_git_repository" "repo" {
95+
project_id = local.project_id
96+
name = "Infrastructure Repository"
97+
initialization {
98+
init_type = "Clean"
99+
}
100+
}
101+
102+
resource "azuredevops_build_definition" "build" {
103+
project_id = local.project_id
104+
name = "Infrastructure CICD"
105+
106+
repository {
107+
repo_type = "TfsGit"
108+
repo_name = azuredevops_git_repository.repo.name
109+
branch_name = azuredevops_git_repository.repo.default_branch
110+
yml_path = "devops/providers/azure-devops/templates/azure-pipelines.yml"
111+
}
112+
113+
variable_groups = concat(
114+
[azuredevops_variable_group.core_vg.id],
115+
azuredevops_variable_group.stage_vg.*.id,
116+
azuredevops_variable_group.secrets_vg.*.id
117+
)
118+
}
119+
120+
resource "azuredevops_serviceendpoint_azurerm" "endpointazure" {
121+
project_id = local.project_id
122+
service_endpoint_name = "Infrastructure Deployment Service Connection"
123+
azurerm_spn_clientid = azuread_service_principal.sp.application_id
124+
azurerm_spn_clientsecret = random_string.random.result
125+
azurerm_spn_tenantid = data.azurerm_subscription.sub.tenant_id
126+
azurerm_subscription_id = data.azurerm_subscription.sub.subscription_id
127+
azurerm_subscription_name = data.azurerm_subscription.sub.display_name
128+
azurerm_scope = data.azurerm_subscription.sub.id
129+
}

0 commit comments

Comments
 (0)