Tuesday, August 22, 2017

Building an Azure SQL Server with Terraform

I want to spend some more time learning Terraform, and I am hoping to build out a script to deploy the infrastructure for a pet project of mine, SleepySecurityNews.  I'd link the site here, but I'm almost certain it isn't functional right now as I have not maintained it in quite some time.

The first thing that my app requires is a SQL server database.  From an Azure perspective, this translates into two resources, an Azure SQL Server and an Azure SQL Database.  This is also how it is represented in Terraform.  The two resources are:
 So first, a couple of notes that I learned while doing this:


Terraform has the concept of variables.  Like ARM templates,  you can specify defaults in the original definition.  You can also pass in variables via a file, an environment variable, or on the command line at "apply" time.  It seems pretty cool.

Variables are stored in state, which by default is a local file on disk.  When you setup an Azure SQL Server, you do have to specify an admin password in the provider.  You will need to refer to the documentation on sensitive data to understand the implications.  This is something I need to look into further and will try to address in a separate blog post.

Concat doesn't work on strings, use format for that

Like ARM templates, there are several functions that you can call within the template.  Of course I instinctively used concat to merge two strings together.  Nope!  Apparently this feature was removed and the recommended advice is to use the format command to put the strings together.  Makes sense!

Timing Error

When I deployed the script, I got a weird timing error.  Terraform deployed the database server, and then when it tried to deploy the database itself, I got the following error:

Essentially, it was telling me that the db server does not exist (when it was trying to deploy the database itself).  Re-running the script again without any changes resolved the issue.  I could see this as a problem going forward, and will keep an eye when building out chains with dependent resources.

In any event, here is the full code that I used to deploy:

provider "azurerm" {
    subscription_id = ""
    client_id = ""
    client_secret = ""
    tenant_id = ""

variable "ssn_environment" {
    type = "string"
    default = "dev"
    description = "environment short"

variable "ssn_db_name" {
    type = "string"
    default = "ssn"
    description = "The name of the sql server db to use"

variable "ssn_db_server_name" {
    type = "string"
    default = "ssndbusw"
    description = "The name of the sql server to use"

variable "ssn_resource_group_name" {
    type = "string"
    default = "ssn_terraform"
    description = "The name of the resource group to deploy all resources"

variable "ssn_db_edition" {
    type = "string"
    default = "Basic"
    description = "The sql server edition"

variable "ssn_location" {
    type = "string"
    default = "West US"
    description = "The location to deploy resources to"

variable "ssn_db_server_login" {
    type = "string"
    default = "ssndbadmin"
    description = "The db server login name to use"

variable "ssn_db_password" {
    type = "string"
    default = ""
    description = "The password to use"

resource "azurerm_sql_server" "ssn_dbserver" {
    name = "${format("%s%s",var.ssn_environment,var.ssn_db_server_name)}"
    resource_group_name = "${var.ssn_resource_group_name}"
    location = "${var.ssn_location}"
    version = "12.0"
    administrator_login = "${var.ssn_db_server_login}"
    administrator_login_password = "${var.ssn_db_password}"

resource "azurerm_sql_database" "ssn_db" {
    name = "${var.ssn_db_name}"
    resource_group_name = "${var.ssn_resource_group_name}"
    server_name = "${format("%s%s",var.ssn_environment,var.ssn_db_server_name)}"
    edition = "${var.ssn_db_edition}"
    location = "${var.ssn_location}"

When running the apply, I specified a var on the command line.  Something like the following would work:

terraform.exe apply -var "ssn_db_password=<enter password here>"

In conclusion, we ran through a more complex Terraform setup that provisioned an Azure SQL server and database.  During this process, I learned more about variables, a bit about interpolation functions within the Terraform language, and ran into a timing issue.  I hope you are enjoying this journey in learning Terraform!

Sunday, August 13, 2017

Getting started with Terraform

When I look back at my time with Azure, I've made the progression from portal, to powershell, to ARM templates.  As I am writing more and more infrastructure-as-code, I'm always looking for better ways to execute deployments as part of a build/release pipeline.  For a while now, Terraform has been on my list of technologies to look in to.  The goal of this post is to provide a brief intro into terraform and build a simple storage account via template.

What is Terraform?

According to their website, Terraform is a "tool for building, changing, and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions."

Terraform excites me for a few reasons, namely:
  • The fact that there is a plan step, allowing you to confirm changes
  • The fact that it is open-source
  • The fact that you can add on other providers (custom or otherwise
Obviously, Terraform is an abstraction language, and comes with the various downfalls of this approach.  It'll be interesting to see where the limitations are.

Installing Terraform

There are two ways you can access terraform, and it really depends on what you are trying to do.  Firstly, and probably the easiest, is you can download a binary.  The second is that Hashicorp does release a docker container with the executable on it.   

Initially I had opted for the docker container, but I was having issues with my docker for windows setup and sharing drives, so I resorted to using the executable.

Configuring the AzureRM Provider

Terraform has the concepts of providers, and you need to initialize one to connect to AzureRM.  The provider configuration looks something like the following:

provider "azurerm" {
  subscription_id = "..."
  client_id       = "..."
  client_secret   = "..."
  tenant_id       = "..."

The authentication mechanism is using Service principals, and you will have to set something up.  There is a script that you can download to automate the process, but I just ran the commands manually.

What I like about the providers it that you can default the pulling of credentials to use environment variables.  This is a step up from putting the provider details in code.  I'm sure there are more secure ways of passing in credentials which I haven't gotten to yet.  In order to mitigate risk here, I only gave access to this service principal to the required resource group.

The storage account resource

The definition for this was surprisingly easy.  Here is my code:

resource "azurerm_storage_account" "shamtestterraform" {
    name = "shamtestterraform1"
    resource_group_name = "terraformTest"
    location = "westus"
    account_type = "Standard_LRS"

    tags {
        environment = "test"

You can find information on the resource definition using this link.

Deploying via Terraform

There are a couple of steps that you can perform to start a deployment via Terraform.

Step 1 is to run "terraform init"

Essentially this step confirms the provider is setup correctly and installs required modules/code to make everything run.

Step 2 is to run "terraform plan"

This is one of the main reasons why I like the idea of Terraform.  Essentially this step will refresh the current state and then compare the code being applied to the that state.  It will produce a report as to what will be created/updated/destroyed.

Here is a screenshot of what this looks like:

Pretty cool.

Step 3 is to run "terraform apply"

This essentially applies to code to the targets.  Creating a storage account is pretty simple and resulted in only one event in Azure.

What is interesting to note is that this does not create an ARM template and then do a deployment.  It is calling the API directly.  At a later date, I want to crack open the code and have a look and what it is actually doing.

Now that you are deployed, you could run "terraform destroy" to remove any created resources.

In conclusion, the goal of this post was to experiment with Terraform on a very simple Azure deployment.  So far, I like what I see, and I hope to create more with Terraform in the future.