A support tool for use with Terraform stacks, Azure DevOps build pipelines, and GitHub projects/repos.
It currently has the following functions:
- initialising Terraform against remote state storage, for local execution
- queueing Terraform plans to build and destroy stacks in an Azure DevOps CI/CD pipeline
- cancelling unneeded releases of aforementioned builds
- creating GitHub issues in corresponding projects
All of these functions are executed contextually against a specific Terraform stack directory.
There are numerous installation options for stack
:
- Homebrew
- building from the source code hosted here
- directly downloading a pre-built binary for your desired platform
brew install jlucktay/tap/stack
brew upgrade jlucktay/tap/stack
You should have a working Go environment and have $GOPATH/bin
in your $PATH
.
To download the source, compile, and install the demo binary, run:
go get go.jlucktay.dev/stack
A newly-compiled stack
binary will be placed in $GOPATH/bin/
.
Binary releases can be downloaded here on GitHub.
There is a sample JSON file named stack.config.example.json
here in the root of this repo.
The search order that stack
follows when looking for the fully populated stack.config.json
config file is as
follows:
<current working directory>/stack.config.json
$HOME/.config/stack/stack.config.json
/etc/stack/stack.config.json
Filling out this config file will require the generation of two personal access tokens, one from Azure DevOps and one from GitHub. Links to the appropriate pages on each site are in the example file.
Under the .azure.subscriptions
section, all keys defined here will map verbatim to the parent directory's name when
stack
is executed. The values need to be set as GUIDs for the corresponding subscriptions in Azure.
Assume - for this example's sake - that the working directory is as follows:
/git/MyGitHubOrg/MyGitHubRepo/stack-prefix/subscription-alias/one/two/three/my-stack
Also assume that the following values are configured:
{
"azure": {
"state": {
"storageAccount": "mytfstatestorage"
},
"subscriptions": {
"subscription-alias": "01234567-89ab-cdef-0123-456789abcdef"
}
},
"github": {
"org": "MyGitHubOrg",
"repo": "MyGitHubRepo"
},
"stackPrefix": "stack-prefix"
}
The keys under .azure.subscriptions
map to the first child directory underneath the directory set under
.stackPrefix
so the sub-directory subscription-alias
(under stack-prefix
) would map to the subscription with a
GUID of 01234567-89ab-cdef-0123-456789abcdef
.
For remote state storage within the storage account, the key value is made up of three components:
- the
.stackPrefix
value (stack-prefix
in this example) - the name of the stack's direct parent directory (
three
) - the name of the stack directory itself (
my-stack
)
The container within the remote state storage account maps to the GUID of the subscription.
$ pwd
/git/MyGitHubOrg/MyGitHubRepo/stack-prefix/subscription-alias/one/two/three/my-stack
$ stack init
Switching subscriptions... done.
Retrieving storage account key... done.
Switching subscriptions... done.
Initialising Terraform with following dynamic values:
container_name: 01234567-89ab-cdef-0123-456789abcdef
key: stack-prefix.three.my-stack
storage_account: mytfstatestorage
...
Some of the functionality in stack
comes from executing other tools, which will need to be installed, configured,
authed, and available on your $PATH
:
stack
itself has several subcommands:
init
build
destroy
cancel
issue
version
Initialises the current Terraform stack directory using the Azure storage account for the remote state backend.
$ stack init
Switching subscriptions... done.
Retrieving storage account key... done.
Switching subscriptions... done.
Initialising Terraform with following dynamic values:
...
{
"azure": {
"state": {
"keyPrefix": "first of three segments for key names of state files within blob storage",
"resourceGroup": "name of resource group on Azure which contains the storage account",
"storageAccount": "name of Azure storage account where Terraform state is stored",
"subscription": "GUID of Azure subscription holding the state storage account"
},
"subscriptions": {
"a stack under '/<stackPrefix>/<this key>/<a stack name>/'": "will map to subscription associated with <this key>",
"exampleSubName": "subscription GUIDs go here",
"this key will be matched to a parent directory": "this value will map said directory to a specific subscription"
}
},
"stackPrefix": "/some/segment/of/repo/directory/structure/"
}
Queues a plan in Azure DevOps to build the Terraform stack in the current directory.
$ stack build
Stack (plan) URL: https://dev.azure.com/MyAzureDevOpsOrg/12345678-90ab-cdef-1234-567890abcdef/_build/results?buildId=1234
{
"azureDevOps": {
"buildDefID": 5,
"org": "the name of the organisation within Azure DevOps",
"pat": "52 character alphanumeric, generated here: https://dev.azure.com/<org>/_usersSettings/tokens",
"project": "the name of the project under the organisation within Azure DevOps"
},
"stackPrefix": "/some/segment/of/repo/directory/structure/"
}
If given, build from this branch. Defaults to the current branch.
If given, target these specific Terraform resources only. Delimit multiple target IDs with a comma ,
.
For example:
stack build --target="azurerm_resource_group.main,azurerm_virtual_machine.app,azurerm_virtual_machine.database"
Queues a plan in Azure DevOps to destroy the Terraform stack in the current directory.
Functionally identical to the stack build
subcommand, including --branch
and --target
optional arguments, with
the singular difference being that this subcommand references .azureDevOps.destroyDefID
in the config instead of
.azureDevOps.buildDefID
.
Cancels any pending releases in Azure DevOps.
Not yet implemented - coming soon!
Creates an issue in GitHub with a label referring to the current Terraform stack directory, and assigned to the current user.
The issue's body text is gathered by way of an interactive editor, designated by the current environment's EDITOR
variable.
$ stack issue -t "There's a problem with this stack!"
...
($EDITOR is launched to gather issue body)
...
New issue: https://github.com/MyGitHubOrg/MyGitHubRepo/issues/1234
{
"github": {
"org": "the name of the organisation within GitHub",
"pat": "<40 character hexadecimal, generated here: https://github.com/settings/tokens>",
"repo": "the name of the repository under the organisation within GitHub"
}
}
Displays version and build information for the current stack
binary.
There is a Dockerfile that is looped into make
and will be built by the default rule.
A typical execution of the Docker image from within your own stack directory needs to mount a volume under /workdir
and would look something like this:
docker run --rm --volume $(pwd):/workdir go.jlucktay.dev/stack[:<tag>]
The subcommands described above may be passed in by appending them to this command line, for example:
docker run --rm --volume $(pwd):/workdir go.jlucktay.dev/stack[:<tag>] version
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Please make sure to update tests as appropriate.
See also the contributing guide.