├── .gitignore ├── Agenda.md ├── README.md ├── challenges.md ├── challenges ├── 00-gettingstarted │ ├── README.md │ └── local.md ├── 01-connectingtoazure │ └── README.md ├── 02-aci-helloworld │ └── README.md ├── 03-azurevm │ └── README.md ├── 04-terraformcount │ └── README.md ├── 05-terraformmodules │ └── README.md ├── 06-publicmoduleregistry │ └── README.md ├── 07-remotebackend │ └── README.md ├── 08-setupterraformenterprise │ └── README.md ├── 09-privatemoduleregistry │ └── README.md └── 10-sentinelpolicy │ └── README.md ├── docs └── AzureTerraformWorkshopPresentation.pptx ├── event-invitation.md ├── examples ├── simple-rg │ └── main.tf └── ssh │ └── main.tf ├── img ├── 2018-04-07-15-08-41.png ├── 2018-04-07-16-54-28.png ├── 2018-04-14-12-58-33.png ├── 2018-04-14-12-59-54.png ├── 2018-04-14-13-10-52.png ├── 2018-04-14-13-21-32.png ├── 2018-04-14-14-04-56.png ├── 2018-04-14-14-10-32.png ├── 2018-04-14-14-11-20.png ├── 2018-04-14-14-12-05.png ├── 2018-04-14-14-13-01.png ├── 2018-04-14-14-13-52.png ├── 2018-04-14-14-15-02.png ├── 2018-04-15-13-09-55.png ├── 2018-04-15-19-23-40.png ├── 2018-04-16-20-02-58.png ├── 2018-04-16-20-03-30.png ├── 2018-05-07-18-08-30.png ├── 2018-05-07-18-11-33.png ├── 2018-05-07-18-13-28.png ├── 2018-05-07-18-20-56.png ├── 2018-05-07-18-29-10.png ├── 2018-05-09-09-10-24.png ├── 2018-05-09-10-20-28.png ├── 2018-05-09-14-55-42.png ├── 2018-05-10-17-14-51.png ├── 2018-05-10-17-17-27.png ├── 2018-05-10-17-37-05.png ├── 2018-05-10-17-40-35.png ├── 2018-05-11-11-22-22.png ├── 2018-05-11-11-26-22.png ├── 2018-05-14-07-27-11.png ├── 2018-05-14-08-18-48.png ├── 2018-05-28-12-25-01.png ├── 2018-05-28-12-27-31.png ├── 2018-05-28-12-29-06.png ├── 2018-05-28-12-30-33.png ├── 2018-05-28-13-58-49.png ├── 2018-05-28-14-01-30.png ├── 2018-05-28-14-03-05.png ├── 2018-05-28-14-04-39.png ├── 2018-05-28-14-05-39.png ├── 2018-05-28-14-09-39.png ├── 2018-06-07-16-23-29.png ├── 2018-11-02-12-59-21.png ├── 2019-05-08-09-24-12.png └── 2019-05-08-09-27-19.png └── solutions ├── 01-connectingtoazure └── main.tf ├── 02-aci-helloworld └── main.tf ├── 03-azurevm └── main.tf ├── 04-terraformcount └── main.tf ├── 05-terraformmodules ├── environments │ └── dev │ │ └── main.tf └── modules │ └── my_virtual_machine │ └── main.tf └── 06-publicmoduleregistry └── main.tf /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.tfstate 3 | *.tfstate.backup 4 | 5 | # Module directory 6 | .terraform/ 7 | Notes.md 8 | 9 | *.tfvars 10 | *terraform.tfstate* 11 | *.terraform.tfstate.lock.info 12 | ~*.pptx 13 | __*.* 14 | .DS_Store 15 | demo/ 16 | 17 | id_rsa* 18 | -------------------------------------------------------------------------------- /Agenda.md: -------------------------------------------------------------------------------- 1 | # Agenda 2 | 3 | | Time | Topic | 4 | | ---- | ----- | 5 | | 9:00-9:30 | Getting Started & Overview | 6 | | 9:30-10:00 | Terraform OSS & Workflow | 7 | | 10:00-10:30 | Challenge 00 - Getting Started | 8 | | | Challenge 01 - Connecting to Azure | 9 | | 10:30-11:00 | State, Variables & Outputs | 10 | | 10:30-11:00 | Challenge 02 - Azure Container Instance | 11 | | 11:00-11:30 | Interpolations & Modules | 12 | | 11:30-12:00 | Challenge 03 - Azure Virtual Machine | 13 | | 12:00-1:00 | Lunch / Solutions Round Table | 14 | | 1:00-1:30 | Backend & Internals | 15 | | 1:30-2:00 | Challenge 06 - Public Module Registry | 16 | | 2:00-3:00 | Terraform Enterprise, Workspaces, & State Management | 17 | | 3:00-3:30 | Collaboration & Private Module Registry | 18 | | 3:30-4:00 | Governance using Sentinel | 19 | | 4:00-4:30 | Closeout and Questions | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform Workshop 2 | 3 | This repository contains materials for the Infrastructure as Code - Building Azure infrastructure using Terraform and Terraform Enterprise event series. This includes attendees challenge guides, slides, and source code. 4 | 5 | Workshop Locations: 6 | 7 | - Wednesday April 24th, 2019 [Nashville, TN](http://www.cvent.com/d/l6qs97) 8 | - Wednesday May 14th, 2019 [Columbus, OH](http://www.cvent.com/d/86qcx5) 9 | - Wednesday May 21st, 2019 [New York, NY](http://www.cvent.com/d/06qw2x) 10 | 11 | ## Surveys 12 | 13 | Handed out at the end of the day. 14 | 15 | ## Presentation 16 | 17 | The Power Point presentation can be found here: [AzureTerraformWorkshopPresentation](docs/AzureTerraformWorkshopPresentation.pptx) 18 | 19 | ## Challenges 20 | 21 | The Challenge description can be found here: [Challenges](challenges.md) 22 | 23 | ## Resources 24 | 25 | - https://www.terraform.io/docs/providers/azurerm 26 | - http://aka.ms/tfhub Microsoft documentation 27 | - https://github.com/kamatama41/tfenv Terraform Version Switcher 28 | 29 | ## Previous Locations 30 | 31 | - Monday November 5th, 2018 [Tampa, FL](https://www.cardinalsolutions.com/events/2018/11/11-5-18-tpainfracstructureascode) 32 | - Monday June 11th, 2018 [New York City, NY](https://www.eventbrite.com/e/provision-and-manage-microsoft-azure-infrastructure-tickets-46152026955) 33 | - Tuesday June 5th, 2018 [Columbus, OH](https://www.eventbrite.com/e/provision-and-manage-microsoft-azure-infrastructure-tickets-45781193783) 34 | - Thursday May 31st, 2018 [Atlanta, GA](https://www.eventbrite.com/e/provision-and-manage-microsoft-azure-infrastructure-tickets-44854601320) 35 | - Wednesday May 23rd, 2018 [Chicago, IL](https://www.eventbrite.com/e/provision-and-manage-microsoft-azure-infrastructure-tickets-45562831656) -------------------------------------------------------------------------------- /challenges.md: -------------------------------------------------------------------------------- 1 | # Building Azure infrastructure using Terraform and Terraform Enterprise Challenge Guide 2 | 3 | Below is a series of "challenges" or guided exercises to help attendees learn about Terraform and how to use it to create Azure Resources. These are not meant the be "hands-on labs" or step-by-step guides. The goal is to provide a series of exercises that have an expected outcome. Some steps and code will be provided. In the end, the hands-on experience should lead to a deeper level of learning. 4 | 5 | ## Prerequisites 6 | 7 | - Laptop 8 | - Azure account with access to deploy at least 10 cores and permissions to generate a service principal. A trail Azure account can be created for this event. 9 | - Github Account (Free) 10 | - Terraform Enterprise Account. Attendees will be provided a 30-day trial. 11 | 12 | Setting up your laptop for the challenges is done in Challenge 00 - Getting Started. 13 | 14 | ## Challenges 15 | 16 | For each challenge, change your working directory to the folder for that challenge. 17 | Each challenge is meant to be independent of each other. 18 | If you get stuck, refer to the `solutions` directory for a working solution to the challenge. 19 | 20 | ### Challenge 00: [Getting Started](challenges/00-gettingstarted/README.md) 21 | 22 | ### Challenge 01: [Connecting to Azure](challenges/01-connectingtoazure/README.md) 23 | 24 | ### Challenge 02: [Azure Container Instance](challenges/02-aci-helloworld/README.md) 25 | 26 | ### Challenge 03: [Azure Virtual Machine](challenges/03-azurevm/README.md) 27 | 28 | ### Challenge 04: [Terraform Count](challenges/04-terraformcount/README.md) 29 | 30 | ### Challenge 05: [Terraform Modules](challenges/05-terraformmodules/README.md) 31 | 32 | ### Challenge 06: [Public Module Registry](challenges/06-publicmoduleregistry/README.md) 33 | 34 | ### Challenge 07: [Remote Backend](challenges/07-remotebackend/README.md) 35 | 36 | ### Challenge 08: [Setup Terraform Enterprise](challenges/08-setupterraformenterprise/README.md) 37 | 38 | ### Challenge 09: [Private Module Registry](challenges/09-privatemoduleregistry/README.md) 39 | 40 | ### Challenge 10: [Sentinel Policy](challenges/10-value/README.md) 41 | -------------------------------------------------------------------------------- /challenges/00-gettingstarted/README.md: -------------------------------------------------------------------------------- 1 | # 00 - Getting Started 2 | 3 | ## Expected Outcome 4 | 5 | In this challenge, you will connect to the [Azure Cloud Shell](https://azure.microsoft.com/en-us/features/cloud-shell/) that will be needed for future challenges. 6 | 7 | In this challenge, you will: 8 | 9 | - Login to the Azure Portal 10 | - Verify `az` installation 11 | - Verify `terraform` installation 12 | - Create a folder structure to complete challenges 13 | 14 | > Note: If you would rather complete the challenges from you local worskstation, detailed instructions can be found [here](local.md). 15 | ## How to 16 | 17 | ### Login to the Azure Portal 18 | 19 | Navigate to [https://portal.azure.com](https://portal.azure.com) and login with your Azure Credentials. 20 | 21 | This workshop will require that you have access to an Azure Subscription with at least Contributor rights to create resources. If you do not currently have access you can create a trial account by going to [https://azure.microsoft.com/en-us/free](https://azure.microsoft.com/en-us/free) and registering for a 3-month trail. 22 | 23 | Signing up for a trial requires: 24 | 25 | - A unique Microsoft Live Account that has not registered for a trial for in the past 26 | - A Credit Card, used to verify identity and will not be charged unless you opt-in after the trial is over 27 | 28 | > If you are having issues with this access, please alert the instructor ASAP as this will prevent you from completing the challenges. 29 | 30 | ### Open the Cloud Shell 31 | 32 | Located at the top of the page is the button open the Azure Cloud Shell inside the Azure Portal. 33 | 34 | ![](../../img/2018-05-28-12-25-01.png) 35 | 36 | > Note: Another option is to use the full screen Azure Cloud Shell at [https://shell.azure.com/](https://shell.azure.com/). 37 | 38 | The first time you connect to the Azure Cloud Shell you will be prompted to setup an Azure File Share that you will persist the environment. 39 | 40 | ![](../../img/2018-05-28-12-27-31.png) 41 | 42 | Click the "Bash (Linux)" option. 43 | 44 | Select the Azure Subscription and click "Create storage": 45 | 46 | ![](../../img/2018-05-28-12-29-06.png) 47 | 48 | After a few seconds you should see that your storage account has been created: 49 | 50 | ![](../../img/2018-05-28-12-30-33.png) 51 | 52 | > Note: Behind the scenes this is creating a new Resource Group with the name `cloud-shell-storage-eastus` (or which ever region you defaulted to). If you need more information, it can be found [here](https://docs.microsoft.com/en-us/azure/cloud-shell/persisting-shell-storage). 53 | 54 | SUCCESS! 55 | You are now logged into the Azure Cloud Shell which uses your portal session to automatically authenticate you with the Azure CLI and Terraform. 56 | 57 | ### Verify Utilities 58 | 59 | In the Cloud Shell type the following commands and verify that the utilities are installed: 60 | 61 | `az -v` 62 | 63 |
View Output 64 |

65 | 66 | ```sh 67 | $ az -v 68 | azure-cli (2.0.64) 69 | 70 | acr 2.2.6 71 | acs 2.4.1 72 | advisor 2.0.0 73 | ams 0.4.5 74 | appservice 0.2.19 75 | backup 1.2.4 76 | batch 4.0.1 77 | batchai 0.4.8 78 | billing 0.2.1 79 | botservice 0.2.0 80 | cdn 0.2.3 81 | cloud 2.1.1 82 | cognitiveservices 0.2.5 83 | command-modules-nspkg 2.0.2 84 | configure 2.0.23 85 | consumption 0.4.2 86 | container 0.3.16 87 | core 2.0.64 88 | cosmosdb 0.2.10 89 | deploymentmanager 0.1.0 90 | dla 0.2.5 91 | dls 0.1.9 92 | dms 0.1.3 93 | eventgrid 0.2.3 94 | eventhubs 0.3.5 95 | extension 0.2.5 96 | feedback 2.2.1 97 | find 0.3.2 98 | hdinsight 0.3.3 99 | interactive 0.4.3 100 | iot 0.3.8 101 | iotcentral 0.1.6 102 | keyvault 2.2.15 103 | kusto 0.2.2 104 | lab 0.1.7 105 | maps 0.3.4 106 | monitor 0.2.13 107 | network 2.4.0 108 | nspkg 3.0.3 109 | policyinsights 0.1.3 110 | privatedns 1.0.0 111 | profile 2.1.5 112 | rdbms 0.3.10 113 | redis 0.4.2 114 | relay 0.1.4 115 | reservations 0.4.2 116 | resource 2.1.14 117 | role 2.6.1 118 | search 0.1.1 119 | security 0.1.1 120 | servicebus 0.3.5 121 | servicefabric 0.1.18 122 | signalr 1.0.0 123 | sql 2.2.3 124 | sqlvm 0.1.1 125 | storage 2.4.1 126 | telemetry 1.0.2 127 | vm 2.2.20 128 | 129 | Python location '/opt/az/bin/python3' 130 | Extensions directory '~/.azure/cliextensions' 131 | 132 | Python (Linux) 3.6.5 (default, May 2 2019, 00:44:44) 133 | [GCC 5.4.0 20160609] 134 | 135 | Legal docs and information: aka.ms/AzureCliLegal 136 | 137 | Your CLI is up-to-date. 138 | ``` 139 |

140 |
141 | 142 | `terraform -v` 143 | 144 |
View Output 145 |

146 | 147 | ```sh 148 | $ terraform -v 149 | Terraform v0.11.13 150 | ``` 151 | 152 |

153 |
154 | 155 | ### Verify Subscription 156 | 157 | Run the command `az account list -o table`. 158 | 159 | ```sh 160 | az account list -o table 161 | Name CloudName SubscriptionId State IsDefault 162 | ------------------------------- ----------- ------------------------------------ ------- ----------- 163 | Visual Studio Premium with MSDN AzureCloud xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx Enabled True 164 | Another sub1 AzureCloud xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx Enabled False 165 | Another sub2 AzureCloud xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx Enabled False 166 | Another sub3 AzureCloud xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx Enabled False 167 | ``` 168 | 169 | If you have more than subscription, make sure that subscription is set as default using the subscription name: 170 | 171 | ```sh 172 | az account set -s 'Visual Studio Premium with MSDN' 173 | ``` 174 | 175 | ### Create Challenge Scaffolding 176 | 177 | To make things easy for the challenges, let's create a folder structure to hold the terraform configuration we will create. 178 | 179 | Make sure you are in the home directory: 180 | 181 | ```sh 182 | cd ~/ 183 | ``` 184 | 185 | Run the following in the azure cloud shell, this will simply create a folder structure for you to place your Terraform configuration: 186 | 187 | ```sh 188 | mkdir AzureWorkChallenges && cd AzureWorkChallenges && mkdir challenge01 && mkdir challenge02 && mkdir challenge03 && mkdir challenge04 && mkdir challenge05 && mkdir challenge06 && mkdir challenge07 189 | ``` 190 | 191 | What you should end up with is a structure like this: 192 | 193 | ```sh 194 | AzureWorkChallenges 195 | |- challenge01 196 | |- challenge02 197 | |- challenge03 198 | |- challenge04 199 | |- challenge05 200 | |- challenge06 201 | |- challenge07 202 | ``` 203 | -------------------------------------------------------------------------------- /challenges/00-gettingstarted/local.md: -------------------------------------------------------------------------------- 1 | # Installing Challenge Requirements Locally 2 | 3 | This section is only needed if you wish to install all the tooling on your local machine. 4 | 5 | > NOTE: If you are using windows, it is advised that you use the git-bash terminal to execute the workshop commands. 6 | 7 | ## How to 8 | 9 | ### Download the Azure CLI 2.0 10 | 11 | To make some of the steps easier we will use the Azure CLI and authenticate to Azure. 12 | 13 | Download and install the latest Azure CLI: 14 | 15 | For **Windows** - [Download Here](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-apt?view=azure-cli-latest) 16 | 17 | For **Mac** - [Download Here](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-macos?view=azure-cli-latest) 18 | 19 | > If you are having issues, more information can be found [here](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) 20 | 21 | Verify the installation by running `az -v`, you should see something like this, version 2.0.0 or higher is needed: 22 | 23 | ```sh 24 | $ az -v 25 | azure-cli (2.0.32) 26 | 27 | ... Other python dependencies 28 | 29 | Python (Darwin) 3.6.5 (default, Apr 25 2018, 14:26:36) 30 | [GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.39.2)] 31 | 32 | Legal docs and information: aka.ms/AzureCliLegal 33 | ``` 34 | 35 | ### Login to Azure 36 | 37 | This workshop will require that you have access to an Azure Subscription with at least Contributor rights to create resources and the ability to generate a service principal for the subscription. If you do not currently have access you can create a trial account by going to [https://azure.microsoft.com/en-us/free](https://azure.microsoft.com/en-us/free) and registering for a 3-month trail. 38 | 39 | Signing up for a trial requires: 40 | 41 | - A unique Microsoft Live Account that has not registered for a trial for in the past 42 | - A Credit Card, used to verify identity and will not be charged unless you opt-in after the trial is over 43 | 44 | > If you are having issues with this access, please alert the instructor ASAP as this will prevent you from completing the challenges. 45 | 46 | Login with the Azure CLI by running `az login`. 47 | 48 | ```sh 49 | $ az login 50 | To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code XXXXXXXX to authenticate. 51 | ``` 52 | 53 | Navigate to [https://microsoft.com/devicelogin](https://microsoft.com/devicelogin) and enter the code given in the CLI, then log in using the account that has access to Azure. 54 | 55 | Once complete, verify Azure CLI Access by running `az account show -o table`. 56 | 57 | ```sh 58 | $ az account show -o table 59 | EnvironmentName IsDefault Name State TenantId 60 | ----------------- ----------- ------------------------------- ------- ------------------------------------ 61 | AzureCloud True Visual Studio Premium with MSDN Enabled GUID 62 | ``` 63 | 64 | You are now connecting to Azure from the Azure CLI! 65 | 66 | As one last step here, login to the [Azure Portal](https://portal.azure.com/), this will be useful to see the resources get created in future challenges. 67 | 68 | ### Download Terraform 69 | 70 | In this workshop we will be using terraform 0.11.7 for all of the challenges. 71 | 72 | Navigate to the downloads page [https://releases.hashicorp.com/terraform/0.11.7/](https://releases.hashicorp.com/terraform/0.11.7/) and select the `.zip` file for your operating system. 73 | 74 | For **Windows** - [Download Here](https://releases.hashicorp.com/terraform/0.11.7/terraform_0.11.7_windows_amd64.zip) 75 | 76 | For **Mac** - [Download Here](https://releases.hashicorp.com/terraform/0.11.7/terraform_0.11.7_darwin_amd64.zip) 77 | 78 | Once downloaded, extract the contents which is just a `terraform` binary and copy it to a folder inside your PATH environment variable. 79 | 80 | For **Windows** - create a new directory and add it to your PATH environment variable 81 | 82 | For **Mac** - typically `/usr/local/bin` 83 | 84 | > If you are having issues, more information can be found [here](https://www.terraform.io/intro/getting-started/install.html) 85 | 86 | Verify the installation by running `terraform -v`, you should see something like this: 87 | 88 | ```sh 89 | $ terraform -v 90 | Terraform v0.11.7 91 | ``` 92 | 93 | The latest release can always be found on the [Terraform Website](https://www.terraform.io/downloads.html) 94 | 95 | ### Download Visual Studio Code 96 | 97 | *or any other text editor...* 98 | 99 | You can Download the latest version here: 100 | 101 | https://code.visualstudio.com/Download 102 | 103 | ![](../../img/2018-05-09-09-10-24.png) 104 | 105 | Optionally you can also install the Terraform Extension [here](https://marketplace.visualstudio.com/items?itemName=mauve.terraform) 106 | 107 | ### Clone this repository 108 | 109 | Install git by going to [here](https://git-scm.com/downloads) and downloading the latest git version. 110 | 111 | Once installed, open up a terminal and change directory into a path that you would like to work out of. 112 | Then open the repository in VS Code. 113 | 114 | ```sh 115 | cd ~/Projects/ 116 | git clone https://github.com/CardinalNow/TerraformWorkshop.git 117 | code TerraformWorkshop 118 | ``` 119 | 120 | > If running `code TerraformWorkshop` doesn't launch VS Code, open up VS Code manually and open the folder you cloned the repository to. 121 | 122 | ### Github Access 123 | 124 | If you already have a github account you can skip this step. 125 | 126 | Github repositories will be needed to complete some of the later challenges. 127 | 128 | Sign up for a free github.com account by going to [https://github.com/join](https://github.com/join) and following the instructions. 129 | 130 | Once created, login. 131 | -------------------------------------------------------------------------------- /challenges/01-connectingtoazure/README.md: -------------------------------------------------------------------------------- 1 | # 01 - Connection To Azure 2 | 3 | ## Expected Outcome 4 | 5 | In this challenge, you will use Terraform from the Azure Cloud Shell to create simple infrastructure in your Azure Subscription. 6 | 7 | In this challenge, you will: 8 | 9 | - Initialize Terraform 10 | - Run a `plan` on simple a simple resource 11 | - Run an `apply` to create Azure infrastructure 12 | - Run a `destroy` to remove Azure infrastructure 13 | 14 | ## How To 15 | 16 | ### Create Terraform Configuration 17 | 18 | From the Cloud Shell, change directory into a folder specific to this challenge. If you created the scaffolding in Challenge 00, then then you can use the command `cd ~/AzureWorkChallenges/challenge01/`. 19 | 20 | Create a file named `main.tf` and add a single Resource Group resource. 21 | 22 | ```hcl 23 | resource "azurerm_resource_group" "test" { 24 | name = "challenge01-rg" 25 | location = "eastus" 26 | } 27 | ``` 28 | 29 | This will create a simple Resource Group and allow you to walk through the Terraform Workflow. 30 | 31 | ### Run the Terraform Workflow 32 | 33 | `terraform init` 34 |
View Output 35 |

36 | 37 | ```sh 38 | $ terraform init 39 | 40 | Initializing provider plugins... 41 | 42 | Terraform has been successfully initialized! 43 | 44 | You may now begin working with Terraform. Try running "terraform plan" to see 45 | any changes that are required for your infrastructure. All Terraform commands 46 | should now work. 47 | 48 | If you ever set or change modules or backend configuration for Terraform, 49 | rerun this command to reinitialize your working directory. If you forget, other 50 | commands will detect it and remind you to do so if necessary. 51 | ``` 52 | 53 |

54 |
55 | 56 | --- 57 | `terraform plan` 58 | 59 |
View Output 60 |

61 | 62 | ```sh 63 | $ terraform plan 64 | Refreshing Terraform state in-memory prior to plan... 65 | The refreshed state will be used to calculate this plan, but will not be 66 | persisted to local or remote state storage. 67 | 68 | 69 | ------------------------------------------------------------------------ 70 | 71 | An execution plan has been generated and is shown below. 72 | Resource actions are indicated with the following symbols: 73 | + create 74 | 75 | Terraform will perform the following actions: 76 | 77 | + azurerm_resource_group.main 78 | id: 79 | location: "eastus" 80 | name: "challenge01-rg" 81 | tags.%: 82 | 83 | 84 | Plan: 1 to add, 0 to change, 0 to destroy. 85 | 86 | ------------------------------------------------------------------------ 87 | 88 | Note: You didn't specify an "-out" parameter to save this plan, so Terraform 89 | can't guarantee that exactly these actions will be performed if 90 | "terraform apply" is subsequently run. 91 | ``` 92 | 93 |

94 |
95 | 96 | --- 97 | `terraform apply` 98 |
View Output 99 |

100 | 101 | ```sh 102 | $ terraform apply 103 | 104 | An execution plan has been generated and is shown below. 105 | Resource actions are indicated with the following symbols: 106 | + create 107 | 108 | Terraform will perform the following actions: 109 | 110 | + azurerm_resource_group.main 111 | id: 112 | location: "eastus" 113 | name: "challenge01-rg" 114 | tags.%: 115 | 116 | 117 | Plan: 1 to add, 0 to change, 0 to destroy. 118 | 119 | Do you want to perform these actions? 120 | Terraform will perform the actions described above. 121 | Only 'yes' will be accepted to approve. 122 | 123 | Enter a value: yes 124 | 125 | azurerm_resource_group.main: Creating... 126 | location: "" => "eastus" 127 | name: "" => "challenge01-rg" 128 | tags.%: "" => "" 129 | azurerm_resource_group.main: Creation complete after 1s (ID: /subscriptions/.../resourceGroups/challenge01-rg) 130 | 131 | Apply complete! Resources: 1 added, 0 changed, 0 destroyed. 132 | ``` 133 |

134 |
135 | 136 | --- 137 | 138 | Congrats, you just created your first Azure resource using Terraform! 139 | 140 | ### Verify in the Azure Portal 141 | 142 | Head over to the [Azure Portal](https://portal.azure.com/) 143 | 144 | View all Resource Groups and you should see the recently created Resource Group. 145 | ![](../../img/2018-05-09-10-20-28.png) 146 | 147 | ### Scale Resources 148 | 149 | Now add a new Resource Group resource that scales with a `count` parameter. 150 | 151 | > Note: This is ADDING another `resource` block in addition to the one you have already created. 152 | 153 | ```hcl 154 | resource "azurerm_resource_group" "count" { 155 | name = "challenge01-rg-${count.index}" 156 | location = "eastus" 157 | count = 2 158 | } 159 | ``` 160 | 161 | Run another `terraform plan` then `terraform apply` and validate the resource groups have been created. 162 | 163 | --- 164 | 165 | ## How To - Part 2 (Import Resources) 166 | 167 | ### Create Infrastructure in the Portal 168 | 169 | Navigate to the Azure Portal and click on the "Resource groups" item on the left side and then click "+ Add": 170 | 171 | ![](../../img/2018-05-28-13-58-49.png) 172 | 173 | In the Resource Group create blade give the resource group the name "myportal-rg" and click "Review + Create" -> "Create": 174 | 175 | ![](../../img/2019-05-08-09-24-12.png) 176 | 177 | 178 | Once the Resource Group is created, navigate to it. 179 | 180 | Find the "+ Add" button and click it: 181 | 182 | ![](../../img/2018-05-28-14-03-05.png) 183 | 184 | Search for "Storage Account" and click the first item and then click "Create" : 185 | 186 | ![](../../img/2019-05-08-09-27-19.png) 187 | 188 | 189 | 190 | In the Storage Account create blade, fill out the following: 191 | 192 | - Subscription = Use the current subscription 193 | - Resource Group = Use Existing and select "myportal-rg" 194 | - Name = Must be a unique name, there will be a green checkmark that shows up in the text box if your name is available. Example "storageaccount" 195 | - Location = East US 196 | - Performance = Standard 197 | - Account Kind = Storage V2 198 | - Replication = LRS 199 | - Access Tier = Hot 200 | 201 | ![](../../img/2018-11-02-12-59-21.png) 202 | 203 | 204 | Click "Review + Create" -> "Create". 205 | 206 | At this point we have a Resource Group and a Storage Account and are ready to import this into Terraform. 207 | 208 | ![](../../img/2018-05-28-14-09-39.png) 209 | 210 | ### Create Terraform Configuration 211 | 212 | Your Azure Cloud Shell should still be in the folder for this challenge with a single `main.tf` file. 213 | We will now add `resource` blocks to represent the infrastructure we are about to import. 214 | 215 | We have two resources we need to import into our Terraform Configuration, to do this we need to do two things: 216 | 217 | 1. Create the base Terraform configuration for both resources. 218 | 2. Run `terraform import` to bring the infrastructure into our state file. 219 | 220 | To create the base configuration place the following code into the `main.tf` file. 221 | 222 | ```hcl 223 | resource "azurerm_resource_group" "import" { 224 | name = "myportal-rg" 225 | location = "eastus" 226 | } 227 | 228 | resource "azurerm_storage_account" "import" { 229 | name = "myusernamestorageaccount" 230 | resource_group_name = "${azurerm_resource_group.import.name}" 231 | location = "eastus" 232 | account_kind = "StorageV2" 233 | account_tier = "Standard" 234 | account_replication_type = "LRS" 235 | enable_https_traffic_only = true 236 | } 237 | ``` 238 | 239 | `terraform plan` 240 | 241 | Shows 2 to add 242 | 243 | ```sh 244 | Terraform will perform the following actions: 245 | 246 | + azurerm_resource_group.main 247 | id: 248 | location: "centralus" 249 | name: "myportal-rg" 250 | tags.%: 251 | 252 | + azurerm_storage_account.main 253 | id: 254 | access_tier: 255 | account_encryption_source: "Microsoft.Storage" 256 | account_kind: "Storage" 257 | account_replication_type: "LRS" 258 | account_tier: "Standard" 259 | enable_blob_encryption: 260 | enable_file_encryption: 261 | location: "centralus" 262 | name: "myusernamestorageaccount" 263 | primary_access_key: 264 | primary_blob_connection_string: 265 | primary_blob_endpoint: 266 | primary_connection_string: 267 | primary_file_endpoint: 268 | primary_location: 269 | primary_queue_endpoint: 270 | primary_table_endpoint: 271 | resource_group_name: "myportal-rg" 272 | secondary_access_key: 273 | secondary_blob_connection_string: 274 | secondary_blob_endpoint: 275 | secondary_connection_string: 276 | secondary_location: 277 | secondary_queue_endpoint: 278 | secondary_table_endpoint: 279 | tags.%: 280 | 281 | 282 | Plan: 2 to add, 0 to change, 0 to destroy. 283 | ``` 284 | 285 | > CAUTION: This is not what we want! 286 | 287 | ### Import the Resource Group 288 | 289 | We need two values to run the `terraform import` command: 290 | 291 | 1. Resource Address from our configuration 292 | 1. Azure Resource ID 293 | 294 | The Resource Address is simple enough, based on the configuration above it is simply "azurerm_resource_group.main". 295 | 296 | The Azure Resource ID can be retrieved using the Azure CLI by running `az group show -g myportal-rg --query id`. The value should look something like "/subscriptions/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myportal-rg". 297 | 298 | Now run the import command: 299 | 300 | ```sh 301 | $ terraform import azurerm_resource_group.import /subscriptions/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myportal-rg 302 | 303 | Import successful! 304 | 305 | The resources that were imported are shown above. These resources are now in 306 | your Terraform state and will henceforth be managed by Terraform. 307 | ``` 308 | 309 | ### Import the Storage Account 310 | 311 | The process here is the same. 312 | 313 | The Resource Address is simple enough, based on the configuration above it is simply "azurerm_storage_account.main". 314 | 315 | The Azure Resource ID can be retrieved using the Azure CLI by running `az storage account show -g myportal-rg -n myusernamestorageaccount --query id`. The value should look something like "/subscriptions/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myportal-rg/providers/Microsoft.Storage/storageAccounts/myusernamestorageaccount". 316 | 317 | ```sh 318 | $ terraform import azurerm_storage_account.import /subscriptions/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myportal-rg/providers/Microsoft.Storage/storageAccounts/myusernamestorageaccount 319 | 320 | Import successful! 321 | 322 | The resources that were imported are shown above. These resources are now in 323 | your Terraform state and will henceforth be managed by Terraform. 324 | ``` 325 | 326 | ### Verify Plan 327 | 328 | Run a `terraform plan`, you should see no changes: 329 | 330 | ```sh 331 | $ terraform plan 332 | 333 | ... 334 | 335 | No changes. Infrastructure is up-to-date. 336 | 337 | This means that Terraform did not detect any differences between your 338 | configuration and real physical resources that exist. As a result, no 339 | actions need to be performed. 340 | ``` 341 | 342 | ### Make a Change 343 | 344 | Add the following tag configuration to both the Resource Group and the Storage Account: 345 | 346 | ```hcl 347 | resource "azurerm_resource_group" "import" { 348 | ... 349 | tags { 350 | terraform = "true" 351 | } 352 | } 353 | 354 | resource "azurerm_storage_account" "import" { 355 | ... 356 | tags { 357 | terraform = "true" 358 | } 359 | } 360 | ``` 361 | 362 | Run a plan, we should see two changes. 363 | 364 | ```sh 365 | ~ azurerm_resource_group.import 366 | tags.%: "0" => "1" 367 | tags.terraform: "" => "true" 368 | 369 | ~ azurerm_storage_account.import 370 | tags.%: "0" => "1" 371 | tags.terraform: "" => "true" 372 | 373 | 374 | Plan: 0 to add, 2 to change, 0 to destroy. 375 | ``` 376 | 377 | Run `terraform apply`. 378 | 379 | SUCCESS! You have now brought existing infrastructure into Terraform. 380 | 381 | ### Cleanup 382 | 383 | When you are done, destroy the infrastructure, you no longer need it. 384 | 385 | ```sh 386 | $ terraform destroy 387 | azurerm_resource_group.main: Refreshing state... (ID: /subscriptions/.../resourceGroups/challenge01-rg) 388 | azurerm_resource_group.import: Refreshing state... (ID: /subscriptions/.../resourceGroups/myportal-rg) 389 | azurerm_resource_group.count[0]: Refreshing state... (ID: /subscriptions/.../resourceGroups/challenge01-rg-0) 390 | azurerm_resource_group.count[1]: Refreshing state... (ID: /subscriptions/.../resourceGroups/challenge01-rg-1) 391 | azurerm_storage_account.import: Refreshing state... (ID: /subscriptions/.../storageAccounts/myusernamestorageaccount) 392 | 393 | An execution plan has been generated and is shown below. 394 | Resource actions are indicated with the following symbols: 395 | - destroy 396 | 397 | Terraform will perform the following actions: 398 | 399 | - azurerm_resource_group.count[0] 400 | 401 | - azurerm_resource_group.count[1] 402 | 403 | - azurerm_resource_group.import 404 | 405 | - azurerm_resource_group.main 406 | 407 | - azurerm_storage_account.import 408 | 409 | 410 | Plan: 0 to add, 0 to change, 5 to destroy. 411 | 412 | Do you really want to destroy all resources? 413 | Terraform will destroy all your managed infrastructure, as shown above. 414 | There is no undo. Only 'yes' will be accepted to confirm.clear 415 | 416 | Enter a value: yes 417 | 418 | azurerm_resource_group.count[1]: Destroying... (ID: /subscriptions/.../resourceGroups/challenge01-rg-1) 419 | azurerm_resource_group.main: Destroying... (ID: /subscriptions/.../resourceGroups/challenge01-rg) 420 | azurerm_storage_account.import: Destroying... (ID: /subscriptions/.../storageAccounts/myusernamestorageaccount) 421 | azurerm_resource_group.count[0]: Destroying... (ID: /subscriptions/.../resourceGroups/challenge01-rg-0) 422 | azurerm_storage_account.import: Destruction complete after 1s 423 | azurerm_resource_group.import: Destroying... (ID: /subscriptions/.../resourceGroups/myportal-rg) 424 | azurerm_resource_group.main: Still destroying... (ID: /subscriptions/.../resourceGroups/challenge01-rg, 10s elapsed) 425 | azurerm_resource_group.count.1: Still destroying... (ID: /subscriptions/.../resourceGroups/challenge01-rg-1, 10s elapsed) 426 | azurerm_resource_group.count.0: Still destroying... (ID: /subscriptions/.../resourceGroups/challenge01-rg-0, 10s elapsed) 427 | azurerm_resource_group.import: Still destroying... (ID: /subscriptions/.../resourceGroups/myportal-rg, 10s elapsed) 428 | azurerm_resource_group.main: Still destroying... (ID: /subscriptions/.../resourceGroups/challenge01-rg, 20s elapsed) 429 | azurerm_resource_group.count.1: Still destroying... (ID: /subscriptions/.../resourceGroups/challenge01-rg-1, 20s elapsed) 430 | azurerm_resource_group.count.0: Still destroying... (ID: /subscriptions/.../resourceGroups/challenge01-rg-0, 20s elapsed) 431 | azurerm_resource_group.import: Still destroying... (ID: /subscriptions/.../resourceGroups/myportal-rg, 20s elapsed) 432 | azurerm_resource_group.main: Still destroying... (ID: /subscriptions/.../resourceGroups/challenge01-rg, 30s elapsed) 433 | azurerm_resource_group.count.1: Still destroying... (ID: /subscriptions/.../resourceGroups/challenge01-rg-1, 30s elapsed) 434 | azurerm_resource_group.count.0: Still destroying... (ID: /subscriptions/.../resourceGroups/challenge01-rg-0, 30s elapsed) 435 | azurerm_resource_group.import: Still destroying... (ID: /subscriptions/.../resourceGroups/myportal-rg, 30s elapsed) 436 | azurerm_resource_group.main: Still destroying... (ID: /subscriptions/.../resourceGroups/challenge01-rg, 40s elapsed) 437 | azurerm_resource_group.count.1: Still destroying... (ID: /subscriptions/.../resourceGroups/challenge01-rg-1, 40s elapsed) 438 | azurerm_resource_group.count.0: Still destroying... (ID: /subscriptions/.../resourceGroups/challenge01-rg-0, 40s elapsed) 439 | azurerm_resource_group.import: Still destroying... (ID: /subscriptions/.../resourceGroups/myportal-rg, 40s elapsed) 440 | azurerm_resource_group.count[0]: Destruction complete after 45s 441 | azurerm_resource_group.count[1]: Destruction complete after 45s 442 | azurerm_resource_group.main: Destruction complete after 45s 443 | azurerm_resource_group.import: Destruction complete after 46s 444 | 445 | Destroy complete! Resources: 5 destroyed. 446 | ``` 447 | 448 | Because the infrastructure is now managed by Terraform, we can destroy just like before. 449 | 450 | Run a `terraform destroy` and follow the prompts to remove the infrastructure. 451 | 452 | ## Advanced areas to explore 453 | 454 | 1. Play around with adjusting the `count` and `name` parameters, then running `plan` and `apply`. 455 | 2. Run the `plan` command with the `-out` option and apply that output. 456 | 3. Add tags to each resource. 457 | 458 | ## Resources 459 | 460 | - [Terraform Count](https://www.terraform.io/docs/configuration/interpolation.html#count-information) 461 | - [Terraform Import](https://www.terraform.io/docs/commands/import.html) 462 | -------------------------------------------------------------------------------- /challenges/02-aci-helloworld/README.md: -------------------------------------------------------------------------------- 1 | # 02 - Azure Container Instance 2 | 3 | ## Expected Outcome 4 | 5 | In this challenge, you will learn how to deploy a PaaS solution using Terraform. 6 | 7 | There are two docker containers that will be deployed: 8 | 9 | - [Hello World](https://github.com/Azure-Samples/aci-helloworld) 10 | - A simple NodeJS web application that displays a message. 11 | - Hosted on [Dockerhub](https://hub.docker.com/r/microsoft/aci-helloworld/) 12 | - [Sidecar](https://github.com/Azure-Samples/aci-tutorial-sidecar) 13 | - A simple watchdog script that calls the Hello World application every 3 seconds. 14 | - Hosted on [Dockerhub](https://hub.docker.com/r/microsoft/aci-tutorial-sidecar/) 15 | 16 | ## How to 17 | 18 | ### Create Terraform Configuration 19 | 20 | From the Cloud Shell, change directory into a folder specific to this challenge. If you created the scaffolding in Challenge 00, then then you can use the command `cd ~/AzureWorkChallenges/challenge02/`. 21 | 22 | Create a new file called `main.tf` with the following contents: 23 | 24 | ```hcl 25 | resource "random_pet" "main" { 26 | length = 2 27 | separator = "" 28 | } 29 | 30 | resource "azurerm_resource_group" "main" { 31 | name = "aci-helloworld" 32 | location = "eastus" 33 | } 34 | 35 | resource "azurerm_storage_account" "main" { 36 | name = "acidev${random_pet.main.id}" 37 | resource_group_name = "${azurerm_resource_group.main.name}" 38 | location = "${azurerm_resource_group.main.location}" 39 | account_tier = "Standard" 40 | account_replication_type = "LRS" 41 | } 42 | 43 | resource "azurerm_storage_share" "main" { 44 | name = "aci-test-share" 45 | resource_group_name = "${azurerm_resource_group.main.name}" 46 | storage_account_name = "${azurerm_storage_account.main.name}" 47 | quota = 1 48 | } 49 | 50 | resource "azurerm_container_group" "main" { 51 | name = "aci-helloworld" 52 | location = "${azurerm_resource_group.main.location}" 53 | resource_group_name = "${azurerm_resource_group.main.name}" 54 | ip_address_type = "public" 55 | dns_name_label = "aci-${random_pet.main.id}" 56 | os_type = "linux" 57 | 58 | container { 59 | name = "helloworld" 60 | image = "microsoft/aci-helloworld" 61 | cpu = "0.5" 62 | memory = "1.5" 63 | port = "80" 64 | 65 | environment_variables { 66 | "NODE_ENV" = "testing" 67 | } 68 | 69 | volume { 70 | name = "logs" 71 | mount_path = "/aci/logs" 72 | read_only = false 73 | share_name = "${azurerm_storage_share.main.name}" 74 | 75 | storage_account_name = "${azurerm_storage_account.main.name}" 76 | storage_account_key = "${azurerm_storage_account.main.primary_access_key}" 77 | } 78 | } 79 | 80 | container { 81 | name = "sidecar" 82 | image = "microsoft/aci-tutorial-sidecar" 83 | cpu = "0.5" 84 | memory = "1.5" 85 | } 86 | 87 | tags { 88 | environment = "testing" 89 | } 90 | } 91 | ``` 92 | 93 | ### Terraform Init and Plan 94 | 95 | Running an `init` should look something like this: 96 | 97 | ```sh 98 | terraform init 99 | 100 | ... 101 | 102 | Terraform has been successfully initialized! 103 | ``` 104 | 105 | Running a `plan` should look something like this: 106 | 107 | ```sh 108 | terraform plan 109 | Terraform will perform the following actions: 110 | 111 | + azurerm_container_group.main 112 | name: "aci-helloworld" 113 | ... 114 | 115 | + azurerm_resource_group.main 116 | name: "aci-helloworld" 117 | ... 118 | 119 | + azurerm_storage_account.main 120 | name: "" 121 | ... 122 | 123 | + azurerm_storage_share.main 124 | name: "aci-test-share" 125 | ... 126 | 127 | 128 | Plan: 4 to add, 0 to change, 0 to destroy. 129 | ``` 130 | 131 | ### Terraform Apply 132 | 133 | Running an `apply` should look just like a plan except you are prompted for approval to apply. 134 | Type 'yes' and let Terraform build your infrastructure. 135 | 136 | ### Navigate to the Azure Portal 137 | 138 | Open a browser and navigate to the the [Azure Portal](https://portal.azure.com) and you should see your resource group and its resources. 139 | ![](../../img/2018-05-07-18-08-30.png) 140 | 141 | ### Find the Full Qualified Domain Name 142 | 143 | Click into the Azure Container Instance and take note of its FQDN. 144 | ![](../../img/2018-05-07-18-11-33.png) 145 | 146 | ### Navigate to the Web App 147 | 148 | Navigate to that URL and you should see the following: 149 | ![](../../img/2018-05-07-18-13-28.png) 150 | 151 | ### View the Logs 152 | Back in the Azure Portal, navigate to the Azure Container Instance and view its logs by clicking on the "Containers" tab: 153 | ![](../../img/2018-05-07-18-20-56.png) 154 | 155 | Wait a few seconds and refresh the logs, you should see more requests due to the sidecar container. 156 | 157 | ## A Step Further (optional) 158 | 159 | Azure Container Instances also support windows containers! 160 | 161 | ### Create a Windows Container 162 | 163 | Create another resource by adding the following to your existing `main.tf` file: 164 | 165 | ```hcl 166 | resource "azurerm_container_group" "windows" { 167 | name = "aci-iis" 168 | location = "${azurerm_resource_group.main.location}" 169 | resource_group_name = "${azurerm_resource_group.main.name}" 170 | ip_address_type = "public" 171 | dns_name_label = "aci-iis-${random_pet.main.id}" 172 | os_type = "windows" 173 | 174 | container { 175 | name = "dotnetsample" 176 | image = "microsoft/iis" 177 | cpu = "0.5" 178 | memory = "1.5" 179 | port = "80" 180 | } 181 | 182 | tags { 183 | environment = "testing" 184 | } 185 | } 186 | ``` 187 | 188 | ### Run Terraform Workflow 189 | 190 | Running an `init`, `plan`, and `apply` should yield another Container Instance. 191 | 192 | Navigating back to the Azure Portal to get the FQDN and following that URL should get you the very familiar IIS default sites page: 193 | ![](../../img/2018-05-07-18-29-10.png) 194 | 195 | ## Cleanup 196 | 197 | Run a `terraform destroy` when you are done exploring. 198 | 199 | ## Advanced areas to explore 200 | 201 | 1. What do you think will happen if you try to combine the Azure Container Instances above (Linux and Windows) into one? 202 | 2. Replicate the Terraform above using a single Azure CLI command. Which is easier? 203 | 204 | ## Resources 205 | 206 | - [Azurerm Container Group Docs](https://www.terraform.io/docs/providers/azurerm/r/container_group.html) 207 | - [Azure Container Instances](https://azure.microsoft.com/en-us/services/container-instances) 208 | -------------------------------------------------------------------------------- /challenges/03-azurevm/README.md: -------------------------------------------------------------------------------- 1 | # 03 - Azure Virtual Machine 2 | 3 | ## Expected Outcome 4 | 5 | In this challenge, you will create a Azure Virtual Machine running Windows Server. 6 | 7 | You will gradually add Terraform configuration to build all the resources needed to be able to login to the Azure Virtual Machine. 8 | 9 | The resources you will use in this challenge: 10 | 11 | - Resource Group 12 | - Virtual Network 13 | - Subnet 14 | - Network Interface 15 | - Virtual Machine 16 | - Public IP Address 17 | 18 | ## How to 19 | 20 | ### Create the base Terraform Configuration 21 | 22 | We will start with a few of the basic resources needed. 23 | 24 | From the Cloud Shell, change directory into a folder specific to this challenge. If you created the scaffolding in Challenge 00, then then you can use the command `cd ~/AzureWorkChallenges/challenge03/`. 25 | 26 | Create a `main.tf` file to hold our configuration. 27 | 28 | ### Create Variables 29 | 30 | Create a few variables that will help keep our code clean: 31 | 32 | ```hcl 33 | variable "name" { 34 | default = "challenge03" 35 | } 36 | 37 | variable "location" { 38 | default = "eastus" 39 | } 40 | ``` 41 | 42 | ### Create a Resource Group 43 | 44 | Now create a Resource Group to contain all of our infrastructure using the variables to interpolate the parameters: 45 | 46 | ```hcl 47 | resource "azurerm_resource_group" "main" { 48 | name = "${var.name}-rg" 49 | location = "${var.location}" 50 | } 51 | ``` 52 | 53 | ### Create Virtual Networking 54 | 55 | In order to create an Azure Virtual Machine we need to create a network in which to place it. 56 | 57 | Create a Virtual Network and Subnet using a basic CIDR block to allocate an IP block: 58 | 59 | ```hcl 60 | resource "azurerm_virtual_network" "main" { 61 | name = "${var.name}-vnet" 62 | address_space = ["10.0.0.0/16"] 63 | location = "${azurerm_resource_group.main.location}" 64 | resource_group_name = "${azurerm_resource_group.main.name}" 65 | } 66 | 67 | resource "azurerm_subnet" "main" { 68 | name = "${var.name}-subnet" 69 | resource_group_name = "${azurerm_resource_group.main.name}" 70 | virtual_network_name = "${azurerm_virtual_network.main.name}" 71 | address_prefix = "10.0.1.0/24" 72 | } 73 | ``` 74 | 75 | > Notice that we use the available metadata from the `azurerm_resource_group.main` resource to populate the parameters of other resources. 76 | 77 | ### Run Terraform Workflow 78 | 79 | Run `terraform init` since this is the first time we are running Terraform from this directory. 80 | 81 | Run `terraform plan` where you should see the plan of two new resources, namely the Resource Group and the Virtual Network. 82 | 83 |
View Output 84 |

85 | 86 | ```sh 87 | $ terraform plan 88 | Refreshing Terraform state in-memory prior to plan... 89 | The refreshed state will be used to calculate this plan, but will not be 90 | persisted to local or remote state storage. 91 | 92 | 93 | ------------------------------------------------------------------------ 94 | 95 | An execution plan has been generated and is shown below. 96 | Resource actions are indicated with the following symbols: 97 | + create 98 | 99 | Terraform will perform the following actions: 100 | 101 | + azurerm_resource_group.main 102 | id: 103 | location: "eastus" 104 | name: "challenge03-rg" 105 | tags.%: 106 | 107 | + azurerm_subnet.main 108 | id: 109 | address_prefix: "10.0.1.0/24" 110 | ip_configurations.#: 111 | name: "challenge03-subnet" 112 | resource_group_name: "challenge03-rg" 113 | virtual_network_name: "challenge03-vnet" 114 | 115 | + azurerm_virtual_network.main 116 | id: 117 | address_space.#: "1" 118 | address_space.0: "10.0.0.0/16" 119 | location: "eastus" 120 | name: "challenge03-vnet" 121 | resource_group_name: "challenge03-rg" 122 | subnet.#: 123 | tags.%: 124 | 125 | 126 | Plan: 3 to add, 0 to change, 0 to destroy. 127 | 128 | ------------------------------------------------------------------------ 129 | 130 | Note: You didn't specify an "-out" parameter to save this plan, so Terraform 131 | can't guarantee that exactly these actions will be performed if 132 | "terraform apply" is subsequently run. 133 | ``` 134 | 135 |

136 |
137 | 138 | If your plan looks good, go ahead and run `terraform apply` and type "yes" to confirm you want to apply. 139 | When it completes you should see: 140 | 141 | ```sh 142 | Apply complete! Resources: 3 added, 0 changed, 0 destroyed. 143 | ``` 144 | 145 | ### Create the Azure Virtual Machine 146 | 147 | Now that we have base networking in place, we will add a Network Interface and Virtual Machine. 148 | We will create a VM with an Azure Marketplace Image for Windows Server 2016 Datacenter. 149 | 150 | Create the Network Interface resource: 151 | 152 | ```hcl 153 | resource "azurerm_network_interface" "main" { 154 | name = "${var.name}-nic" 155 | location = "${azurerm_resource_group.main.location}" 156 | resource_group_name = "${azurerm_resource_group.main.name}" 157 | 158 | ip_configuration { 159 | name = "config1" 160 | subnet_id = "${azurerm_subnet.main.id}" 161 | private_ip_address_allocation = "dynamic" 162 | } 163 | } 164 | ``` 165 | 166 | Create the Virtual Machine resource: 167 | 168 | ```hcl 169 | resource "azurerm_virtual_machine" "main" { 170 | name = "${var.name}-vm" 171 | location = "${azurerm_resource_group.main.location}" 172 | resource_group_name = "${azurerm_resource_group.main.name}" 173 | network_interface_ids = ["${azurerm_network_interface.main.id}"] 174 | vm_size = "Standard_A2_v2" 175 | 176 | storage_image_reference { 177 | publisher = "MicrosoftWindowsServer" 178 | offer = "WindowsServer" 179 | sku = "2016-Datacenter" 180 | version = "latest" 181 | } 182 | 183 | storage_os_disk { 184 | name = "${var.name}vm-osdisk" 185 | caching = "ReadWrite" 186 | create_option = "FromImage" 187 | managed_disk_type = "Standard_LRS" 188 | } 189 | 190 | os_profile { 191 | computer_name = "${var.name}vm" 192 | admin_username = "testadmin" 193 | admin_password = "Password1234!" 194 | } 195 | 196 | os_profile_windows_config {} 197 | } 198 | ``` 199 | 200 | Take note of the OS image: 201 | 202 | ```hcl 203 | storage_image_reference { 204 | publisher = "MicrosoftWindowsServer" 205 | offer = "WindowsServer" 206 | sku = "2016-Datacenter" 207 | version = "latest" 208 | } 209 | ``` 210 | 211 | Run a plan and apply to create both these resources. 212 | 213 | ### Add a Public IP 214 | 215 | At this point you should have a running Virtual Machine in Azure running Windows Server, however you have no way to access it. To do this we must do two things, create the Public IP Resource and configure the Network Interface to use it. 216 | 217 | Create a Public IP Address that will assign an IP address: 218 | 219 | ```hcl 220 | resource "azurerm_public_ip" "main" { 221 | name = "${var.name}-pubip" 222 | location = "${azurerm_resource_group.main.location}" 223 | resource_group_name = "${azurerm_resource_group.main.name}" 224 | allocation_method = "Static" 225 | } 226 | ``` 227 | 228 | Update the IP Configuration parameter of the Network Interface to attach the Public IP: 229 | 230 | ```hcl 231 | resource "azurerm_network_interface" "main" { 232 | ... 233 | 234 | ip_configuration { 235 | ... 236 | public_ip_address_id = "${azurerm_public_ip.main.id}" 237 | } 238 | } 239 | ``` 240 | 241 | ### Terraform Plan 242 | 243 | Running `terraform plan` should contain something like the following: 244 | 245 | ```sh 246 | ~ azurerm_network_interface.main 247 | ip_configuration.0.public_ip_address_id: "" => "${azurerm_public_ip.main.id}" 248 | 249 | + azurerm_public_ip.main 250 | id: 251 | fqdn: 252 | ip_address: 253 | location: "eastus" 254 | name: "challenge03-pubip" 255 | public_ip_address_allocation: "static" 256 | resource_group_name: "challenge03-rg" 257 | sku: "Basic" 258 | tags.%: 259 | 260 | 261 | Plan: 1 to add, 1 to change, 0 to destroy. 262 | ``` 263 | 264 | > Notice that there is a new resource being added and one being updated. 265 | 266 | Run `terraform apply` to apply the changes. 267 | 268 | ### Outputs 269 | 270 | You now have all the infrastructure in place and can now Remote Desktop into the Windows Server VM we just stood up. 271 | 272 | But wait, the Public IP was dynamically created, how do I access it? 273 | 274 | You could check the value in the Azure Portal, however let's instead add an output to get that information. 275 | 276 | Add the following output: 277 | 278 | ```hcl 279 | output "private-ip" { 280 | value = "${azurerm_network_interface.main.private_ip_address}" 281 | description = "Private IP Address" 282 | } 283 | 284 | output "public-ip" { 285 | value = "${azurerm_public_ip.main.ip_address}" 286 | description = "Public IP Address" 287 | } 288 | ``` 289 | 290 | Now run a `terraform refresh`, which will refresh your state file with the real-world infrastructure and resolve the new outputs you just created. 291 | 292 | ```sh 293 | $ terraform refresh 294 | azurerm_resource_group.main: Refreshing state... (ID: /subscriptions/.../resourceGroups/challenge03-rg) 295 | azurerm_virtual_network.main: Refreshing state... (ID: /subscriptions/.../virtualNetworks/challenge03-vnet) 296 | azurerm_public_ip.main: Refreshing state... (ID: /subscriptions/.../publicIPAddresses/challenge03-pubip) 297 | azurerm_subnet.main: Refreshing state... (ID: /subscriptions/.../subnets/challenge03-subnet) 298 | azurerm_network_interface.main: Refreshing state... (ID: /subscriptions/.../networkInterfaces/challenge03-nic) 299 | azurerm_virtual_machine.main: Refreshing state... (ID: /subscriptions/.../virtualMachines/challenge03-vm) 300 | 301 | Outputs: 302 | 303 | private-ip = 10.0.1.4 304 | public-ip = 168.61.55.117 305 | ``` 306 | 307 | > Note: you can also run `terraform output` to see just these outputs without having to run refresh again. 308 | 309 | ### Remote Desktop (optional) 310 | 311 | Using the Public IP output value, Remote Desktop into the Virtual Machine to verify connectivity. 312 | 313 | ![](../../img/2018-05-09-14-55-42.png) 314 | 315 | Success! You have now stood up a Virtual Machine in Azure using Terraform! 316 | 317 | ### Clean up 318 | 319 | When you are done, run `terraform destroy` to remove everything we created: 320 | 321 | ```sh 322 | terraform destroy 323 | azurerm_resource_group.main: Refreshing state... (ID: /subscriptions/.../resourceGroups/challenge03-rg) 324 | azurerm_public_ip.main: Refreshing state... (ID: /subscriptions/.../publicIPAddresses/challenge03-pubip) 325 | azurerm_virtual_network.main: Refreshing state... (ID: /subscriptions/.../virtualNetworks/challenge03-vnet) 326 | azurerm_subnet.main: Refreshing state... (ID: /subscriptions/.../subnets/challenge03-subnet) 327 | azurerm_network_interface.main: Refreshing state... (ID: /subscriptions/.../networkInterfaces/challenge03-nic) 328 | azurerm_virtual_machine.main: Refreshing state... (ID: /subscriptions/.../virtualMachines/challenge03-vm) 329 | 330 | An execution plan has been generated and is shown below. 331 | Resource actions are indicated with the following symbols: 332 | - destroy 333 | 334 | Terraform will perform the following actions: 335 | 336 | - azurerm_network_interface.main 337 | 338 | - azurerm_public_ip.main 339 | 340 | - azurerm_resource_group.main 341 | 342 | - azurerm_subnet.main 343 | 344 | - azurerm_virtual_machine.main 345 | 346 | - azurerm_virtual_network.main 347 | 348 | 349 | Plan: 0 to add, 0 to change, 6 to destroy. 350 | 351 | Do you really want to destroy? 352 | Terraform will destroy all your managed infrastructure, as shown above. 353 | There is no undo. Only 'yes' will be accepted to confirm. 354 | ... 355 | ``` 356 | 357 | ## Advanced areas to explore 358 | 359 | 1. Extract secrets into required variables. 360 | 1. Add a data disk. 361 | 1. Add a DNS Label to the Public IP Address. 362 | 1. Search for Marketplace Images. (hint: use the Azurel CLI and start with `az vm image -h`) 363 | 364 | ## Resources 365 | 366 | - [Azure Resource Group](https://www.terraform.io/docs/providers/azurerm/r/resource_group.html) 367 | - [Azure Virtual Network](https://www.terraform.io/docs/providers/azurerm/r/virtual_network.html) 368 | - [Azure Subnet](https://www.terraform.io/docs/providers/azurerm/r/subnet.html) 369 | - [Azure Network Interface](https://www.terraform.io/docs/providers/azurerm/r/network_interface.html) 370 | - [Azure Virtual Machine](https://www.terraform.io/docs/providers/azurerm/r/virtual_machine.html) 371 | - [Public IP Address](https://www.terraform.io/docs/providers/azurerm/r/public_ip.html) 372 | -------------------------------------------------------------------------------- /challenges/04-terraformcount/README.md: -------------------------------------------------------------------------------- 1 | # 04 - Terraform Count 2 | 3 | ## Expected Outcome 4 | 5 | In this challenge, you will take what you did in Challenge 03 and expand to take a count variable. 6 | 7 | Be aware that if you did not destroy the infrastructure from Challenge 03 you may run into resource naming conflicts (namely "Resource already exists"). 8 | 9 | ## How to 10 | 11 | ### Copy Terraform Configuration 12 | 13 | From the Cloud Shell, change directory into a folder specific to this challenge. If you created the scaffolding in Challenge 00, then then you can use the command `cd ~/AzureWorkChallenges/challenge04/`. 14 | 15 | Copy the `main.tf` file from challenge 03 into the current directory. 16 | 17 | Be sure to update the value of the `name` variable: 18 | 19 | ```hcl 20 | variable "name" { 21 | default = "challenge04" 22 | } 23 | ``` 24 | 25 | ### Add a count variable 26 | 27 | Create a new variable called `vmcount` and default it to `1`. 28 | 29 | ```hcl 30 | variable "vmcount" { 31 | default = 1 32 | } 33 | ``` 34 | 35 | ### Update Existing Resources 36 | 37 | Not every resource needs to scale as the number of VM's increase. 38 | 39 | The Resource Group, Virtual Network and Subnet will not change. 40 | 41 | We will have to scale the Network Interface, Public IP, and VM resources. 42 | 43 | Each of these resources you will need make these changes: 44 | 45 | - Add the count variable 46 | 47 | ```hcl 48 | count = "${var.vmcount}" 49 | ``` 50 | 51 | - Update the resource name to include the count index, for example the VM resource: 52 | 53 | ```hcl 54 | name = "${var.name}-vm${count.index}" 55 | ``` 56 | 57 | - Update the OS Disk Name on the VM resource: 58 | 59 | ```hcl 60 | storage_os_disk { 61 | name = "${var.name}vm${count.index}-osdisk" 62 | ... 63 | } 64 | ``` 65 | 66 | - Update the computer name on the VM resource. 67 | 68 | ```hcl 69 | os_profile { 70 | computer_name = "${var.name}vm${count.index}" 71 | ... 72 | } 73 | ``` 74 | 75 | - Update the ID reference for the Virtual Machine: 76 | 77 | ```hcl 78 | network_interface_ids = ["${element(azurerm_network_interface.main.*.id, count.index)}"] 79 | ``` 80 | 81 | - Update the Public IP block: 82 | 83 | ```hcl 84 | resource "azurerm_public_ip" "main" { 85 | name = "${var.name}-pubip${count.index}" 86 | ... 87 | count = "${var.vmcount}" 88 | } 89 | ``` 90 | 91 | - Update the Public IP ID reference for the Network Interface: 92 | 93 | ```hcl 94 | public_ip_address_id = "${element(azurerm_public_ip.main.*.id, count.index)}" 95 | ``` 96 | 97 | - Update the Private IP outputs to display an array of IPs: 98 | 99 | ```hcl 100 | value = "${azurerm_network_interface.main.*.private_ip_address}" 101 | ``` 102 | 103 | - Update the Public IP outputs to display an array of IPs: 104 | 105 | ```hcl 106 | value = "${azurerm_public_ip.main.*.ip_address}" 107 | ``` 108 | 109 | ### Run a plan 110 | 111 | If all the changes above have been made without error, runnning `terraform init` and `terraform plan` should execute without any errors. 112 | 113 | The plan should end with: 114 | 115 | ```sh 116 | Plan: 6 to add, 0 to change, 0 to destroy. 117 | ``` 118 | 119 | Now investigate this plan in more detail and you will notice that names have been set using the count index: 120 | 121 | ```sh 122 | ... 123 | + azurerm_virtual_machine.module[0] 124 | ... 125 | name: "challenge04-vm0" 126 | ... 127 | os_profile.3613624746.computer_name: "challenge04vm0" 128 | ... 129 | 130 | ``` 131 | 132 | ### Update Count 133 | 134 | Set the default value of the count to `2`. 135 | Before running a plan consider the following questions: 136 | 137 | - How many resources do expect the plan to show? 138 | - What will the outputs look like? 139 | 140 | Your plan should look similar to the following 141 |
View Output 142 |

143 | 144 | ```sh 145 | $ terraform plan 146 | Refreshing Terraform state in-memory prior to plan... 147 | The refreshed state will be used to calculate this plan, but will not be 148 | persisted to local or remote state storage. 149 | 150 | 151 | ------------------------------------------------------------------------ 152 | 153 | An execution plan has been generated and is shown below. 154 | Resource actions are indicated with the following symbols: 155 | + create 156 | 157 | Terraform will perform the following actions: 158 | 159 | + azurerm_network_interface.main[0] 160 | id: 161 | applied_dns_servers.#: 162 | dns_servers.#: 163 | enable_accelerated_networking: "false" 164 | enable_ip_forwarding: "false" 165 | internal_dns_name_label: 166 | internal_fqdn: 167 | ip_configuration.#: "1" 168 | ip_configuration.0.application_gateway_backend_address_pools_ids.#: 169 | ip_configuration.0.application_security_group_ids.#: 170 | ip_configuration.0.load_balancer_backend_address_pools_ids.#: 171 | ip_configuration.0.load_balancer_inbound_nat_rules_ids.#: 172 | ip_configuration.0.name: "config1" 173 | ip_configuration.0.primary: 174 | ip_configuration.0.private_ip_address: 175 | ip_configuration.0.private_ip_address_allocation: "dynamic" 176 | ip_configuration.0.public_ip_address_id: "${element(azurerm_public_ip.main.*.id, count.index)}" 177 | ip_configuration.0.subnet_id: "${azurerm_subnet.main.id}" 178 | location: "eastus" 179 | mac_address: 180 | name: "challenge04-nic0" 181 | private_ip_address: 182 | private_ip_addresses.#: 183 | resource_group_name: "challenge04-rg" 184 | tags.%: 185 | virtual_machine_id: 186 | 187 | + azurerm_network_interface.main[1] 188 | id: 189 | applied_dns_servers.#: 190 | dns_servers.#: 191 | enable_accelerated_networking: "false" 192 | enable_ip_forwarding: "false" 193 | internal_dns_name_label: 194 | internal_fqdn: 195 | ip_configuration.#: "1" 196 | ip_configuration.0.application_gateway_backend_address_pools_ids.#: 197 | ip_configuration.0.application_security_group_ids.#: 198 | ip_configuration.0.load_balancer_backend_address_pools_ids.#: 199 | ip_configuration.0.load_balancer_inbound_nat_rules_ids.#: 200 | ip_configuration.0.name: "config1" 201 | ip_configuration.0.primary: 202 | ip_configuration.0.private_ip_address: 203 | ip_configuration.0.private_ip_address_allocation: "dynamic" 204 | ip_configuration.0.public_ip_address_id: "${element(azurerm_public_ip.main.*.id, count.index)}" 205 | ip_configuration.0.subnet_id: "${azurerm_subnet.main.id}" 206 | location: "eastus" 207 | mac_address: 208 | name: "challenge04-nic1" 209 | private_ip_address: 210 | private_ip_addresses.#: 211 | resource_group_name: "challenge04-rg" 212 | tags.%: 213 | virtual_machine_id: 214 | 215 | + azurerm_public_ip.main[0] 216 | id: 217 | fqdn: 218 | ip_address: 219 | location: "eastus" 220 | name: "challenge04-pubip0" 221 | public_ip_address_allocation: "static" 222 | resource_group_name: "challenge04-rg" 223 | sku: "Basic" 224 | tags.%: 225 | 226 | + azurerm_public_ip.main[1] 227 | id: 228 | fqdn: 229 | ip_address: 230 | location: "eastus" 231 | name: "challenge04-pubip1" 232 | public_ip_address_allocation: "static" 233 | resource_group_name: "challenge04-rg" 234 | sku: "Basic" 235 | tags.%: 236 | 237 | + azurerm_resource_group.main 238 | id: 239 | location: "eastus" 240 | name: "challenge04-rg" 241 | tags.%: 242 | 243 | + azurerm_subnet.main 244 | id: 245 | address_prefix: "10.0.1.0/24" 246 | ip_configurations.#: 247 | name: "challenge04-subnet" 248 | resource_group_name: "challenge04-rg" 249 | virtual_network_name: "challenge04-vnet" 250 | 251 | + azurerm_virtual_machine.main[0] 252 | id: 253 | availability_set_id: 254 | delete_data_disks_on_termination: "false" 255 | delete_os_disk_on_termination: "false" 256 | identity.#: 257 | location: "eastus" 258 | name: "challenge04-vm0" 259 | network_interface_ids.#: 260 | os_profile.#: "1" 261 | os_profile.1750279281.admin_password: 262 | os_profile.1750279281.admin_username: "testadmin" 263 | os_profile.1750279281.computer_name: "challenge04vm0" 264 | os_profile.1750279281.custom_data: 265 | os_profile_windows_config.#: "1" 266 | os_profile_windows_config.429474957.additional_unattend_config.#: "0" 267 | os_profile_windows_config.429474957.enable_automatic_upgrades: "false" 268 | os_profile_windows_config.429474957.provision_vm_agent: "false" 269 | os_profile_windows_config.429474957.winrm.#: "0" 270 | resource_group_name: "challenge04-rg" 271 | storage_image_reference.#: "1" 272 | storage_image_reference.3904372903.id: "" 273 | storage_image_reference.3904372903.offer: "WindowsServer" 274 | storage_image_reference.3904372903.publisher: "MicrosoftWindowsServer" 275 | storage_image_reference.3904372903.sku: "2016-Datacenter" 276 | storage_image_reference.3904372903.version: "latest" 277 | storage_os_disk.#: "1" 278 | storage_os_disk.0.caching: "ReadWrite" 279 | storage_os_disk.0.create_option: "FromImage" 280 | storage_os_disk.0.disk_size_gb: 281 | storage_os_disk.0.managed_disk_id: 282 | storage_os_disk.0.managed_disk_type: "Standard_LRS" 283 | storage_os_disk.0.name: "challenge04vm0-osdisk" 284 | tags.%: 285 | vm_size: "Standard_A2_v2" 286 | 287 | + azurerm_virtual_machine.main[1] 288 | id: 289 | availability_set_id: 290 | delete_data_disks_on_termination: "false" 291 | delete_os_disk_on_termination: "false" 292 | identity.#: 293 | location: "eastus" 294 | name: "challenge04-vm1" 295 | network_interface_ids.#: 296 | os_profile.#: "1" 297 | os_profile.1900549424.admin_password: 298 | os_profile.1900549424.admin_username: "testadmin" 299 | os_profile.1900549424.computer_name: "challenge04vm1" 300 | os_profile.1900549424.custom_data: 301 | os_profile_windows_config.#: "1" 302 | os_profile_windows_config.429474957.additional_unattend_config.#: "0" 303 | os_profile_windows_config.429474957.enable_automatic_upgrades: "false" 304 | os_profile_windows_config.429474957.provision_vm_agent: "false" 305 | os_profile_windows_config.429474957.winrm.#: "0" 306 | resource_group_name: "challenge04-rg" 307 | storage_image_reference.#: "1" 308 | storage_image_reference.3904372903.id: "" 309 | storage_image_reference.3904372903.offer: "WindowsServer" 310 | storage_image_reference.3904372903.publisher: "MicrosoftWindowsServer" 311 | storage_image_reference.3904372903.sku: "2016-Datacenter" 312 | storage_image_reference.3904372903.version: "latest" 313 | storage_os_disk.#: "1" 314 | storage_os_disk.0.caching: "ReadWrite" 315 | storage_os_disk.0.create_option: "FromImage" 316 | storage_os_disk.0.disk_size_gb: 317 | storage_os_disk.0.managed_disk_id: 318 | storage_os_disk.0.managed_disk_type: "Standard_LRS" 319 | storage_os_disk.0.name: "challenge04vm1-osdisk" 320 | tags.%: 321 | vm_size: "Standard_A2_v2" 322 | 323 | + azurerm_virtual_network.main 324 | id: 325 | address_space.#: "1" 326 | address_space.0: "10.0.0.0/16" 327 | location: "eastus" 328 | name: "challenge04-vnet" 329 | resource_group_name: "challenge04-rg" 330 | subnet.#: 331 | tags.%: 332 | 333 | 334 | Plan: 9 to add, 0 to change, 0 to destroy. 335 | 336 | ------------------------------------------------------------------------ 337 | 338 | Note: You didn't specify an "-out" parameter to save this plan, so Terraform 339 | can't guarantee that exactly these actions will be performed if 340 | "terraform apply" is subsequently run. 341 | ``` 342 | 343 |

344 |
345 | 346 | Run `terraform apply` to create all the infrastructure. 347 | 348 | ### Azure Portal 349 | 350 | In the Azure Portal, view all the resources in the `challenge04-rg` Resource Group. 351 | 352 | How important do think naming is? 353 | 354 | How can Terraform help with keeping things consistent? 355 | 356 | ### Clean up 357 | 358 | Run `terraform destroy` to remove everything we created. 359 | 360 | ## Advanced areas to explore 361 | 362 | 1. Add an Azure Load Balancer. 363 | 2. Add tags to the Virtual Machine and then use the `-target` option to target only a single resource. 364 | 365 | ## Resources 366 | 367 | - [Azure Load Balancer](https://www.terraform.io/docs/providers/azurerm/r/loadbalancer.html) 368 | - [Resource Targeting](https://www.terraform.io/docs/commands/plan.html#resource-targeting) 369 | -------------------------------------------------------------------------------- /challenges/05-terraformmodules/README.md: -------------------------------------------------------------------------------- 1 | # 05 - Terraform Modules 2 | 3 | ## Expected Outcome 4 | 5 | In this challenge, you will create a module to contain a scalable virtual machine deployment, then create an environment where you will call the module. 6 | 7 | ## How to 8 | 9 | ### Create Folder Structure 10 | 11 | From the Cloud Shell, change directory into a folder specific to this challenge. If you created the scaffolding in Challenge 00, then then you can use the command `cd ~/AzureWorkChallenges/challenge05/`. 12 | 13 | In order to organize your code, create the following folder structure with `main.tf` files. 14 | 15 | ```sh 16 | ├── environments 17 | │ └── dev 18 | │ └── main.tf 19 | └── modules 20 | └── my_virtual_machine 21 | └── main.tf 22 | ``` 23 | 24 | ### Create the Module 25 | 26 | Inside the `my_virtual_machine` module folder copy over the terraform configuration from challenge 04. 27 | 28 | ### Create Variables 29 | 30 | Extract name, vm size, username and password into variables without defaults. 31 | 32 | This will result in them being required. 33 | 34 | ```hcl 35 | variable "name" {} 36 | variable "vm_size" {} 37 | variable "username" {} 38 | variable "password" {} 39 | ``` 40 | 41 | > Extra credit: How many other variables can you extract? 42 | 43 | ### Create the Environment 44 | 45 | Change your working directory to the `environments/dev` folder. 46 | 47 | Update main.tf to declare your module, it could look similar to this: 48 | 49 | ```hcl 50 | variable "username" {} 51 | variable "password" {} 52 | 53 | module "myawesomewindowsvm" { 54 | source = "../../modules/my_virtual_machine" 55 | name = "awesomeapp" 56 | } 57 | ``` 58 | 59 | > Notice the relative module sourcing. 60 | 61 | ### Terraform Init 62 | 63 | Run `terraform init`. 64 | 65 | ```sh 66 | Initializing modules... 67 | - module.myawesomewindowsvm 68 | Getting source "../../modules/my_virtual_machine" 69 | 70 | Error: module "myawesomewindowsvm": missing required argument "name" 71 | Error: module "myawesomewindowsvm": missing required argument "vm_size" 72 | Error: module "myawesomewindowsvm": missing required argument "username" 73 | Error: module "myawesomewindowsvm": missing required argument "password" 74 | ``` 75 | 76 | We have a problem! We didn't set required variables for our module. 77 | 78 | Update the `main.tf` file: 79 | 80 | ```hcl 81 | module "myawesomewindowsvm" { 82 | source = "../../modules/my_virtual_machine" 83 | name = "awesomeapp" 84 | vm_size = "Standard_A2_v2" 85 | username = "${var.username}" 86 | password = "${var.password}" 87 | } 88 | ``` 89 | 90 | Run `terraform init` again, this time there should not be any errors. 91 | 92 | ## Terraform Plan 93 | 94 | Run `terraform plan` and you should see your linux VM built from your module. 95 | 96 | ```sh 97 | + module.myawesomewindowsvm.azurerm_resource_group.module 98 | id: 99 | location: "centralus" 100 | name: "awesomeapp-rg" 101 | 102 | ... 103 | 104 | Plan: 6 to add, 0 to change, 0 to destroy. 105 | ``` 106 | 107 | ## Add Another Module 108 | 109 | Add another `module` block describing another set of Virtual Machines: 110 | 111 | ```hcl 112 | module "differentwindowsvm" { 113 | source = "../../modules/my_virtual_machine" 114 | name = "differentapp" 115 | vm_size = "Standard_A2_v2" 116 | username = "${var.username}" 117 | password = "${var.password}" 118 | } 119 | ``` 120 | 121 | ## Scale a single module 122 | 123 | Set the count of your first module to 2 and rerun a plan. 124 | 125 | ```hcl 126 | ... 127 | vmcount = 2 128 | ... 129 | ``` 130 | 131 | Run a plan and observer that your first module can scale independently of the second one. 132 | 133 | ## Terraform Plan 134 | 135 | Since we added another module call, we must run `terraform init` again before running `terraform plan`. 136 | 137 | We should see twice as much infrastructure in our plan. 138 | 139 | ```sh 140 | + module.myawesomewindowsvm.azurerm_resource_group.module 141 | id: 142 | location: "centralus" 143 | name: "awesomeapp-rg" 144 | 145 | ... 146 | 147 | + module.differentlinuxvm.azurerm_resource_group.module 148 | id: 149 | location: "centralus" 150 | name: "differentapp-rg" 151 | 152 | ... 153 | 154 | Plan: 12 to add, 0 to change, 0 to destroy. 155 | 156 | ``` 157 | 158 | ## More Variables 159 | 160 | In your `environments/dev/main.tf` file we can see some duplication and secrets we do not want to store in configuration. 161 | 162 | Add two variables to your environment `main.tf` file for username and password. 163 | 164 | Create a new file and name it `terraform.tfvars` that will contain our secrets and automatically loaded when we run a `plan`. 165 | 166 | ```hcl 167 | username = "testadmin" 168 | password = "Password1234!" 169 | ``` 170 | 171 | ## Terraform Plan 172 | 173 | Run `terraform plan` and verify that your plan succeeds and looks the same. 174 | 175 | ## Advanced areas to explore 176 | 177 | 1. Use environment variables to load your secrets. 178 | 1. Add a reference to the Public Terraform Module for [Azure Compute](https://registry.terraform.io/modules/Azure/compute/azurerm) 179 | 180 | ## Resources 181 | 182 | - [Using Terraform Modules](https://www.terraform.io/docs/modules/usage.html) 183 | - [Source Terraform Modiules](https://www.terraform.io/docs/modules/sources.html) 184 | - [Public Module Registry](https://www.terraform.io/docs/registry/index.html) 185 | -------------------------------------------------------------------------------- /challenges/06-publicmoduleregistry/README.md: -------------------------------------------------------------------------------- 1 | # 06 - Public Module Registry 2 | 3 | ## Expected Outcome 4 | 5 | In this challenge, you will take a look at the public [Module Registry](https://registry.terraform.io/) and create a Virtual Machine from verified ![](../../img/2018-05-14-07-27-11.png) Public Modules. 6 | 7 | ## How to 8 | 9 | ### Navigate the Public Module Registry 10 | 11 | Open your browser and navigate to the [Module Registry](https://registry.terraform.io/). 12 | 13 | Search for "Compute" which will yield all compute resources in the registry. 14 | 15 | Now Filter By 'azurerm' which should give you (among others) the Microsoft Azure Compute Module. 16 | 17 | If you are having issues locating the module, you can find it directly at [https://registry.terraform.io/modules/Azure/compute/azurerm/1.1.7](https://registry.terraform.io/modules/Azure/compute/azurerm/1.1.7). 18 | 19 | Search again for "Networking" and apply the same Filter By, which should give you the Microsoft Azure Networking Module. 20 | 21 | If you are having issues locating the module, you can find it directly at [https://registry.terraform.io/modules/Azure/network/azurerm/2.0.0](https://registry.terraform.io/modules/Azure/network/azurerm/2.0.0). 22 | 23 | ### Create Terraform Configuration 24 | 25 | From the Cloud Shell, change directory into a folder specific to this challenge. If you created the scaffolding in Challenge 00, then then you can use the command `cd ~/AzureWorkChallenges/challenge06/`. 26 | 27 | To create an Azure Virtual Machine we need the networking in place, to do so we will be using both the modules above. 28 | The Networking module will create the Virtual Network and Subnet, then the Compute module will use that subnet as an input to create its Virtual Machine. 29 | 30 | Create a `main.tf` file in this directory and add the networking module. 31 | 32 | The following will configure the module to create a single Virtual Network and a Subnet. 33 | 34 | ```hcl 35 | module "network" { 36 | source = "Azure/network/azurerm" 37 | version = "2.0.0" 38 | resource_group_name = "myapp-networking" 39 | location = "eastus" 40 | 41 | tags = { 42 | environment = "dev" 43 | } 44 | } 45 | ``` 46 | 47 | Run a `terraform init` and `terraform plan` to verify that all the resources look correct. 48 | 49 |
View Output 50 |

51 | 52 | ```sh 53 | An execution plan has been generated and is shown below. 54 | Resource actions are indicated with the following symbols: 55 | + create 56 | 57 | Terraform will perform the following actions: 58 | 59 | + module.network.azurerm_resource_group.network 60 | id: 61 | location: "eastus" 62 | name: "myapp-networking" 63 | tags.%: 64 | 65 | + module.network.azurerm_subnet.subnet 66 | id: 67 | address_prefix: "10.0.1.0/24" 68 | ip_configurations.#: 69 | name: "subnet1" 70 | resource_group_name: "myapp-networking" 71 | virtual_network_name: "acctvnet" 72 | 73 | + module.network.azurerm_virtual_network.vnet 74 | id: 75 | address_space.#: "1" 76 | address_space.0: "10.0.0.0/16" 77 | location: "eastus" 78 | name: "acctvnet" 79 | resource_group_name: "myapp-networking" 80 | subnet.#: 81 | tags.%: "1" 82 | tags.environment: "dev" 83 | 84 | 85 | Plan: 3 to add, 0 to change, 0 to destroy. 86 | ``` 87 | 88 |

89 |
90 | 91 | Run `terraform apply` to create the infrastructure. 92 | 93 | ### View Outputs 94 | 95 | The Public Registry contains a lot of information about the module. Navigate to the outputs tab for the [Networking Module](https://registry.terraform.io/modules/Azure/network/azurerm/2.0.0?tab=outputs). 96 | 97 | We can see the outputs we should expect and a short description of each of them. 98 | 99 | ![](../../img/2018-05-14-08-18-48.png) 100 | 101 | Now that we have the networking infrastructure applied, we can view the outputs with terraform by running `terraform output -module network`. 102 | 103 | > Note: Because we are using a module, the outputs are not available at the root module, hence the need to specify the `-module network` option. 104 | 105 | ```sh 106 | $ terraform output -module network 107 | vnet_address_space = [ 108 | 10.0.0.0/16 109 | ] 110 | vnet_id = /subscriptions/.../resourceGroups/myapp-networking/providers/Microsoft.Network/virtualNetworks/acctvnet 111 | vnet_location = eastus 112 | vnet_name = acctvnet 113 | vnet_subnets = [ 114 | /subscriptions/.../resourceGroups/myapp-networking/providers/Microsoft.Network/virtualNetworks/acctvnet/subnets/subnet1 115 | ] 116 | ``` 117 | 118 | ### Add Compute Module - Windows 119 | 120 | With Networking in place you can now add the Compute module to create a Windows Virtual Machine. 121 | 122 | ```hcl 123 | module "windowsservers" { 124 | source = "Azure/compute/azurerm" 125 | version = "1.1.7" 126 | resource_group_name = "myapp-compute-windows" 127 | location = "eastus" 128 | admin_password = "ComplxP@ssw0rd!" 129 | vm_os_simple = "WindowsServer" 130 | nb_public_ip = 0 131 | vnet_subnet_id = "${module.network.vnet_subnets[0]}" 132 | } 133 | ``` 134 | 135 | Run a `terraform init` and `terraform plan` to verify that all the resources look correct. 136 | 137 | When running a plan you may run into the following error: 138 | ![](../../img/2018-06-07-16-23-29.png) 139 | 140 | To get past this, simply run the `az login` command and follow the prompts. 141 | 142 | > Note: Take a minute to analyse why you needed to run another `terraform init` command before you could run a plan. 143 | 144 |
View Output 145 |

146 | 147 | ```sh 148 | An execution plan has been generated and is shown below. 149 | Resource actions are indicated with the following symbols: 150 | + create 151 | 152 | Terraform will perform the following actions: 153 | 154 | + module.windowsservers.azurerm_availability_set.vm 155 | id: 156 | location: "eastus" 157 | managed: "true" 158 | name: "myvm-avset" 159 | platform_fault_domain_count: "2" 160 | platform_update_domain_count: "2" 161 | resource_group_name: "myapp-compute" 162 | tags.%: 163 | 164 | + module.windowsservers.azurerm_network_interface.vm 165 | id: 166 | applied_dns_servers.#: 167 | dns_servers.#: 168 | enable_ip_forwarding: "false" 169 | internal_dns_name_label: 170 | internal_fqdn: 171 | ip_configuration.#: "1" 172 | ip_configuration.0.load_balancer_backend_address_pools_ids.#: 173 | ip_configuration.0.load_balancer_inbound_nat_rules_ids.#: 174 | ip_configuration.0.name: "ipconfig0" 175 | ip_configuration.0.primary: 176 | ip_configuration.0.private_ip_address: 177 | ip_configuration.0.private_ip_address_allocation: "dynamic" 178 | ip_configuration.0.public_ip_address_id: "${length(azurerm_public_ip.vm.*.id) > 0 ? element(concat(azurerm_public_ip.vm.*.id, list(\"\")), count.index) : \"\"}" 179 | ip_configuration.0.subnet_id: "/subscriptions/27e9ff76-ce7b-4176-b2bb-4d3f40e1c999/resourceGroups/myapp-networking/providers/Microsoft.Network/virtualNetworks/acctvnet/subnets/subnet1" 180 | location: "eastus" 181 | mac_address: 182 | name: "nic-myvm-0" 183 | network_security_group_id: "${azurerm_network_security_group.vm.id}" 184 | private_ip_address: 185 | private_ip_addresses.#: 186 | resource_group_name: "myapp-compute" 187 | tags.%: 188 | virtual_machine_id: 189 | 190 | + module.windowsservers.azurerm_network_security_group.vm 191 | id: 192 | location: "eastus" 193 | name: "myvm-3389-nsg" 194 | resource_group_name: "myapp-compute" 195 | security_rule.#: "1" 196 | security_rule.0.access: "Allow" 197 | security_rule.0.description: "Allow remote protocol in from all locations" 198 | security_rule.0.destination_address_prefix: "*" 199 | security_rule.0.destination_port_range: "3389" 200 | security_rule.0.direction: "Inbound" 201 | security_rule.0.name: "allow_remote_3389_in_all" 202 | security_rule.0.priority: "100" 203 | security_rule.0.protocol: "tcp" 204 | security_rule.0.source_address_prefix: "*" 205 | security_rule.0.source_port_range: "*" 206 | tags.%: 207 | 208 | + module.windowsservers.azurerm_public_ip.vm 209 | id: 210 | domain_name_label: "winsimplevmips" 211 | fqdn: 212 | ip_address: 213 | location: "eastus" 214 | name: "myvm-0-publicIP" 215 | public_ip_address_allocation: "dynamic" 216 | resource_group_name: "myapp-compute" 217 | tags.%: 218 | 219 | + module.windowsservers.azurerm_resource_group.vm 220 | id: 221 | location: "eastus" 222 | name: "myapp-compute" 223 | tags.%: "1" 224 | tags.source: "terraform" 225 | 226 | + module.windowsservers.azurerm_virtual_machine.vm-windows 227 | id: 228 | availability_set_id: "${azurerm_availability_set.vm.id}" 229 | boot_diagnostics.#: "1" 230 | boot_diagnostics.0.enabled: "false" 231 | delete_data_disks_on_termination: "false" 232 | delete_os_disk_on_termination: "false" 233 | location: "eastus" 234 | name: "myvm0" 235 | network_interface_ids.#: 236 | os_profile.#: "1" 237 | os_profile.249456377.admin_password: 238 | os_profile.249456377.admin_username: "azureuser" 239 | os_profile.249456377.computer_name: "myvm0" 240 | os_profile.249456377.custom_data: 241 | os_profile_windows_config.#: "1" 242 | os_profile_windows_config.429474957.additional_unattend_config.#: "0" 243 | os_profile_windows_config.429474957.enable_automatic_upgrades: "false" 244 | os_profile_windows_config.429474957.provision_vm_agent: "false" 245 | os_profile_windows_config.429474957.winrm.#: "0" 246 | resource_group_name: "myapp-compute" 247 | storage_image_reference.#: "1" 248 | storage_image_reference.3904372903.id: "" 249 | storage_image_reference.3904372903.offer: "WindowsServer" 250 | storage_image_reference.3904372903.publisher: "MicrosoftWindowsServer" 251 | storage_image_reference.3904372903.sku: "2016-Datacenter" 252 | storage_image_reference.3904372903.version: "latest" 253 | storage_os_disk.#: "1" 254 | storage_os_disk.0.caching: "ReadWrite" 255 | storage_os_disk.0.create_option: "FromImage" 256 | storage_os_disk.0.disk_size_gb: 257 | storage_os_disk.0.managed_disk_id: 258 | storage_os_disk.0.managed_disk_type: "Premium_LRS" 259 | storage_os_disk.0.name: "osdisk-myvm-0" 260 | tags.%: "1" 261 | tags.source: "terraform" 262 | vm_size: "Standard_DS1_V2" 263 | 264 | + module.windowsservers.random_id.vm-sa 265 | id: 266 | b64: 267 | b64_std: 268 | b64_url: 269 | byte_length: "6" 270 | dec: 271 | hex: 272 | keepers.%: "1" 273 | keepers.vm_hostname: "myvm" 274 | 275 | 276 | Plan: 7 to add, 0 to change, 0 to destroy. 277 | ``` 278 | 279 |

280 |
281 | 282 | Before applying, take a look at all the resources that are going to be created from our simple `module` block. 283 | 284 | Run `terraform apply` to create the infrastructure. 285 | 286 | ### Clean up 287 | 288 | Run `terraform destroy` to remove everything we created. 289 | 290 | ## Advanced areas to explore 291 | 292 | 1. Add a public ip to the Windows Compute instance using additional parameters built into the Compute module. 293 | 1. Create a second module instance of Compute to stand up a Linux Virtual Machine. 294 | 295 | ## Resources 296 | 297 | - [Azurerm Networking Moduel Source](https://github.com/Azure/terraform-azurerm-network) 298 | - [Azurerm Compute Module Source](https://github.com/Azure/terraform-azurerm-compute) 299 | - [Network Security Groups](https://docs.microsoft.com/en-us/azure/virtual-network/virtual-networks-nsg) 300 | -------------------------------------------------------------------------------- /challenges/07-remotebackend/README.md: -------------------------------------------------------------------------------- 1 | # 07 - Remote Backend 2 | 3 | ## Expected Outcome 4 | 5 | In this challenge, you will move your state file to a remote backend. 6 | 7 | ## How to 8 | 9 | ### Create Azure Storage Account 10 | 11 | In the Portal, create a SA. 12 | 13 | Get the account information, including SAS token. 14 | 15 | Create a Blob Container 16 | 17 | ### Config Backend 18 | 19 | Update your configuration with the info: 20 | 21 | ```hcl 22 | terraform { 23 | backend { 24 | account = "" 25 | key = "" 26 | name = "" 27 | } 28 | } 29 | ``` 30 | 31 | Run `terraform init`. 32 | 33 | ### View Lock State 34 | 35 | Run a plan and view the file in the portal, notice how a lease is put on it. 36 | -------------------------------------------------------------------------------- /challenges/08-setupterraformenterprise/README.md: -------------------------------------------------------------------------------- 1 | # 08 - Setup Terraform Enterprise 2 | 3 | ## Expected Outcome 4 | 5 | In this challenge, you will create your Terraform Enterprise trial and your first workspace to build infrastructure in Azure. 6 | 7 | This challenge will require that you have a github account so that you can fork repositories. Be sure to login before beginning. 8 | 9 | ## How to 10 | 11 | ### Create Service Principal 12 | 13 | Create a Service Principal on your Azure Subscription that Terraform will use to authenticate. 14 | To do this we need to get the following: 15 | 16 | - Tenant ID 17 | - Subscription ID 18 | - Client ID 19 | - Client Secret 20 | 21 | The tenant and subscription info are static, but we need to generate that service principal to get the Client ID and Secret. 22 | To make things easy here is a one line command to get the job done: 23 | 24 | ```sh 25 | az ad sp create-for-rbac -n TerraformAzureWorkshop --role="Contributor" --scopes /subscriptions/$(az account show -o tsv --query id) 26 | ``` 27 | 28 | > Note: As mentioned above, this command might not work in the cmd shell in Windows. If you can't use PowerShell or the Git bash, you should be able to separate this into multiple commands to get around cmd shell limitations, first getting your account ID and using that in the second query, like so: 29 | > 30 | > `az account show -o tsv --query id` 31 | > 32 | > `az ad sp create-for-rbac -n TerraformAzureWorkshop --role="Contributor" --scopes /subscriptions/` 33 | 34 | You may see output stating "Retrying", this is normal and is just the CLI waiting for the role to be created. 35 | 36 | When everything is complete you should see something like this: 37 | 38 | ```sh 39 | Retrying role assignment creation: 1/36 40 | Retrying role assignment creation: 2/36 41 | Retrying role assignment creation: 3/36 42 | Retrying role assignment creation: 4/36 43 | { 44 | "appId": "THIS IS YOUR CLIENT ID", 45 | "displayName": "TerraformAzureWorkshop", 46 | "name": "http://TerraformAzureWorkshop", 47 | "password": "THIS IS YOUR CLIENT PASSWORD", 48 | "tenant": "THIS IS YOUR TENANT ID" 49 | } 50 | ``` 51 | 52 | > The subscription id can be seen in the Azure Portal, or by running the Azure CLI command `az account show` 53 | 54 | Take note of all 4 of these values and keep them safe, you will need to access them throughout the workshop. 55 | 56 | > NOTE: It is a good idea to remove this Service Principal after the workshop! Using the ID of the service principal, you can run an `az ad sp delete --id `. See [here](https://docs.microsoft.com/en-us/cli/azure/ad/sp?view=azure-cli-latest) for more details. 57 | 58 | ### Set Azure Credentials 59 | 60 | Terraform Enterprise uses a Service Principal to authenticate Terraform for use with Azure. 61 | 62 | Keep track of the following environment variables based on the Service Principal: 63 | 64 | ```sh 65 | export ARM_TENANT_ID= 66 | export ARM_SUBSCRIPTION_ID= 67 | export ARM_CLIENT_ID= 68 | export ARM_CLIENT_SECRET= 69 | ``` 70 | 71 | ### Fork the Repository 72 | 73 | Open up a browser and navigate to the pre-built repository https://github.com/azure-terraform-workshop/azureworkshop-workspaces. 74 | 75 | In the top right, click the `Fork` button. 76 | 77 | ![](../../img/2018-05-10-17-14-51.png) 78 | 79 | Follow the prompts which will fork the repository into your own space you control. 80 | 81 | > Note: We need to fork this repository to allow you to connect it to Terraform Enterprise which will create webhooks to get vital information about how and when the repository changes. 82 | 83 | You should land in your forked version of the repository. 84 | 85 | ![](../../img/2018-05-10-17-17-27.png) 86 | 87 | 88 | ### azureworkshop-workspaces repository 89 | 90 | There is not much to this repository, just a couple folders that we will use in the next few challenges. 91 | 92 | **app-dev** - Simple set of infrastructure to deploy from Terraform Enterprise. 93 | 94 | **app-dev-modules** - More complex set infrastructure utilizing the Private Module Registry. Ignore this for now, we will use this in the next Challenge. 95 | 96 | ### Create a Trial Account for Terraform Enterprise 97 | 98 | Register for a [Terraform Enterprise Trial](https://app.terraform.io/account/new?trial=terraform). 99 | ![](../../img/2018-04-14-12-58-33.png) 100 | 101 | If you are working on this today with others from your organization, you can create a single trial and work together through the last few challenges. 102 | 103 | ### Create a New Organization 104 | 105 | Pick a name that includes your name. Example: 'tstraubworkshop' 106 | 107 | ![](../../img/2018-04-14-12-59-54.png) 108 | 109 | > __Note:__ Organization must be globally unique. 110 | 111 | Verify you can login to your Terraform Enterprise organization. 112 | 113 | Now you are ready to start using Terraform Enterprise! 114 | 115 | ### Create a New Workspace 116 | 117 | Click the "New Workspace" button. 118 | 119 | Pick a name that indicates the intent of the infrastructure. Example: 'app-dev' 120 | 121 | ![](../../img/2018-04-14-13-10-52.png) 122 | 123 | ### Setup VCS 124 | 125 | 126 | 127 | You won't have any "Source" options, so click the "+" button to connect Terraform Enterprise to your source control. You will see the following screen asking you to add a VCS root. Click the "Add VCS Provider" button to continue: 128 | 129 | ![](../../img/2018-05-11-11-22-22.png) 130 | 131 | You will be brought to the Add VCS Provider page: 132 | 133 | ![](../../img/2018-05-11-11-26-22.png) 134 | 135 | Follow the instructions for any of the following VCS providers (we are going to be using Github): 136 | 137 | - [Github](https://www.terraform.io/docs/enterprise/vcs/github.html) 138 | - [Github Enterprise](https://www.terraform.io/docs/enterprise/vcs/github-enterprise.html) 139 | - [GitLab](https://www.terraform.io/docs/enterprise/vcs/gitlab-com.html) 140 | - [GitLab EE and CE](https://www.terraform.io/docs/enterprise/vcs/gitlab-eece.html) 141 | - [Bitbucket Cloud](https://www.terraform.io/docs/enterprise/vcs/bitbucket-cloud.html) 142 | - [Bitbucket Server](https://www.terraform.io/docs/enterprise-legacy/index.html) 143 | 144 | > Note: This only has to be done once for each Version Control Provider. 145 | 146 | > Note: You will need to update your placeholder URL to successfully connect/create your GitHub VCS root. Make sure you grab your authorization callback URL from GitHub as defined in Step 3 of the instructions for GitHub above. You may not see the menus described in Step 2. 147 | 148 | After this is done, you may have to go back and create your workspace if you didn't do so before you created the VCS provider. You can do so by navigating to the Workspaces tab at the top of the page and clicking the "New Workspace" button, choosing your GitHub VCS provider during creation. 149 | 150 | ### Connect Workspace 151 | 152 | Connect your workspace to your VCS. 153 | 154 | ![](../../img/2018-04-14-14-04-56.png) 155 | 156 | Set working directory and branch properly! 157 | 158 | ### Configure Variables 159 | 160 | Now that you have a workspace, navigate to the variables. 161 | 162 | Set a the Terraform Variable "name" to something unique. Example "app-dev". Click "Save". 163 | 164 | Set Environment Variables for your Azure Service Principal (be sure check the 'sensitive' checkbox to hide these values): 165 | 166 | - ARM_TENANT_ID 167 | - ARM_SUBSCRIPTION_ID 168 | - ARM_CLIENT_ID 169 | - ARM_CLIENT_SECRET 170 | 171 | > Note: You used commands to get/set this information in Step 1 [here.](../01-connectingtoazure/README.md) Refer back to it if you need to refresh your memory. 172 | 173 | > Note: Remember also that some aliases/commands don't work as expected from a Windows cmd shell; if you have any issues with the commands you can try to run them either in the Git bash or PowerShell. 174 | 175 | Click "Save". 176 | 177 | ![](../../img/2018-04-14-14-10-32.png) 178 | 179 | ### Run a Plan 180 | 181 | Click the "Queue Plan" button. 182 | ![](../../img/2018-04-14-14-12-05.png) 183 | 184 | ### View the Plan 185 | 186 | ![](../../img/2018-04-14-14-13-01.png) 187 | 188 | ### Run an Apply 189 | 190 | Enter a comment and then apply by clicking "Confirm & Apply". 191 | 192 | ![](../../img/2018-04-14-14-13-52.png) 193 | 194 | ### View the Apply 195 | 196 | ![](../../img/2018-04-14-14-15-02.png) 197 | 198 | ## Advanced areas to explore 199 | 200 | 1. Explore state versions after the apply. 201 | 1. Add another folder in the repository for 'app-prod' and create another workspace with different settings. 202 | 1. Push a change to the repository with the workspaces in it, what happens in Terraform Enterprise? 203 | 204 | ## Resources 205 | 206 | - [TFE Access](https://www.terraform.io/docs/enterprise/getting-started/access.html) 207 | -------------------------------------------------------------------------------- /challenges/09-privatemoduleregistry/README.md: -------------------------------------------------------------------------------- 1 | # 09 - Private Module Registry with Terraform Enterprise 2 | 3 | ## Expected Outcome 4 | 5 | In this challenge you will register some modules with your Private Module Registry then reference them in a workspace. 6 | 7 | ## How to: 8 | 9 | ### Fork the Module Repositories 10 | 11 | Just like in the last challenge, you are going to fork the following repos into your own GitHub account: 12 | 13 | - https://github.com/azure-terraform-workshop/terraform-azurerm-networking.git 14 | - https://github.com/azure-terraform-workshop/terraform-azurerm-webserver.git 15 | - https://github.com/azure-terraform-workshop/terraform-azurerm-appserver.git 16 | - https://github.com/azure-terraform-workshop/terraform-azurerm-dataserver.git 17 | 18 | Each of these repositories represents a module that can be developed and versioned independently. 19 | 20 | ### Add Modules 21 | 22 | Navigate back to Terraform Enterprise and click the "Modules" menu at the top of the page. From there click the "+ Add Module" button. 23 | 24 | ![](../../img/2018-05-10-17-37-05.png) 25 | 26 | You are now ready to add your modules. 27 | 28 | ![](../../img/2018-04-15-13-09-55.png) 29 | 30 | Enter the name of the source repository you forked in the previous step. For example: 'YOUR_GITHUB_USERNAME/terraform-azurerm-networking`. 31 | 32 | Click "Publish Module". 33 | 34 | This will query the repository for necessary files and tags used for versioning. 35 | 36 | Congrats, you are done! 37 | 38 | Ok, not really... 39 | 40 | Repeat this step for the other three modules: 41 | 42 | - terraform-azurerm-appserver 43 | - terraform-azurerm-dataserver 44 | - terraform-azurerm-webserver 45 | 46 | ### Consume Modules 47 | 48 | Create a new workspace just like in the previous Challenge, except this time enter the working directory of "app-dev-modules" that will reference your the modules you just added. 49 | 50 | ![](../../img/2018-05-10-17-40-35.png) 51 | 52 | ### Configure Variables 53 | 54 | Set the Terraform Variables: 55 | 56 | - 'name' - A unique environment name such as `devmodules` 57 | - 'location' - An Azure region such as `eastus` or `centralus` 58 | - 'username' (sensitive) - A username for the VM's 59 | > Note: this can not be "admin" 60 | - 'password' (sensitive) - A password for the VM's 61 | > NOTE: password must be between 6-72 characters long and must satisfy at least 3 of password complexity requirements from the following: 62 | > 1. Contains an uppercase character 63 | > 2. Contains a lowercase character 64 | > 3. Contains a numeric digit 65 | > 4. Contains a special character 66 | - 'vnet_address_spacing' (HCL) - The Vnet Address space 67 | ```hcl 68 | ["10.0.0.0/16"] 69 | ``` 70 | - 'subnet_address_prefixes' (HCL) - The Subnet Address spaces representing 3 subnets 71 | ```hcl 72 | [ 73 | "10.0.0.0/24", 74 | "10.0.1.0/24", 75 | "10.0.2.0/24", 76 | ] 77 | ``` 78 | 79 | Set Environment Variables for your Azure Service Principal (be sure check the 'sensitive' checkbox to hide these values): 80 | 81 | - ARM_TENANT_ID 82 | - ARM_SUBSCRIPTION_ID 83 | - ARM_CLIENT_ID 84 | - ARM_CLIENT_SECRET 85 | 86 | ### Run a Plan 87 | 88 | Click the "Queue Plan" button. 89 | 90 | ![](../../img/2018-04-15-19-23-40.png) 91 | 92 | Wait for the Plan to complete. 93 | 94 | ### Fix the Errors 95 | 96 | The `/app-dev-modules/main.tf` file references the wrong modules source. Update all the modules sources (in your forked `azureworkshop-workspaces` GitHub repo) to match your Terraform Enterprise Organization. 97 | 98 | For example: 99 | 100 | Change this: 101 | 102 | ```hcl 103 | module "networking" { 104 | source = "app.terraform.io/cardinalsolutions/networking/azurerm" 105 | version = "0.0.1" 106 | ... 107 | } 108 | ``` 109 | 110 | To something like this: 111 | ```hcl 112 | module "networking" { 113 | source = "app.terraform.io/YOUR_TFE_ORGANIZATION/networking/azurerm" 114 | version = "0.0.1" 115 | ... 116 | } 117 | ``` 118 | 119 | Queue a new Plan. 120 | 121 | ### Apply the Plan 122 | 123 | Approve the plan and apply it. 124 | 125 | Watch the apply progress and complete. 126 | 127 | Login to the at Azure Portal to see your infrastructure. 128 | 129 | ### Update a Module 130 | 131 | In the `azureworkshop-workspaces` repository, navigate to the `app-dev-modules/main.tf` file and update one (or several) of the modules versions from "0.0.1" to "0.0.2". 132 | 133 | Commit your change and see what the changes show in the plan. What was the difference between version 0.0.1 and 0.0.2? Does this look like a safe change to make? 134 | 135 | ## Advanced areas to explore 136 | 137 | 1. Add another workspace using a different combination of the 3-tier application. 138 | 1. Make a change to one of the modules and commit that to the repository. How do you get a new version to show in your Private Module Registry? 139 | 140 | ## Resources 141 | 142 | - [Private Registries](https://www.terraform.io/docs/registry/private.html) 143 | - [Publishing Modules](https://www.terraform.io/docs/registry/modules/publish.html) -------------------------------------------------------------------------------- /challenges/10-sentinelpolicy/README.md: -------------------------------------------------------------------------------- 1 | # 10 - Sentinel Policy with Terraform Enterprise 2 | 3 | ## Expected Outcome 4 | 5 | In this challenge, you will see how you can apply policies around your Azure subscriptions using Sentinel Policies. 6 | 7 | 8 | ## How to 9 | 10 | ### View Policies 11 | 12 | In the Terraform Enterprise web app, click on your organization -> Organization Settings 13 | 14 | https://app.terraform.io/app/YOUR_TFE_ORGANIZATION/settings/policies 15 | 16 | ![](../../img/2018-04-16-20-02-58.png) 17 | 18 | ### Create Policy in App 19 | 20 | Click "Create new Policy" 21 | 22 | ![](../../img/2018-04-16-20-03-30.png) 23 | 24 | ### Create Policy from API 25 | 26 | Create the following policy: 27 | 28 | __Policy Name:__ ResourceGroupRequireTag 29 | 30 | __Policy Enforcement:__ advisory (logging only) 31 | 32 | __Policy Code:__ 33 | 34 | ```hcl 35 | import "tfplan" 36 | 37 | required_tags = [ 38 | "owner", 39 | "environment", 40 | ] 41 | 42 | getTags = func(group) { 43 | tags = keys(group.applied.tags) 44 | 45 | for required_tags as t { 46 | if t not in tags { 47 | print("Resource Missing Tag:", t) 48 | return false 49 | } 50 | } 51 | 52 | return true 53 | } 54 | main = rule { 55 | all tfplan.resources.azurerm_resource_group as _, groups { 56 | all groups as _, group { 57 | getTags(group) 58 | } 59 | } 60 | } 61 | ``` 62 | 63 | ### Run a Plan 64 | 65 | Queue a plan for the workspace `app-dev`. 66 | 67 | ### Review the Plan 68 | 69 | Will see the plan was successful but there was a policy failure, however the option to Apply is still available. 70 | 71 | ### Update the Policy 72 | 73 | Update the Policy Enforcement to be `hard-mandatory`. 74 | 75 | ### Run a Plan 76 | 77 | Queue a plan for the workspace `app-dev`. 78 | 79 | ### Review the Plan 80 | 81 | This time the the run fails due to the hard enforcement. 82 | 83 | ### Update Workspace 84 | 85 | In the `app-dev` workspace, add the following to the `azurerm_resource_group` declaration: 86 | 87 | ```hcl 88 | resource "azurerm_resource_group" "module" { 89 | 90 | ... 91 | 92 | tags { 93 | Owner = "me" 94 | } 95 | } 96 | ``` 97 | 98 | Save and commit the code to your repository. 99 | 100 | ### Run a Plan 101 | 102 | Run another plan. 103 | 104 | > Note: You may need to discard the last non-applied build. 105 | 106 | ### Review the Plan 107 | 108 | The plan should succeed and now pass the sentinel policy check. 109 | 110 | ## Advanced areas to explore 111 | 112 | 1. Write another Sentinel Policy restricting VM types in Azure. 113 | 114 | ## Resources 115 | 116 | - [Policy](https://app.terraform.io/app/cardinalsolutions/settings/policies) 117 | - [Sentinel Language Spec](https://docs.hashicorp.com/sentinel/language/spec) 118 | -------------------------------------------------------------------------------- /docs/AzureTerraformWorkshopPresentation.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/docs/AzureTerraformWorkshopPresentation.pptx -------------------------------------------------------------------------------- /event-invitation.md: -------------------------------------------------------------------------------- 1 | # INVITE: Infrastructure as Code - Building Azure infrastructure using Terraform and Terraform Enterprise 2 | 3 | Infrastructure as Code allows the management of your infrastructure including networks, virtual machines, platform services, and load balancers. Utilizing Terraform configuration to describe the desired Azure resources needed for an environment ensures that consistent infrastructure is created each time it is deployed. 4 | 5 | This Workshop is focused on: 6 | 7 | - Understanding Infrastructure as Code concepts 8 | - Terraform as an Infrastructure as Code provider 9 | - Terraform Enterprise features and benefits 10 | 11 | ### Understanding Infrastructure as Code concepts 12 | 13 | Infrastructure as Code allows DevOps teams to deploy production-like environments early in the development cycle. Declarative code representing infrastructure can also be validated and approved before moving downstream. Using Azure's cloud platform, dynamic creation, modification, and deletion of infrastructure that is built rapidly, reliably, and at scale. 14 | 15 | ### Terraform as an Infrastructure as Code provider 16 | 17 | Utilizing a simple configuration language Terraform allows for a human readable representation of your infrastructure. The execution plan phase allows infrastructure to be viewed and approved with full confidence that you are changing your environment how you intended. 18 | 19 | ### Terraform Enterprise features and benefits 20 | 21 | Terraform Enterprise offers a single location to perform all of your Terraform workflows. It is a central place to manage your infrastructure and includes collaboration, governance, and private module registry capabilities. 22 | 23 | ## Target Audience 24 | 25 | The event is targeted at DevOps teams and managers looking to automate infrastructure deployments. The session will start with core concepts around IaC and then deep dive into how Terraform with hand-on coding. 26 | 27 | ## Gives and Gets 28 | 29 | ### Gets 30 | 31 | - Learn alongside Cardinal and Microsoft Engineers to help you understand the design considerations to accelerate your Azure infrastructure deployments. 32 | - Engage in hands on coding to help understand the options available to designing your IaC solution. 33 | - Post-Hackfest engagements to ensure your continued success. 34 | 35 | ### Gives 36 | 37 | - Attend the Workshop with your leaders in DevOps and IT Pro’s. We strongly recommend including a technical decision maker and architect level resource. 38 | - Defined Azure Subscription to deploy real infrastructure to during the event. 39 | - Initial set of infrastructure you are looking to create as an environment. 40 | -------------------------------------------------------------------------------- /examples/simple-rg/main.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_resource_group" "test" { 2 | name = "test-rg-tom" 3 | location = "centralus" 4 | } -------------------------------------------------------------------------------- /examples/ssh/main.tf: -------------------------------------------------------------------------------- 1 | locals{ 2 | ssh_key_name = "bastion" 3 | } 4 | resource "tls_private_key" "main" { 5 | algorithm = "RSA" 6 | } 7 | 8 | resource "local_file" "private" { 9 | content = "${tls_private_key.main.private_key_pem}" 10 | filename = "./id_rsa_${local.ssh_key_name}.pem" 11 | 12 | provisioner "local-exec" { 13 | command = "chmod 600 ./id_rsa_${local.ssh_key_name}.pem" 14 | } 15 | } 16 | 17 | resource "local_file" "public" { 18 | content = "${tls_private_key.main.public_key_openssh}" 19 | filename = "./id_rsa_${local.ssh_key_name}.pub" 20 | 21 | provisioner "local-exec" { 22 | command = "chmod 600 ./id_rsa_${local.ssh_key_name}.pub" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /img/2018-04-07-15-08-41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-07-15-08-41.png -------------------------------------------------------------------------------- /img/2018-04-07-16-54-28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-07-16-54-28.png -------------------------------------------------------------------------------- /img/2018-04-14-12-58-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-14-12-58-33.png -------------------------------------------------------------------------------- /img/2018-04-14-12-59-54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-14-12-59-54.png -------------------------------------------------------------------------------- /img/2018-04-14-13-10-52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-14-13-10-52.png -------------------------------------------------------------------------------- /img/2018-04-14-13-21-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-14-13-21-32.png -------------------------------------------------------------------------------- /img/2018-04-14-14-04-56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-14-14-04-56.png -------------------------------------------------------------------------------- /img/2018-04-14-14-10-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-14-14-10-32.png -------------------------------------------------------------------------------- /img/2018-04-14-14-11-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-14-14-11-20.png -------------------------------------------------------------------------------- /img/2018-04-14-14-12-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-14-14-12-05.png -------------------------------------------------------------------------------- /img/2018-04-14-14-13-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-14-14-13-01.png -------------------------------------------------------------------------------- /img/2018-04-14-14-13-52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-14-14-13-52.png -------------------------------------------------------------------------------- /img/2018-04-14-14-15-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-14-14-15-02.png -------------------------------------------------------------------------------- /img/2018-04-15-13-09-55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-15-13-09-55.png -------------------------------------------------------------------------------- /img/2018-04-15-19-23-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-15-19-23-40.png -------------------------------------------------------------------------------- /img/2018-04-16-20-02-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-16-20-02-58.png -------------------------------------------------------------------------------- /img/2018-04-16-20-03-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-04-16-20-03-30.png -------------------------------------------------------------------------------- /img/2018-05-07-18-08-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-07-18-08-30.png -------------------------------------------------------------------------------- /img/2018-05-07-18-11-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-07-18-11-33.png -------------------------------------------------------------------------------- /img/2018-05-07-18-13-28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-07-18-13-28.png -------------------------------------------------------------------------------- /img/2018-05-07-18-20-56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-07-18-20-56.png -------------------------------------------------------------------------------- /img/2018-05-07-18-29-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-07-18-29-10.png -------------------------------------------------------------------------------- /img/2018-05-09-09-10-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-09-09-10-24.png -------------------------------------------------------------------------------- /img/2018-05-09-10-20-28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-09-10-20-28.png -------------------------------------------------------------------------------- /img/2018-05-09-14-55-42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-09-14-55-42.png -------------------------------------------------------------------------------- /img/2018-05-10-17-14-51.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-10-17-14-51.png -------------------------------------------------------------------------------- /img/2018-05-10-17-17-27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-10-17-17-27.png -------------------------------------------------------------------------------- /img/2018-05-10-17-37-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-10-17-37-05.png -------------------------------------------------------------------------------- /img/2018-05-10-17-40-35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-10-17-40-35.png -------------------------------------------------------------------------------- /img/2018-05-11-11-22-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-11-11-22-22.png -------------------------------------------------------------------------------- /img/2018-05-11-11-26-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-11-11-26-22.png -------------------------------------------------------------------------------- /img/2018-05-14-07-27-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-14-07-27-11.png -------------------------------------------------------------------------------- /img/2018-05-14-08-18-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-14-08-18-48.png -------------------------------------------------------------------------------- /img/2018-05-28-12-25-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-28-12-25-01.png -------------------------------------------------------------------------------- /img/2018-05-28-12-27-31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-28-12-27-31.png -------------------------------------------------------------------------------- /img/2018-05-28-12-29-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-28-12-29-06.png -------------------------------------------------------------------------------- /img/2018-05-28-12-30-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-28-12-30-33.png -------------------------------------------------------------------------------- /img/2018-05-28-13-58-49.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-28-13-58-49.png -------------------------------------------------------------------------------- /img/2018-05-28-14-01-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-28-14-01-30.png -------------------------------------------------------------------------------- /img/2018-05-28-14-03-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-28-14-03-05.png -------------------------------------------------------------------------------- /img/2018-05-28-14-04-39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-28-14-04-39.png -------------------------------------------------------------------------------- /img/2018-05-28-14-05-39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-28-14-05-39.png -------------------------------------------------------------------------------- /img/2018-05-28-14-09-39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-05-28-14-09-39.png -------------------------------------------------------------------------------- /img/2018-06-07-16-23-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-06-07-16-23-29.png -------------------------------------------------------------------------------- /img/2018-11-02-12-59-21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2018-11-02-12-59-21.png -------------------------------------------------------------------------------- /img/2019-05-08-09-24-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2019-05-08-09-24-12.png -------------------------------------------------------------------------------- /img/2019-05-08-09-27-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardinalNow/TerraformWorkshop/24a729e333b53687951c14f31528fb330f648e0c/img/2019-05-08-09-27-19.png -------------------------------------------------------------------------------- /solutions/01-connectingtoazure/main.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_resource_group" "main" { 2 | name = "challenge01-rg" 3 | location = "eastus" 4 | } 5 | 6 | resource "azurerm_resource_group" "count" { 7 | name = "challenge01-rg-${count.index}" 8 | location = "eastus" 9 | count = 2 10 | } 11 | 12 | // Import these resources that were manually created in the Azure Portal 13 | resource "azurerm_resource_group" "import" { 14 | name = "myportal-rg" 15 | location = "eastus" 16 | 17 | tags { 18 | terraform = "true" 19 | } 20 | } 21 | 22 | resource "azurerm_storage_account" "import" { 23 | name = "myusernamestorageaccount" 24 | resource_group_name = "${azurerm_resource_group.import.name}" 25 | location = "eastus" 26 | account_kind = "StorageV2" 27 | account_tier = "Standard" 28 | account_replication_type = "LRS" 29 | enable_https_traffic_only = true 30 | 31 | tags { 32 | terraform = "true" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /solutions/02-aci-helloworld/main.tf: -------------------------------------------------------------------------------- 1 | resource "random_pet" "main" { 2 | length = 2 3 | separator = "" 4 | } 5 | 6 | resource "azurerm_resource_group" "main" { 7 | name = "aci-helloworld" 8 | location = "eastus" 9 | } 10 | 11 | resource "azurerm_storage_account" "main" { 12 | name = "acidev${random_pet.main.id}" 13 | resource_group_name = "${azurerm_resource_group.main.name}" 14 | location = "${azurerm_resource_group.main.location}" 15 | account_tier = "Standard" 16 | account_replication_type = "LRS" 17 | } 18 | 19 | resource "azurerm_storage_share" "main" { 20 | name = "aci-test-share" 21 | resource_group_name = "${azurerm_resource_group.main.name}" 22 | storage_account_name = "${azurerm_storage_account.main.name}" 23 | quota = 1 24 | } 25 | 26 | resource "azurerm_container_group" "main" { 27 | name = "aci-helloworld" 28 | location = "${azurerm_resource_group.main.location}" 29 | resource_group_name = "${azurerm_resource_group.main.name}" 30 | ip_address_type = "public" 31 | dns_name_label = "aci-${random_pet.main.id}" 32 | os_type = "linux" 33 | 34 | container { 35 | name = "helloworld" 36 | image = "microsoft/aci-helloworld" 37 | cpu = "0.5" 38 | memory = "1.5" 39 | 40 | ports { 41 | port = 80 42 | protocol = "TCP" 43 | } 44 | 45 | environment_variables { 46 | "NODE_ENV" = "testing" 47 | } 48 | 49 | volume { 50 | name = "logs" 51 | mount_path = "/aci/logs" 52 | read_only = false 53 | share_name = "${azurerm_storage_share.main.name}" 54 | 55 | storage_account_name = "${azurerm_storage_account.main.name}" 56 | storage_account_key = "${azurerm_storage_account.main.primary_access_key}" 57 | } 58 | } 59 | 60 | container { 61 | name = "sidecar" 62 | image = "microsoft/aci-tutorial-sidecar" 63 | cpu = "0.5" 64 | memory = "1.5" 65 | } 66 | 67 | tags { 68 | environment = "testing" 69 | } 70 | } 71 | 72 | resource "azurerm_container_group" "windows" { 73 | name = "aci-iis" 74 | location = "${azurerm_resource_group.main.location}" 75 | resource_group_name = "${azurerm_resource_group.main.name}" 76 | ip_address_type = "public" 77 | dns_name_label = "aci-iis-${random_pet.main.id}" 78 | os_type = "windows" 79 | 80 | container { 81 | name = "dotnetsample" 82 | image = "microsoft/iis" 83 | cpu = "0.5" 84 | memory = "1.5" 85 | 86 | ports { 87 | port = 80 88 | protocol = "TCP" 89 | } 90 | } 91 | 92 | tags { 93 | environment = "testing" 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /solutions/03-azurevm/main.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | default = "challenge03" 3 | } 4 | 5 | variable "location" { 6 | default = "eastus" 7 | } 8 | 9 | # Basic Resources 10 | resource "azurerm_resource_group" "main" { 11 | name = "${var.name}-rg" 12 | location = "${var.location}" 13 | } 14 | 15 | resource "azurerm_virtual_network" "main" { 16 | name = "${var.name}-vnet" 17 | address_space = ["10.0.0.0/16"] 18 | location = "${azurerm_resource_group.main.location}" 19 | resource_group_name = "${azurerm_resource_group.main.name}" 20 | } 21 | 22 | resource "azurerm_subnet" "main" { 23 | name = "${var.name}-subnet" 24 | resource_group_name = "${azurerm_resource_group.main.name}" 25 | virtual_network_name = "${azurerm_virtual_network.main.name}" 26 | address_prefix = "10.0.1.0/24" 27 | } 28 | 29 | # VM Resources 30 | resource "azurerm_public_ip" "main" { 31 | name = "${var.name}-pubip" 32 | location = "${azurerm_resource_group.main.location}" 33 | resource_group_name = "${azurerm_resource_group.main.name}" 34 | allocation_method = "Static" 35 | } 36 | 37 | resource "azurerm_network_interface" "main" { 38 | name = "${var.name}-nic" 39 | location = "${azurerm_resource_group.main.location}" 40 | resource_group_name = "${azurerm_resource_group.main.name}" 41 | 42 | ip_configuration { 43 | name = "config1" 44 | subnet_id = "${azurerm_subnet.main.id}" 45 | public_ip_address_id = "${azurerm_public_ip.main.id}" 46 | private_ip_address_allocation = "dynamic" 47 | } 48 | } 49 | 50 | resource "azurerm_virtual_machine" "main" { 51 | name = "${var.name}-vm" 52 | location = "${azurerm_resource_group.main.location}" 53 | resource_group_name = "${azurerm_resource_group.main.name}" 54 | network_interface_ids = ["${azurerm_network_interface.main.id}"] 55 | vm_size = "Standard_A2_v2" 56 | 57 | storage_image_reference { 58 | publisher = "MicrosoftWindowsServer" 59 | offer = "WindowsServer" 60 | sku = "2016-Datacenter" 61 | version = "latest" 62 | } 63 | 64 | storage_os_disk { 65 | name = "${var.name}vm-osdisk" 66 | caching = "ReadWrite" 67 | create_option = "FromImage" 68 | managed_disk_type = "Standard_LRS" 69 | } 70 | 71 | os_profile { 72 | computer_name = "${var.name}vm" 73 | admin_username = "testadmin" 74 | admin_password = "Password1234!" 75 | } 76 | 77 | os_profile_windows_config {} 78 | } 79 | 80 | ## Outputs 81 | output "private-ip" { 82 | value = "${azurerm_network_interface.main.private_ip_address}" 83 | description = "Private IP Address" 84 | } 85 | 86 | output "public-ip" { 87 | value = "${azurerm_public_ip.main.ip_address}" 88 | description = "Public IP Address" 89 | } 90 | -------------------------------------------------------------------------------- /solutions/04-terraformcount/main.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | default = "challenge04" 3 | } 4 | 5 | variable "location" { 6 | default = "eastus" 7 | } 8 | 9 | variable "vmcount" { 10 | default = 2 11 | } 12 | 13 | # Basic Resources 14 | resource "azurerm_resource_group" "main" { 15 | name = "${var.name}-rg" 16 | location = "${var.location}" 17 | } 18 | 19 | resource "azurerm_virtual_network" "main" { 20 | name = "${var.name}-vnet" 21 | address_space = ["10.0.0.0/16"] 22 | location = "${azurerm_resource_group.main.location}" 23 | resource_group_name = "${azurerm_resource_group.main.name}" 24 | } 25 | 26 | resource "azurerm_subnet" "main" { 27 | name = "${var.name}-subnet" 28 | resource_group_name = "${azurerm_resource_group.main.name}" 29 | virtual_network_name = "${azurerm_virtual_network.main.name}" 30 | address_prefix = "10.0.1.0/24" 31 | } 32 | 33 | # VM Resources 34 | resource "azurerm_public_ip" "main" { 35 | name = "${var.name}-pubip${count.index}" 36 | location = "${azurerm_resource_group.main.location}" 37 | resource_group_name = "${azurerm_resource_group.main.name}" 38 | allocation_method = "Static" 39 | count = "${var.vmcount}" 40 | } 41 | 42 | resource "azurerm_network_interface" "main" { 43 | name = "${var.name}-nic${count.index}" 44 | location = "${azurerm_resource_group.main.location}" 45 | resource_group_name = "${azurerm_resource_group.main.name}" 46 | count = "${var.vmcount}" 47 | 48 | ip_configuration { 49 | name = "config1" 50 | subnet_id = "${azurerm_subnet.main.id}" 51 | 52 | // public_ip_address_id = "${azurerm_public_ip.main.id}" 53 | public_ip_address_id = "${element(azurerm_public_ip.main.*.id, count.index)}" 54 | private_ip_address_allocation = "dynamic" 55 | } 56 | } 57 | 58 | resource "azurerm_virtual_machine" "main" { 59 | name = "${var.name}-vm${count.index}" 60 | location = "${azurerm_resource_group.main.location}" 61 | resource_group_name = "${azurerm_resource_group.main.name}" 62 | 63 | // network_interface_ids = ["${azurerm_network_interface.main.id}"] 64 | network_interface_ids = ["${element(azurerm_network_interface.main.*.id, count.index)}"] 65 | vm_size = "Standard_A2_v2" 66 | count = "${var.vmcount}" 67 | 68 | storage_image_reference { 69 | publisher = "MicrosoftWindowsServer" 70 | offer = "WindowsServer" 71 | sku = "2016-Datacenter" 72 | version = "latest" 73 | } 74 | 75 | storage_os_disk { 76 | name = "${var.name}vm${count.index}-osdisk" 77 | caching = "ReadWrite" 78 | create_option = "FromImage" 79 | managed_disk_type = "Standard_LRS" 80 | } 81 | 82 | os_profile { 83 | computer_name = "${var.name}vm${count.index}" 84 | admin_username = "testadmin" 85 | admin_password = "Password1234!" 86 | } 87 | 88 | os_profile_windows_config {} 89 | } 90 | 91 | ## Outputs 92 | output "private-ip" { 93 | value = "${azurerm_network_interface.main.*.private_ip_address}" 94 | description = "Private IP Address" 95 | } 96 | 97 | output "public-ip" { 98 | value = "${azurerm_public_ip.main.*.ip_address}" 99 | description = "Public IP Address" 100 | } 101 | -------------------------------------------------------------------------------- /solutions/05-terraformmodules/environments/dev/main.tf: -------------------------------------------------------------------------------- 1 | variable "username" {} 2 | variable "password" {} 3 | 4 | module "myawesomewindowsvm" { 5 | source = "../../modules/my_virtual_machine" 6 | name = "awesomeapp" 7 | vm_size = "Standard_A2_v2" 8 | username = "${var.username}" 9 | password = "${var.password}" 10 | vmcount = 2 11 | } 12 | 13 | module "differentwindowsvm" { 14 | source = "../../modules/my_virtual_machine" 15 | name = "differentapp" 16 | vm_size = "Standard_A2_v2" 17 | username = "${var.username}" 18 | password = "${var.password}" 19 | } 20 | -------------------------------------------------------------------------------- /solutions/05-terraformmodules/modules/my_virtual_machine/main.tf: -------------------------------------------------------------------------------- 1 | variable "vm_size" {} 2 | variable "username" {} 3 | variable "password" {} 4 | 5 | variable "name" { 6 | default = "challenge05" 7 | } 8 | 9 | variable "location" { 10 | default = "eastus" 11 | } 12 | 13 | variable "vmcount" { 14 | default = 2 15 | } 16 | 17 | # Basic Resources 18 | resource "azurerm_resource_group" "main" { 19 | name = "${var.name}-rg" 20 | location = "${var.location}" 21 | } 22 | 23 | resource "azurerm_virtual_network" "main" { 24 | name = "${var.name}-vnet" 25 | address_space = ["10.0.0.0/16"] 26 | location = "${azurerm_resource_group.main.location}" 27 | resource_group_name = "${azurerm_resource_group.main.name}" 28 | } 29 | 30 | resource "azurerm_subnet" "main" { 31 | name = "${var.name}-subnet" 32 | resource_group_name = "${azurerm_resource_group.main.name}" 33 | virtual_network_name = "${azurerm_virtual_network.main.name}" 34 | address_prefix = "10.0.1.0/24" 35 | } 36 | 37 | # VM Resources 38 | resource "azurerm_public_ip" "main" { 39 | name = "${var.name}-pubip${count.index}" 40 | location = "${azurerm_resource_group.main.location}" 41 | resource_group_name = "${azurerm_resource_group.main.name}" 42 | allocation_method = "Static" 43 | count = "${var.vmcount}" 44 | } 45 | 46 | resource "azurerm_network_interface" "main" { 47 | name = "${var.name}-nic${count.index}" 48 | location = "${azurerm_resource_group.main.location}" 49 | resource_group_name = "${azurerm_resource_group.main.name}" 50 | count = "${var.vmcount}" 51 | 52 | ip_configuration { 53 | name = "config1" 54 | subnet_id = "${azurerm_subnet.main.id}" 55 | public_ip_address_id = "${element(azurerm_public_ip.main.*.id, count.index)}" 56 | private_ip_address_allocation = "dynamic" 57 | } 58 | } 59 | 60 | resource "azurerm_virtual_machine" "main" { 61 | name = "${var.name}-vm${count.index}" 62 | location = "${azurerm_resource_group.main.location}" 63 | resource_group_name = "${azurerm_resource_group.main.name}" 64 | network_interface_ids = ["${element(azurerm_network_interface.main.*.id, count.index)}"] 65 | vm_size = "${var.vm_size}" 66 | count = "${var.vmcount}" 67 | 68 | storage_image_reference { 69 | publisher = "MicrosoftWindowsServer" 70 | offer = "WindowsServer" 71 | sku = "2016-Datacenter" 72 | version = "latest" 73 | } 74 | 75 | storage_os_disk { 76 | name = "${var.name}vm${count.index}-osdisk" 77 | caching = "ReadWrite" 78 | create_option = "FromImage" 79 | managed_disk_type = "Standard_LRS" 80 | } 81 | 82 | os_profile { 83 | computer_name = "${var.name}vm${count.index}" 84 | admin_username = "${var.username}" 85 | admin_password = "${var.password}" 86 | } 87 | 88 | os_profile_windows_config {} 89 | } 90 | 91 | ## Outputs 92 | output "private-ip" { 93 | value = "${azurerm_network_interface.main.*.private_ip_address}" 94 | description = "Private IP Address" 95 | } 96 | 97 | output "public-ip" { 98 | value = "${azurerm_public_ip.main.*.ip_address}" 99 | description = "Public IP Address" 100 | } 101 | -------------------------------------------------------------------------------- /solutions/06-publicmoduleregistry/main.tf: -------------------------------------------------------------------------------- 1 | module "network" { 2 | source = "Azure/network/azurerm" 3 | version = "2.0.0" 4 | resource_group_name = "myapp-networking" 5 | location = "eastus" 6 | 7 | tags = { 8 | environment = "dev" 9 | } 10 | } 11 | 12 | module "windowsservers" { 13 | source = "Azure/compute/azurerm" 14 | version = "1.1.7" 15 | resource_group_name = "myapp-compute-windows" 16 | location = "eastus" 17 | admin_password = "ComplxP@ssw0rd!" 18 | vm_os_simple = "WindowsServer" 19 | nb_public_ip = 0 20 | vnet_subnet_id = "${module.network.vnet_subnets[0]}" 21 | } 22 | 23 | module "linuxservers" { 24 | source = "Azure/compute/azurerm" 25 | version = "1.1.7" 26 | resource_group_name = "myapp-compute-linux" 27 | location = "eastus" 28 | vm_os_simple = "UbuntuServer" 29 | nb_public_ip = 0 30 | vnet_subnet_id = "${module.network.vnet_subnets[0]}" 31 | } 32 | --------------------------------------------------------------------------------