Terraform Workspaces and Remote State

October 6, 2019
Contact Us
Weekly Shorts are topics we discuss in our weekly remote meeting related to recent work we have done with our customers
Terraform Workspaces and Remote State

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">

<head>

 <meta charset="utf-8" />

 <meta name="generator" content="pandoc" />

 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />

 <title>Terraform Workspaces and Remote State</title>

 <style>

     code{white-space: pre-wrap;}

     span.smallcaps{font-variant: small-caps;}

     span.underline{text-decoration: underline;}

     div.column{display: inline-block; vertical-align: top; width: 50%;}

 </style>

 <style>

code.sourceCode > span { display: inline-block; line-height: 1.25; }

code.sourceCode > span { color: inherit; text-decoration: inherit; }

code.sourceCode > span:empty { height: 1.2em; }

.sourceCode { overflow: visible; }

code.sourceCode { white-space: pre; position: relative; }

div.sourceCode { margin: 1em 0; }

pre.sourceCode { margin: 0; }

@media screen {

div.sourceCode { overflow: auto; }

}

@media print {

code.sourceCode { white-space: pre-wrap; }

code.sourceCode > span { text-indent: -5em; padding-left: 5em; }

}

pre.numberSource code

 { counter-reset: source-line 0; }

pre.numberSource code > span

 { position: relative; left: -4em; counter-increment: source-line; }

pre.numberSource code > span > a:first-child::before

 { content: counter(source-line);

   position: relative; left: -1em; text-align: right; vertical-align: baseline;

   border: none; display: inline-block;

   -webkit-touch-callout: none; -webkit-user-select: none;

   -khtml-user-select: none; -moz-user-select: none;

   -ms-user-select: none; user-select: none;

   padding: 0 4px; width: 4em;

   background-color: #232629;

   color: #7a7c7d;

 }

pre.numberSource { margin-left: 3em; border-left: 1px solid #7a7c7d;  padding-left: 4px; }

div.sourceCode

 { color: #cfcfc2; background-color: #232629; }

@media screen {

code.sourceCode > span > a:first-child::before { text-decoration: underline; }

}

code span. { color: #cfcfc2; } /* Normal */

code span.al { color: #95da4c; } /* Alert */

code span.an { color: #3f8058; } /* Annotation */

code span.at { color: #2980b9; } /* Attribute */

code span.bn { color: #f67400; } /* BaseN */

code span.bu { color: #7f8c8d; } /* BuiltIn */

code span.cf { color: #fdbc4b; } /* ControlFlow */

code span.ch { color: #3daee9; } /* Char */

code span.cn { color: #27aeae; } /* Constant */

code span.co { color: #7a7c7d; } /* Comment */

code span.cv { color: #7f8c8d; } /* CommentVar */

code span.do { color: #a43340; } /* Documentation */

code span.dt { color: #2980b9; } /* DataType */

code span.dv { color: #f67400; } /* DecVal */

code span.er { color: #da4453; } /* Error */

code span.ex { color: #0099ff; } /* Extension */

code span.fl { color: #f67400; } /* Float */

code span.fu { color: #8e44ad; } /* Function */

code span.im { color: #27ae60; } /* Import */

code span.in { color: #c45b00; } /* Information */

code span.kw { color: #cfcfc2; } /* Keyword */

code span.op { color: #cfcfc2; } /* Operator */

code span.ot { color: #27ae60; } /* Other */

code span.pp { color: #27ae60; } /* Preprocessor */

code span.re { color: #2980b9; } /* RegionMarker */

code span.sc { color: #3daee9; } /* SpecialChar */

code span.ss { color: #da4453; } /* SpecialString */

code span.st { color: #f44f4f; } /* String */

code span.va { color: #27aeae; } /* Variable */

code span.vs { color: #da4453; } /* VerbatimString */

code span.wa { color: #da4453; } /* Warning */

 </style>

 <!--[if lt IE 9]>

   <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>

 <![endif]-->

</head>

<body>

<header id="title-block-header">

<h1 class="title">Terraform Workspaces and Remote State</h1>

</header>

<p>As promised in my last article, <a href="https://dev.to/prodopsio/terraform-aws-dynamic-subnets-2cgo">Terraform AWS - Dynamic Subnets</a>, today you’re going to learn how to manage <a href="https://www.terraform.io/docs/state/workspaces.html">Workspaces</a> in Terraform, which are simply used for segregating your developing environments (dev, qa, stage, prod) while sharing the same infrastructure between them. We will also take advantage of the free <a href="https://app.terraform.io/session">Terraform Cloud</a> service to store the state file (tfstate) remotely.</p>

<h1 id="objectives">Objectives</h1>

<ol type="1">

<li>Share the same infrastructure as code (IaC) in multiple environments (Workspaces)</li>

<li>Store the <a href="https://www.terraform.io/docs/state/index.html">tfstate file</a> <a href="https://www.terraform.io/docs/state/remote.html">remotely</a> to allow colleagues to manage the infrastructure you’re working on</li>

</ol>

<h1 id="knowledge-and-assumptions">Knowledge and assumptions</h1>

<ol type="1">

<li>You read my <a href="https://dev.to/prodopsio/terraform-aws-dynamic-subnets-2cgo">Terraform AWS - Dynamic Subnets</a> tutorial which covers most of the Terraform functions that I’ll use in this tutorial</li>

<li>You know how to use <a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html">AWS named profiles</a></li>

<li>Your infrastructure’s repository is in <strong>one</strong> of the following: GitHub, GitLab, or Bitbucket. The repository I’ll be using is in <a href="https://github.com/unfor19/tf-tutorial-workspaces">Github</a></li>

<li>I’ll create a single S3 bucket resource. We will be able to create this resource in each environment just by switching Workspaces; we’ll get to that</li>

</ol>

<h1 id="setup">Setup</h1>

<h2 id="create-workspaces">Create Workspaces</h2>

<ol type="1">

<li>Create a free account in <a href="https://app.terraform.io/signup/account">Terraform Cloud</a></li>

<li>Create a new Organization; I’ve created <em>tutorials</em> organization</li>

<li>Create a new Workspace and connect it to the VCS provider. Select <strong>Only select repositories</strong> and select the relevant repository, in my case, it’s <em>tf-tutorial-workspaces</em></li>

<li>Click the repository’s name to continue</li>

<li>Workspace name should be the name of the environment you’re working on, in my case tf-tutorial-workspaces-<strong>development</strong> [Create-a-new-Workspace] (https://i.imgur.com/K6mBoSz.png)</li>

<li>(Optional) Configure Workspace Advanced Options - insert VCS branch if relevant. I’ll be using the <strong>master</strong> branch</li>

<li>Click <strong>Create workspace</strong>!</li>

<li>Repeat steps 3 to 7, in my case: tf-tutorial-workspaces-<strong>qa</strong>, tf-tutorial-workspaces-<strong>staging</strong>, tf-tutorial-workspaces-<strong>production</strong></li>

<li>For each Workspace that you’ve created, click Workspaces –&gt; click Workspace name –&gt; Settings –&gt; General –&gt; Execution Mode: <strong>Local</strong> <img src="https://i.imgur.com/L9KkSk6.png" alt="Execution-Mode-Local" /></li>

</ol>

<p><strong>Important</strong>: The default Execution Mode for each Workspace is Remote, which is the best practice for executing a Terraform plan. Unfortunately, there’s a bug, and Remote execution mode fails to work with the variable <code>terraform.workspace</code>. Make sure you set Execution Mode to Local until this bug is fixed, you can track it here, <a href="https://github.com/hashicorp/terraform/issues/22802">issue 22802</a>.</p>

<p>Workspaces page should look something like this: <img src="https://i.imgur.com/8K6liuI.png" alt="Workspaces-page" /></p>

<p>Now it’s time to generate a token that will allow us to use the Workspaces we’ve created.</p>

<h2 id="terraform-cloud-tokens">Terraform Cloud Tokens</h2>

<p>There are two types of tokens that we’re going to use, the first one is <strong>Team Token</strong>, which we will use in our automation processes and CI/CD pipeline. The second token is <strong>User Token</strong>, and we will use it for planning/applying infrastructure manually, so it’s usually good for planning and testing. In this tutorial, we’ll create both tokens.</p>

<p><em>Note</em>: In case you’re concerned about Two-Factor-Authentication, it will be ignored when you use Tokens as an authentication method, so keep that in mind.</p>

<h3 id="team-token">Team Token</h3>

<ol type="1">

<li>Create the file <strong>.terraformrc-team</strong> in your project’s directory, with Bash it’s <code>touch /.terraformrc-team</code></li>

<li>Generate a token in Terraform Cloud; click on Settings –&gt; Teams –&gt; Create an authentication token</li>

<li>Edit the file <strong>/.terraformrc-team</strong>, with Bash it’s: <code>vim /.terraformrc-team</code> then hit <strong>I</strong> for INSERT mode The file should look like this:</li>

</ol>

<div class="sourceCode" id="cb1"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1"></a><span class="ex">credentials</span> <span class="st">&quot;app.terraform.io&quot;</span> {</span>

<span id="cb1-2"><a href="#cb1-2"></a>   <span class="ex">token</span> = <span class="st">&quot;Your.generated.team.token.xctOeIoLtmjydg.jCCpJ6GKjCCpJe&quot;</span></span>

<span id="cb1-3"><a href="#cb1-3"></a>}</span></code></pre></div>

<p><em>Vim</em>: To save the file in vim, hit <em>ESC</em>, type: <code>:x</code> and hit <em>ENTER</em>. To make sure the file was correctly saved, execute <code>cat /.terraformrc</code>, the output should look like the above example. Here’s an excellent <a href="https://github.com/omerxx/vim-notebook/blob/master/VIM_NOTEBOOK.md">vim-notebook</a>. And if you’re asking yourself why to use vim, then read <a href="https://dev.to/omerxx/vim-from-foe-to-friend-in-9-minutes-2np0">Vim: from foe to friend in 9 minutes</a></p>

<h3 id="user-token">User Token</h3>

<ol type="1">

<li>Create the file <strong>.terraformrc-user</strong> in your project’s directory, with Bash it’s <code>touch /.terraformrc-user</code></li>

<li>Generate a token in Terraform Cloud; click on your profile picture –&gt; User Settings –&gt; <a href="https://app.terraform.io/app/settings/tokens">Tokens</a> –&gt; Generate token</li>

<li>Edit the file <strong>/.terraformrc-user</strong>, with Bash it’s: <code>vim /.terraformrc-user</code> then hit <strong>I</strong> for INSERT mode The file should look like this:</li>

</ol>

<div class="sourceCode" id="cb2"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1"></a><span class="ex">credentials</span> <span class="st">&quot;app.terraform.io&quot;</span> {</span>

<span id="cb2-2"><a href="#cb2-2"></a>   <span class="ex">token</span> = <span class="st">&quot;Your.generated.user.token.xctOeIoLtmjydg.jCCpJ6GKjCCpJe&quot;</span></span>

<span id="cb2-3"><a href="#cb2-3"></a>}</span></code></pre></div>

<h2 id="setting-up-aws-named-profiles">Setting up AWS named profiles</h2>

<p>To be able to apply the changes, we’ll create a named profile for each environment (Workspace).</p>

<p><em>Note</em>: Terraform developers decided to use the word Workspace instead of Environment due to the overuse of this word, see <a href="https://www.terraform.io/docs/state/workspaces.html">here</a>. Right choice (not kidding).</p>

<p>The credentials and config file should look like this: <code>~/.aws/credentials</code></p>

<div class="sourceCode" id="cb3"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1"></a>[<span class="ex">tf-tutorial-workspaces-development</span>]</span>

<span id="cb3-2"><a href="#cb3-2"></a><span class="ex">aws_access_key_id</span> = ACCESS_KEY_FOR_DEVELOPMENT</span>

<span id="cb3-3"><a href="#cb3-3"></a><span class="ex">aws_secret_access_key</span> = ZKBZ6_rsRFJx+bU2#=jY]w%u_e!Xrau?9fc!}:}<span class="ex">c</span></span>

<span id="cb3-4"><a href="#cb3-4"></a></span>

<span id="cb3-5"><a href="#cb3-5"></a>[<span class="ex">tf-tutorial-workspaces-qa</span>]</span>

<span id="cb3-6"><a href="#cb3-6"></a><span class="ex">aws_access_key_id</span> = ACCESS_KEY_FOR_QA</span>

<span id="cb3-7"><a href="#cb3-7"></a><span class="ex">aws_secret_access_key</span> = K9N?Bjb:w<span class="op">&gt;</span>Uyw9w.k^,Ap2BK-7CbsZ^fY*J3t}<span class="ex">vp</span></span>

<span id="cb3-8"><a href="#cb3-8"></a></span>

<span id="cb3-9"><a href="#cb3-9"></a>[<span class="ex">tf-tutorial-workspaces-staging</span>]</span>

<span id="cb3-10"><a href="#cb3-10"></a><span class="ex">aws_access_key_id</span> = ACCESS_KEY_FOR_STAGING</span>

<span id="cb3-11"><a href="#cb3-11"></a><span class="ex">aws_secret_access_key</span> = VpKHs2*Urp3BhE3j~MVC9@W<span class="kw">&amp;</span><span class="ex">TpR.aQu?s.n.PrBP</span></span>

<span id="cb3-12"><a href="#cb3-12"></a></span>

<span id="cb3-13"><a href="#cb3-13"></a>[<span class="ex">tf-tutorial-workspaces-production</span>]</span>

<span id="cb3-14"><a href="#cb3-14"></a><span class="ex">aws_access_key_id</span> = ACCESS_KEY_FOR_PRODUCTION</span>

<span id="cb3-15"><a href="#cb3-15"></a><span class="ex">aws_secret_access_key</span> = Kk~Xo<span class="kw">&amp;</span><span class="ex">Z</span><span class="op">&gt;</span>3QKi-M%Vq6]LRLNAwy<span class="op">&gt;</span>7R-q4=C2rGJ8x</span></code></pre></div>

<p><code>~/.aws/config</code></p>

<div class="sourceCode" id="cb4"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1"></a>[<span class="ex">profile</span> tf-tutorial-workspaces-development]</span>

<span id="cb4-2"><a href="#cb4-2"></a><span class="ex">output</span> = json</span>

<span id="cb4-3"><a href="#cb4-3"></a></span>

<span id="cb4-4"><a href="#cb4-4"></a>[<span class="ex">profile</span> tf-tutorial-workspaces-qa]</span>

<span id="cb4-5"><a href="#cb4-5"></a><span class="ex">output</span> = json</span>

<span id="cb4-6"><a href="#cb4-6"></a></span>

<span id="cb4-7"><a href="#cb4-7"></a>[<span class="ex">profile</span> tf-tutorial-workspaces-staging]</span>

<span id="cb4-8"><a href="#cb4-8"></a><span class="ex">output</span> = json</span>

<span id="cb4-9"><a href="#cb4-9"></a></span>

<span id="cb4-10"><a href="#cb4-10"></a>[<span class="ex">profile</span> tf-tutorial-workspaces-prodcution]</span>

<span id="cb4-11"><a href="#cb4-11"></a><span class="ex">output</span> = json</span></code></pre></div>

<h2 id="configuring-the-backend">Configuring the backend</h2>

<p>Now we need to configure our infrastructure to use <a href="https://www.terraform.io/docs/backends/types/remote.html">Terraform’s Remote backend</a>.</p>

<p>Project structure:</p>

<div class="sourceCode" id="cb5"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1"></a><span class="ex">.</span></span>

<span id="cb5-2"><a href="#cb5-2"></a>├── <span class="ex">LICENSE</span></span>

<span id="cb5-3"><a href="#cb5-3"></a>├── <span class="ex">README.md</span></span>

<span id="cb5-4"><a href="#cb5-4"></a>├── <span class="ex">main.tf</span></span>

<span id="cb5-5"><a href="#cb5-5"></a>└── <span class="ex">variables.tf</span></span></code></pre></div>

<h3 id="main.tf">main.tf</h3>

<p>First, let’s set up <code>main.tf</code>:</p>

<p><code>main.tf</code></p>

<div class="sourceCode" id="cb6"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1"></a><span class="ex">provider</span> <span class="st">&quot;aws&quot;</span> {</span>

<span id="cb6-2"><a href="#cb6-2"></a>  <span class="ex">version</span> = <span class="st">&quot;~&gt; 2.28&quot;</span></span>

<span id="cb6-3"><a href="#cb6-3"></a>  <span class="ex">profile</span> = lookup(local.profile, local.environment)</span>

<span id="cb6-4"><a href="#cb6-4"></a>  <span class="ex">region</span>  = lookup(local.region, local.environment)</span>

<span id="cb6-5"><a href="#cb6-5"></a>}</span>

<span id="cb6-6"><a href="#cb6-6"></a></span>

<span id="cb6-7"><a href="#cb6-7"></a><span class="ex">terraform</span> {</span>

<span id="cb6-8"><a href="#cb6-8"></a>  <span class="ex">required_version</span> = <span class="st">&quot;~&gt; 0.12&quot;</span></span>

<span id="cb6-9"><a href="#cb6-9"></a>  <span class="ex">backend</span> <span class="st">&quot;remote&quot;</span> {</span>

<span id="cb6-10"><a href="#cb6-10"></a>    <span class="fu">hostname</span>     = <span class="st">&quot;app.terraform.io&quot;</span></span>

<span id="cb6-11"><a href="#cb6-11"></a>    <span class="ex">organization</span> = <span class="st">&quot;tutorials&quot;</span></span>

<span id="cb6-12"><a href="#cb6-12"></a>    <span class="ex">workspaces</span> { prefix = <span class="st">&quot;tf-tutorial-workspaces-&quot;</span> }</span>

<span id="cb6-13"><a href="#cb6-13"></a>  }</span>

<span id="cb6-14"><a href="#cb6-14"></a>}</span></code></pre></div>

<p>Some explanations to the code above: 1. <em>profile</em> - Will be selected according to environment (Workspace) 2. <em>region</em> - Will be selected according to environment (Workspace) 3. <em>backend “remote” {}</em> - configuring our backend - <em>hostname</em> - This is the configuration that tells our tfstate to be hosted remotely in <em>app.terraform.io</em> (Terraform Cloud) - <em>organization</em> - the organization that we’ve created in Terraform Cloud - <em>workspaces</em> - Since we’re using multiple Workspaces (environments), we need to use the keyword <em>prefix</em>. Take the prefix of your workspaces and add a dash (<em>-</em>) to the end of it, just like in the code above</p>

<p><strong>Important</strong>: The backend’s configuration currently does not support using variables/local values. We have to hardcode our prefix; otherwise, I would’ve used <code>"${local.profile_prefix}-"</code></p>

<h3 id="variables.tf">variables.tf</h3>

<p>Moving on to the <code>variables.tf</code> file, this is where the magic happens. Before I share 30 lines of code with you, let’s break it down for our needs and how we answer those needs.</p>

<ol type="1">

<li><em>app_name</em> - We need to give a name to our application, this will serve as a prefix for all of our resources</li>

<li><em>profile_prefix</em> - For convenience, will be used in the local value <em>profile</em></li>

<li><em>profile</em> - We need a map for profile per environment (Workspace), used in <code>main.tf</code></li>

<li><em>region</em> - We need a map for region per environment (Workspace), also used in <code>main.tf</code></li>

<li><em>environment</em> - We need to initialize this variable with the name of the Workspace we’re currently using, luckily we have <code>terraform.workspace</code> for doing that</li>

<li><em>common_tags</em> - For convenience, we will use it in all resources, it will help us mark the resources that are managed by Terraform</li>

<li><em>name_prefix</em> - For convenience, we will use it in all resources</li>

</ol>

<p><code>variables.tf</code></p>

<div class="sourceCode" id="cb7"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1"></a><span class="ex">locals</span> {</span>

<span id="cb7-2"><a href="#cb7-2"></a>  <span class="ex">app_name</span>       = <span class="st">&quot;workspaces-app&quot;</span></span>

<span id="cb7-3"><a href="#cb7-3"></a>  <span class="ex">profile_prefix</span> = <span class="st">&quot;tf-tutorial-workspaces&quot;</span></span>

<span id="cb7-4"><a href="#cb7-4"></a>}</span>

<span id="cb7-5"><a href="#cb7-5"></a></span>

<span id="cb7-6"><a href="#cb7-6"></a><span class="ex">locals</span> {</span>

<span id="cb7-7"><a href="#cb7-7"></a>  <span class="ex">profile</span> = {</span>

<span id="cb7-8"><a href="#cb7-8"></a>    <span class="st">&quot;development&quot;</span> = <span class="st">&quot;</span><span class="va">${local</span><span class="er">.profile_prefix</span><span class="va">}</span><span class="st">-development&quot;</span></span>

<span id="cb7-9"><a href="#cb7-9"></a>    <span class="st">&quot;qa&quot;</span>          = <span class="st">&quot;</span><span class="va">${local</span><span class="er">.profile_prefix</span><span class="va">}</span><span class="st">-qa&quot;</span></span>

<span id="cb7-10"><a href="#cb7-10"></a>    <span class="st">&quot;staging&quot;</span>     = <span class="st">&quot;</span><span class="va">${local</span><span class="er">.profile_prefix</span><span class="va">}</span><span class="st">-staging&quot;</span></span>

<span id="cb7-11"><a href="#cb7-11"></a>    <span class="st">&quot;production&quot;</span>  = <span class="st">&quot;</span><span class="va">${local</span><span class="er">.profile_prefix</span><span class="va">}</span><span class="st">-production&quot;</span></span>

<span id="cb7-12"><a href="#cb7-12"></a>  }</span>

<span id="cb7-13"><a href="#cb7-13"></a>  <span class="ex">region</span> = {</span>

<span id="cb7-14"><a href="#cb7-14"></a>    <span class="st">&quot;development&quot;</span> = <span class="st">&quot;us-west-2&quot;</span></span>

<span id="cb7-15"><a href="#cb7-15"></a>    <span class="st">&quot;qa&quot;</span>          = <span class="st">&quot;us-east-2&quot;</span></span>

<span id="cb7-16"><a href="#cb7-16"></a>    <span class="st">&quot;staging&quot;</span>     = <span class="st">&quot;us-east-1&quot;</span></span>

<span id="cb7-17"><a href="#cb7-17"></a>    <span class="st">&quot;production&quot;</span>  = <span class="st">&quot;ca-central-1&quot;</span></span>

<span id="cb7-18"><a href="#cb7-18"></a>  }</span>

<span id="cb7-19"><a href="#cb7-19"></a>}</span>

<span id="cb7-20"><a href="#cb7-20"></a></span>

<span id="cb7-21"><a href="#cb7-21"></a><span class="ex">locals</span> {</span>

<span id="cb7-22"><a href="#cb7-22"></a>  <span class="ex">environment</span> = <span class="st">&quot;</span><span class="va">${terraform</span><span class="er">.workspace</span><span class="va">}</span><span class="st">&quot;</span></span>

<span id="cb7-23"><a href="#cb7-23"></a>}</span>

<span id="cb7-24"><a href="#cb7-24"></a></span>

<span id="cb7-25"><a href="#cb7-25"></a><span class="ex">locals</span> {</span>

<span id="cb7-26"><a href="#cb7-26"></a>  <span class="ex">common_tags</span> = {</span>

<span id="cb7-27"><a href="#cb7-27"></a>    <span class="ex">Terraform</span>   = <span class="st">&quot;true&quot;</span></span>

<span id="cb7-28"><a href="#cb7-28"></a>    <span class="ex">Environment</span> = local.environment</span>

<span id="cb7-29"><a href="#cb7-29"></a>  }</span>

<span id="cb7-30"><a href="#cb7-30"></a>  <span class="ex">name_prefix</span> = <span class="st">&quot;</span><span class="va">${local</span><span class="er">.app_name</span><span class="va">}</span><span class="st">-</span><span class="va">${local</span><span class="er">.environment</span><span class="va">}</span><span class="st">&quot;</span></span>

<span id="cb7-31"><a href="#cb7-31"></a>}</span></code></pre></div>

<p><em>Note</em>: I split the local values into four groups to make it more readable and organized.</p>

<p>Everything is ready! Now we need to initialize Terraform to make it work with Workspaces and a remote backend that stores tfstate.</p>

<h2 id="select-relevant-terraformrc-configuration-file">Select relevant terraformrc configuration file</h2>

<p>We’ve created the files <code>terraformrc-team</code> and <code>terraformrc-user</code>, since we’re doing manual work and we’re not executing CI/CD pipeline, we’ll use the <code>terraformrc-user</code>. The environment variable <a href="https://www.terraform.io/docs/commands/environment-variables.html#tf_cli_config_file">TF_CLI_CONFIG_FILE</a> defines the location of the configuration file that will be used for the current run.</p>

<p>Assuming your current working directory is your project’s directory:</p>

<div class="sourceCode" id="cb8"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb8-1"><a href="#cb8-1"></a><span class="bu">export</span> <span class="va">TF_CLI_CONFIG_FILE=</span><span class="st">&quot;</span><span class="va">${PWD}</span><span class="st">/.terraformrc-user&quot;</span></span></code></pre></div>

<p>You can set this environment variable automatically on system startup, follow these instructions: <a href="https://unix.stackexchange.com/a/21600/368610">Windows Git Bash</a>, <a href="https://askubuntu.com/a/58828/961789">Ubuntu</a>, <a href="https://apple.stackexchange.com/a/106823">MacOS</a>. Make sure you replace <code>${PWD}</code> with the absolute path to <code>.terraformrc-user</code></p>

<h2 id="initialize-terraform">Initialize Terraform</h2>

<p>Make sure you’re in the repository’s working dir, in my case it’s <code>./tf-tutorial-workspaces</code>.</p>

<p>Execute:</p>

<div class="sourceCode" id="cb9"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb9-1"><a href="#cb9-1"></a><span class="ex">terraform</span> init</span></code></pre></div>

<p>When prompted to select a Workspace, insert one (1), and hit <em>ENTER</em>, it doesn’t matter at this point.</p>

<p>Expected output:</p>

<div class="sourceCode" id="cb10"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1"></a><span class="ex">Initializing</span> the backend...</span>

<span id="cb10-2"><a href="#cb10-2"></a></span>

<span id="cb10-3"><a href="#cb10-3"></a><span class="ex">The</span> currently selected workspace (default) <span class="ex">does</span> not exist.</span>

<span id="cb10-4"><a href="#cb10-4"></a>  <span class="ex">This</span> is expected behavior when the selected workspace did not have an</span>

<span id="cb10-5"><a href="#cb10-5"></a>  <span class="ex">existing</span> non-empty state. Please enter a number to select a workspace:</span>

<span id="cb10-6"><a href="#cb10-6"></a></span>

<span id="cb10-7"><a href="#cb10-7"></a>  <span class="ex">1.</span> development</span>

<span id="cb10-8"><a href="#cb10-8"></a>  <span class="ex">2.</span> production</span>

<span id="cb10-9"><a href="#cb10-9"></a>  <span class="ex">3.</span> qa</span>

<span id="cb10-10"><a href="#cb10-10"></a>  <span class="ex">4.</span> staging</span>

<span id="cb10-11"><a href="#cb10-11"></a></span>

<span id="cb10-12"><a href="#cb10-12"></a>  <span class="ex">Enter</span> a value: 1</span>

<span id="cb10-13"><a href="#cb10-13"></a></span>

<span id="cb10-14"><a href="#cb10-14"></a></span>

<span id="cb10-15"><a href="#cb10-15"></a><span class="ex">Initializing</span> provider plugins...</span>

<span id="cb10-16"><a href="#cb10-16"></a><span class="ex">-</span> Checking for available provider plugins...</span>

<span id="cb10-17"><a href="#cb10-17"></a><span class="ex">-</span> Downloading plugin for provider <span class="st">&quot;aws&quot;</span> (hashicorp/aws) <span class="ex">2.29.0...</span></span>

<span id="cb10-18"><a href="#cb10-18"></a></span>

<span id="cb10-19"><a href="#cb10-19"></a><span class="ex">Terraform</span> has been successfully initialized!</span>

<span id="cb10-20"><a href="#cb10-20"></a></span>

<span id="cb10-21"><a href="#cb10-21"></a><span class="co"># omitted the rest of the text for brevity</span></span></code></pre></div>

<h3 id="troubleshooting---terraform-init">Troubleshooting - terraform init</h3>

<h4 id="local.environment-is-default">“local.environment is default”</h4>

<p>This error happens when Execution Mode is Remote in the Workspace’s settings. The Remote Execution Mode doesn’t work with the <code>terraform.workspace</code> variable, so make sure you set it in the Workspace’s settings to Local, at least until the <a href="https://github.com/hashicorp/terraform/issues/22802">issue22802</a> is fixed.</p>

<h1 id="share-tfstate-and-workspaces-with-colleagues">Share tfstate and Workspaces with colleagues</h1>

<p>Everything we did so far was the initial setup. Now tell your colleagues to: 1. Create an account in Terraform Cloud - and then you need to add them to you your organization, click organization name –&gt; Settings –&gt; Teams –&gt; owners –&gt; Add a New Member - If you can’t see <strong>owners</strong>, then it’s ok, click on Add Users and it will add this user to the <strong>owners</strong> team. The <strong>owners</strong> team is the default team 2. Configure Terraform credentials with user token in <code>.terraformrc-user</code> - just like you did earlier. Don’t forget to select TF_CLI_CONFIG_FILE 3. Create AWS named profiles (config, credentials), just like you did earlier 4. Execute <code>terraform init</code></p>

<p>Your colleagues will now be using the same tfstate file you’re using, and they can access the Workspaces that you’ve already created.</p>

<p><em>Note</em>: Instead of adding users to the <code>owners</code> team, you should create a team per department/actual team, for example, developers-team, operations-team, etc.</p>

<h1 id="working-with-workspaces">Working with Workspaces</h1>

<h2 id="select-workspace-environment">Select Workspace (environment)</h2>

<p>You’ll be amazed by how simple it is to select the environment, here are the available commands:</p>

<ol type="1">

<li><code>terraform workspace list</code> - List available Workspaces</li>

<li><code>terraform workspace select workspace_name</code> - Select relevant Workspace</li>

<li><code>terraform workspace show</code> - Shows the selected Workspace</li>

<li><code>terraform apply</code> - Apply changes to infrastructure</li>

<li><code>terraform destroy</code> - Destroy infrastructure</li>

</ol>

<h1 id="example-for-usage">Example for usage</h1>

<h2 id="adding-resources-to-your-git-repository">Adding resources to your git repository</h2>

<p>Let’s add an S3 bucket:</p>

<p><code>s3.tf</code></p>

<div class="sourceCode" id="cb11"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1"></a><span class="ex">resource</span> <span class="st">&quot;aws_s3_bucket&quot;</span> <span class="st">&quot;bucket&quot;</span> {</span>

<span id="cb11-2"><a href="#cb11-2"></a>  <span class="ex">count</span>  = <span class="st">&quot;</span><span class="va">${lookup</span><span class="er">(local.create_s3</span><span class="va">,</span> local.environment)<span class="va">}</span><span class="st">&quot;</span></span>

<span id="cb11-3"><a href="#cb11-3"></a>  <span class="ex">bucket</span> = <span class="st">&quot;</span><span class="va">${local</span><span class="er">.name_prefix</span><span class="va">}</span><span class="st">-s3&quot;</span></span>

<span id="cb11-4"><a href="#cb11-4"></a>  <span class="ex">acl</span>    = <span class="st">&quot;private&quot;</span></span>

<span id="cb11-5"><a href="#cb11-5"></a>  <span class="ex">region</span> = <span class="st">&quot;</span><span class="va">${lookup</span><span class="er">(local.region</span><span class="va">,</span> local.environment)<span class="va">}</span><span class="st">&quot;</span></span>

<span id="cb11-6"><a href="#cb11-6"></a></span>

<span id="cb11-7"><a href="#cb11-7"></a>  <span class="ex">versioning</span> {</span>

<span id="cb11-8"><a href="#cb11-8"></a>    <span class="ex">enabled</span> = true</span>

<span id="cb11-9"><a href="#cb11-9"></a>  }</span>

<span id="cb11-10"><a href="#cb11-10"></a>  <span class="ex">tags</span> = local.common_tags</span>

<span id="cb11-11"><a href="#cb11-11"></a>}</span></code></pre></div>

<p><code>s3.variables.tf</code> - A good example of how we can control the creation of resources per environment.</p>

<div class="sourceCode" id="cb12"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb12-1"><a href="#cb12-1"></a><span class="ex">locals</span> {</span>

<span id="cb12-2"><a href="#cb12-2"></a>  <span class="ex">create_s3</span> = {</span>

<span id="cb12-3"><a href="#cb12-3"></a>    <span class="st">&quot;development&quot;</span> = <span class="ex">0</span></span>

<span id="cb12-4"><a href="#cb12-4"></a>    <span class="st">&quot;qa&quot;</span>          = <span class="ex">1</span></span>

<span id="cb12-5"><a href="#cb12-5"></a>    <span class="st">&quot;staging&quot;</span>     = <span class="ex">1</span></span>

<span id="cb12-6"><a href="#cb12-6"></a>    <span class="st">&quot;production&quot;</span>  = <span class="ex">1</span></span>

<span id="cb12-7"><a href="#cb12-7"></a>  }</span>

<span id="cb12-8"><a href="#cb12-8"></a>}</span></code></pre></div>

<p>The current project structure:</p>

<div class="sourceCode" id="cb13"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb13-1"><a href="#cb13-1"></a><span class="ex">.</span></span>

<span id="cb13-2"><a href="#cb13-2"></a>├── <span class="ex">LICENSE</span></span>

<span id="cb13-3"><a href="#cb13-3"></a>├── <span class="ex">README.md</span></span>

<span id="cb13-4"><a href="#cb13-4"></a>├── <span class="ex">main.tf</span></span>

<span id="cb13-5"><a href="#cb13-5"></a>├── <span class="ex">s3.tf</span></span>

<span id="cb13-6"><a href="#cb13-6"></a>├── <span class="ex">s3.variables.tf</span></span>

<span id="cb13-7"><a href="#cb13-7"></a>└── <span class="ex">variables.tf</span></span></code></pre></div>

<p>This project is available on GitHub: {% github https://github.com/unfor19/tf-tutorial-workspaces no-readme %}</p>

<h2 id="example">Example</h2>

<div class="sourceCode" id="cb14"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb14-1"><a href="#cb14-1"></a>$ <span class="ex">terraform</span> workspace list</span>

<span id="cb14-2"><a href="#cb14-2"></a><span class="ex">*</span> development</span>

<span id="cb14-3"><a href="#cb14-3"></a>  <span class="ex">production</span></span>

<span id="cb14-4"><a href="#cb14-4"></a>  <span class="ex">qa</span></span>

<span id="cb14-5"><a href="#cb14-5"></a>  <span class="ex">staging</span></span>

<span id="cb14-6"><a href="#cb14-6"></a>$ <span class="ex">terraform</span> workspace select qa</span>

<span id="cb14-7"><a href="#cb14-7"></a>$ <span class="ex">terraform</span> apply</span>

<span id="cb14-8"><a href="#cb14-8"></a><span class="ex">Acquiring</span> state lock. This may take a few moments...</span>

<span id="cb14-9"><a href="#cb14-9"></a></span>

<span id="cb14-10"><a href="#cb14-10"></a><span class="co"># omitted text for brevity</span></span>

<span id="cb14-11"><a href="#cb14-11"></a></span>

<span id="cb14-12"><a href="#cb14-12"></a>  <span class="co"># aws_s3_bucket.bucket[0] will be created</span></span>

<span id="cb14-13"><a href="#cb14-13"></a>  <span class="ex">+</span> resource <span class="st">&quot;aws_s3_bucket&quot;</span> <span class="st">&quot;bucket&quot;</span> {</span>

<span id="cb14-14"><a href="#cb14-14"></a>      <span class="ex">+</span> acceleration_status         = (known after apply)</span>

<span id="cb14-15"><a href="#cb14-15"></a>      <span class="ex">+</span> acl                         = <span class="st">&quot;private&quot;</span></span>

<span id="cb14-16"><a href="#cb14-16"></a>      <span class="ex">+</span> arn                         = (known after apply)</span>

<span id="cb14-17"><a href="#cb14-17"></a>      <span class="ex">+</span> bucket                      = <span class="st">&quot;workspaces-app-qa-s3&quot;</span></span>

<span id="cb14-18"><a href="#cb14-18"></a>      <span class="ex">+</span> bucket_domain_name          = (known after apply)</span>

<span id="cb14-19"><a href="#cb14-19"></a>      <span class="ex">+</span> bucket_regional_domain_name = (known after apply)</span>

<span id="cb14-20"><a href="#cb14-20"></a>      <span class="ex">+</span> force_destroy               = false</span>

<span id="cb14-21"><a href="#cb14-21"></a>      <span class="ex">+</span> hosted_zone_id              = (known after apply)</span>

<span id="cb14-22"><a href="#cb14-22"></a>      <span class="ex">+</span> id                          = (known after apply)</span>

<span id="cb14-23"><a href="#cb14-23"></a>      <span class="ex">+</span> region                      = <span class="st">&quot;us-east-2&quot;</span></span>

<span id="cb14-24"><a href="#cb14-24"></a>      <span class="ex">+</span> request_payer               = (known after apply)</span>

<span id="cb14-25"><a href="#cb14-25"></a>      <span class="ex">+</span> tags                        = {</span>

<span id="cb14-26"><a href="#cb14-26"></a>          <span class="ex">+</span> <span class="st">&quot;Environment&quot;</span> = <span class="st">&quot;qa&quot;</span></span>

<span id="cb14-27"><a href="#cb14-27"></a>          <span class="ex">+</span> <span class="st">&quot;Terraform&quot;</span>   = <span class="st">&quot;true&quot;</span></span>

<span id="cb14-28"><a href="#cb14-28"></a>        }</span>

<span id="cb14-29"><a href="#cb14-29"></a><span class="co"># omitted arguments for brevity</span></span>

<span id="cb14-30"><a href="#cb14-30"></a>    }</span>

<span id="cb14-31"><a href="#cb14-31"></a></span>

<span id="cb14-32"><a href="#cb14-32"></a><span class="ex">Plan</span>: 1 to add, 0 to change, 0 to destroy.</span>

<span id="cb14-33"><a href="#cb14-33"></a></span>

<span id="cb14-34"><a href="#cb14-34"></a><span class="ex">Do</span> you want to perform these actions in workspace <span class="st">&quot;qa&quot;</span>?</span>

<span id="cb14-35"><a href="#cb14-35"></a>  <span class="ex">Terraform</span> will perform the actions described above.</span>

<span id="cb14-36"><a href="#cb14-36"></a>  <span class="ex">Only</span> <span class="st">&#39;yes&#39;</span> will be accepted to approve.</span>

<span id="cb14-37"><a href="#cb14-37"></a></span>

<span id="cb14-38"><a href="#cb14-38"></a>  <span class="ex">Enter</span> a value:</span></code></pre></div>

<p>Look at the S3 bucket name and region! Everything indicates that we’re working on the qa Workspace, woohoo!</p>

<h4 id="troubleshooting---terraform-plan">Troubleshooting - terraform plan</h4>

<div class="sourceCode" id="cb15"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb15-1"><a href="#cb15-1"></a><span class="ex">Error</span>: error starting operation: The configured <span class="st">&quot;remote&quot;</span> backend encountered an unexpected error:</span>

<span id="cb15-2"><a href="#cb15-2"></a></span>

<span id="cb15-3"><a href="#cb15-3"></a><span class="ex">invalid</span> value for workspace</span></code></pre></div>

<p>You executed a <code>terraform plan</code> or <code>terraform apply</code> without selecting a Workspace. Make sure you select a Workspace first.</p>

<h1 id="final-words">Final words</h1>

<p>Terraform Cloud service is still new, but it’s fantastic, and if you have any questions or comments, fire at will!</p>

<p>My next article will be about how to use 3rd-party binaries, such as aws-vault and Terraform, in Windows Git Bash.</p>

<p>Did you like this tutorial? Clap/heart/unicorn and share it with your friends and colleagues.</p>

</body>

</html>

Terraform Workspaces and Remote State
Meir Gabay
Junior Operations Engineer
Meir is an experienced automation engineer and a certified AWS solutions architect. He has four years of experience as Operations and Automation at NICE's training department. With knowledge in web development, both server-side and front-end. The critical thing that motivates him while working with customers - "To continue evolving and improving independently, a customer needs to know how the solution works." While Meir is not helping customers, you'll find him studying new technologies and writing a blog post of what he had learned.