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/stackbrew upgrade jlucktay/tap/stackYou 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/stackA 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-stackAlso 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
.stackPrefixvalue (stack-prefixin 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:
initbuilddestroycancelissueversion
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>] versionPull 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.