{"id":25319786,"url":"https://github.com/jlucktay/stack","last_synced_at":"2026-05-03T22:31:28.991Z","repository":{"id":39633252,"uuid":"195425748","full_name":"jlucktay/stack","owner":"jlucktay","description":"A support tool for use with Terraform stacks, Azure DevOps build pipelines, and GitHub projects/repos.","archived":false,"fork":false,"pushed_at":"2024-02-12T13:51:19.000Z","size":535,"stargazers_count":1,"open_issues_count":18,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-25T13:18:21.763Z","etag":null,"topics":["azure-devops","cli","github","terraform"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jlucktay.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-07-05T14:47:18.000Z","updated_at":"2021-12-20T13:55:11.000Z","dependencies_parsed_at":"2024-06-21T01:06:32.702Z","dependency_job_id":"b54dd5b7-f419-4f20-a5fc-a9d7ddb54893","html_url":"https://github.com/jlucktay/stack","commit_stats":null,"previous_names":[],"tags_count":50,"template":false,"template_full_name":null,"purl":"pkg:github/jlucktay/stack","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlucktay%2Fstack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlucktay%2Fstack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlucktay%2Fstack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlucktay%2Fstack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jlucktay","download_url":"https://codeload.github.com/jlucktay/stack/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlucktay%2Fstack/sbom","scorecard":{"id":523682,"data":{"date":"2025-08-11","repo":{"name":"github.com/jlucktay/stack","commit":"f726ae12e499e32db0e9b8ed22a6ff662a84a0bc"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/17 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/auto-merge-dependabot.yml:1","Warn: no topLevel permission defined: .github/workflows/go.yaml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: third-party GitHubAction not pinned by hash: .github/workflows/auto-merge-dependabot.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/jlucktay/stack/auto-merge-dependabot.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yaml:39: update your workflow using https://app.stepsecurity.io/secureworkflow/jlucktay/stack/go.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yaml:44: update your workflow using https://app.stepsecurity.io/secureworkflow/jlucktay/stack/go.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yaml:50: update your workflow using https://app.stepsecurity.io/secureworkflow/jlucktay/stack/go.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yaml:91: update your workflow using https://app.stepsecurity.io/secureworkflow/jlucktay/stack/go.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yaml:101: update your workflow using https://app.stepsecurity.io/secureworkflow/jlucktay/stack/go.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/go.yaml:106: update your workflow using https://app.stepsecurity.io/secureworkflow/jlucktay/stack/go.yaml/main?enable=pin","Warn: containerImage not pinned by hash: Dockerfile:1","Info:   0 out of   5 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned","Info:   0 out of   1 containerImage dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v0.11.3 not signed: https://api.github.com/repos/jlucktay/stack/releases/55094491","Warn: release artifact v0.11.1 not signed: https://api.github.com/repos/jlucktay/stack/releases/51693801","Warn: release artifact v0.11.0 not signed: https://api.github.com/repos/jlucktay/stack/releases/29195759","Warn: release artifact v0.10.3 not signed: https://api.github.com/repos/jlucktay/stack/releases/28770267","Warn: release artifact v0.10.2 not signed: https://api.github.com/repos/jlucktay/stack/releases/28637971","Warn: release artifact v0.11.3 does not have provenance: https://api.github.com/repos/jlucktay/stack/releases/55094491","Warn: release artifact v0.11.1 does not have provenance: https://api.github.com/repos/jlucktay/stack/releases/51693801","Warn: release artifact v0.11.0 does not have provenance: https://api.github.com/repos/jlucktay/stack/releases/29195759","Warn: release artifact v0.10.3 does not have provenance: https://api.github.com/repos/jlucktay/stack/releases/28770267","Warn: release artifact v0.10.2 does not have provenance: https://api.github.com/repos/jlucktay/stack/releases/28637971"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":5,"reason":"5 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2024-2456 / GHSA-449p-3h89-pw88","Warn: Project is vulnerable to: GO-2024-2466 / GHSA-mw99-9chc-xw7r","Warn: Project is vulnerable to: GO-2025-3367 / GHSA-r9px-m959-cxf4","Warn: Project is vulnerable to: GO-2025-3368 / GHSA-v725-9546-7q7m","Warn: Project is vulnerable to: GO-2025-3488 / GHSA-6v2p-p543-phr9"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T03:45:29.118Z","repository_id":39633252,"created_at":"2025-08-20T03:45:29.118Z","updated_at":"2025-08-20T03:45:29.118Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32587816,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T22:12:39.696Z","status":"ssl_error","status_checked_at":"2026-05-03T22:09:10.534Z","response_time":103,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["azure-devops","cli","github","terraform"],"created_at":"2025-02-13T20:55:07.941Z","updated_at":"2026-05-03T22:31:28.962Z","avatar_url":"https://github.com/jlucktay.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `stack`\n\n[![Go][actions-badge]][actions]\n[![Go Report Card][goreportcard-badge]][goreportcard]\n[![PkgGoDev][pkggodev-badge]][pkggodev]\n[![Contribute][contrib-badge]][contrib]\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-blueviolet.svg)](http://makeapullrequest.com)\n\nA support tool for use with Terraform stacks, Azure DevOps build pipelines, and GitHub projects/repos.\n\nIt currently has the following functions:\n\n- initialising Terraform against remote state storage, for local execution\n- queueing Terraform plans to build and destroy stacks in an Azure DevOps CI/CD pipeline\n- cancelling unneeded releases of aforementioned builds\n- creating GitHub issues in corresponding projects\n\nAll of these functions are executed contextually against a specific Terraform stack directory.\n\n## Installation\n\nThere are numerous installation options for `stack`:\n\n- [Homebrew](https://brew.sh)\n- building from the source code hosted here\n- directly downloading a pre-built binary for your desired platform\n\n### Homebrew\n\n#### First time install\n\n```shell\nbrew install jlucktay/tap/stack\n```\n\n#### Ongoing upgrades\n\n```shell\nbrew upgrade jlucktay/tap/stack\n```\n\n### Building from source\n\n#### Prerequisites\n\nYou should have a [working Go environment](https://golang.org/doc/install) and have `$GOPATH/bin` in your `$PATH`.\n\n#### Compiling\n\nTo download the source, compile, and install the demo binary, run:\n\n```shell\ngo get go.jlucktay.dev/stack\n```\n\nA newly-compiled `stack` binary will be placed in `$GOPATH/bin/`.\n\n### Direct download of binary relases\n\nBinary releases can be downloaded [here on GitHub](https://github.com/jlucktay/stack/releases/latest).\n\n## Configuration\n\nThere is a sample JSON file named `stack.config.example.json` here in the root of this repo.\n\nThe search order that `stack` follows when looking for the fully populated `stack.config.json` config file is as\nfollows:\n\n1. `\u003ccurrent working directory\u003e/stack.config.json`\n1. `$HOME/.config/stack/stack.config.json`\n1. `/etc/stack/stack.config.json`\n\nFilling out this config file will require the generation of two personal access tokens, one from Azure DevOps and one\nfrom GitHub. Links to the appropriate pages on each site are in the example file.\n\n### Mapping subscriptions to remote state storage containers and keys\n\nUnder the `.azure.subscriptions` section, all keys defined here will map verbatim to the parent directory's name when\n`stack` is executed. The values need to be set as GUIDs for the corresponding subscriptions in Azure.\n\nAssume - for this example's sake - that the working directory is as follows:\n\n```bash\n/git/MyGitHubOrg/MyGitHubRepo/stack-prefix/subscription-alias/one/two/three/my-stack\n```\n\nAlso assume that the following values are configured:\n\n```json\n{\n  \"azure\": {\n    \"state\": {\n      \"storageAccount\": \"mytfstatestorage\"\n    },\n    \"subscriptions\": {\n      \"subscription-alias\": \"01234567-89ab-cdef-0123-456789abcdef\"\n    }\n  },\n  \"github\": {\n    \"org\": \"MyGitHubOrg\",\n    \"repo\": \"MyGitHubRepo\"\n  },\n  \"stackPrefix\": \"stack-prefix\"\n}\n```\n\nThe keys under `.azure.subscriptions` map to the first child directory underneath the directory set under\n`.stackPrefix` so the sub-directory `subscription-alias` (under `stack-prefix`) would map to the subscription with a\nGUID of `01234567-89ab-cdef-0123-456789abcdef`.\n\nFor remote state storage within the storage account, the key value is made up of three components:\n\n1. the `.stackPrefix` value (`stack-prefix` in this example)\n1. the name of the stack's direct parent directory (`three`)\n1. the name of the stack directory itself (`my-stack`)\n\nThe container within the remote state storage account maps to the GUID of the subscription.\n\n```bash\n$ pwd\n/git/MyGitHubOrg/MyGitHubRepo/stack-prefix/subscription-alias/one/two/three/my-stack\n$ stack init\nSwitching subscriptions... done.\nRetrieving storage account key... done.\nSwitching subscriptions... done.\nInitialising Terraform with following dynamic values:\n        container_name:         01234567-89ab-cdef-0123-456789abcdef\n        key:                    stack-prefix.three.my-stack\n        storage_account:        mytfstatestorage\n...\n```\n\n### Other tools in use\n\nSome of the functionality in `stack` comes from executing other tools, which will need to be installed, configured,\nauthed, and available on your `$PATH`:\n\n- [Git](https://git-scm.com) - `git`\n- [Terraform](https://www.terraform.io) - `terraform`\n\n## Usage\n\n`stack` itself has several subcommands:\n\n- `init`\n- `build`\n- `destroy`\n- `cancel`\n- `issue`\n- `version`\n\n### `stack init`\n\nInitialises the current Terraform stack directory using the Azure storage account for the remote state backend.\n\n```bash\n$ stack init\nSwitching subscriptions... done.\nRetrieving storage account key... done.\nSwitching subscriptions... done.\nInitialising Terraform with following dynamic values:\n...\n```\n\n#### `stack init` relevant config keys\n\n```json\n{\n  \"azure\": {\n    \"state\": {\n      \"keyPrefix\": \"first of three segments for key names of state files within blob storage\",\n      \"resourceGroup\": \"name of resource group on Azure which contains the storage account\",\n      \"storageAccount\": \"name of Azure storage account where Terraform state is stored\",\n      \"subscription\": \"GUID of Azure subscription holding the state storage account\"\n    },\n    \"subscriptions\": {\n      \"a stack under '/\u003cstackPrefix\u003e/\u003cthis key\u003e/\u003ca stack name\u003e/'\": \"will map to subscription associated with \u003cthis key\u003e\",\n      \"exampleSubName\": \"subscription GUIDs go here\",\n      \"this key will be matched to a parent directory\": \"this value will map said directory to a specific subscription\"\n    }\n  },\n  \"stackPrefix\": \"/some/segment/of/repo/directory/structure/\"\n}\n```\n\n### `stack build`\n\nQueues a plan in Azure DevOps to build the Terraform stack in the current directory.\n\n```bash\n$ stack build\nStack (plan) URL: https://dev.azure.com/MyAzureDevOpsOrg/12345678-90ab-cdef-1234-567890abcdef/_build/results?buildId=1234\n```\n\n#### `stack build` relevant config keys\n\n```json\n{\n  \"azureDevOps\": {\n    \"buildDefID\": 5,\n    \"org\": \"the name of the organisation within Azure DevOps\",\n    \"pat\": \"52 character alphanumeric, generated here: https://dev.azure.com/\u003corg\u003e/_usersSettings/tokens\",\n    \"project\": \"the name of the project under the organisation within Azure DevOps\"\n  },\n  \"stackPrefix\": \"/some/segment/of/repo/directory/structure/\"\n}\n```\n\n#### `stack build` optional arguments\n\n##### `--branch`\n\nIf given, build from this branch. Defaults to the current branch.\n\n##### `--target`\n\nIf given, target these specific Terraform resources only. Delimit multiple target IDs with a comma `,`.\nFor example:\n\n```bash\nstack build --target=\"azurerm_resource_group.main,azurerm_virtual_machine.app,azurerm_virtual_machine.database\"\n```\n\n### `stack destroy`\n\nQueues a plan in Azure DevOps to destroy the Terraform stack in the current directory.\n\nFunctionally identical to the `stack build` subcommand, including `--branch` and `--target` optional arguments, with\nthe singular difference being that this subcommand references `.azureDevOps.destroyDefID` in the config instead of\n`.azureDevOps.buildDefID`.\n\n### `stack cancel`\n\nCancels any pending releases in Azure DevOps.\n\n**Not yet implemented - coming soon!**\n\n### `stack issue`\n\nCreates an issue in GitHub with a label referring to the current Terraform stack directory, and assigned to the\ncurrent user.\n\nThe issue's body text is gathered by way of an interactive editor, designated by the current environment's `EDITOR`\nvariable.\n\n```bash\n$ stack issue -t \"There's a problem with this stack!\"\n...\n($EDITOR is launched to gather issue body)\n...\nNew issue: https://github.com/MyGitHubOrg/MyGitHubRepo/issues/1234\n```\n\n#### `stack issue` relevant config keys\n\n```json\n{\n  \"github\": {\n    \"org\": \"the name of the organisation within GitHub\",\n    \"pat\": \"\u003c40 character hexadecimal, generated here: https://github.com/settings/tokens\u003e\",\n    \"repo\": \"the name of the repository under the organisation within GitHub\"\n  }\n}\n```\n\n### `stack version`\n\nDisplays version and build information for the current `stack` binary.\n\n## Docker\n\nThere is a [Dockerfile](Dockerfile) that is looped into `make` and will be built by the default rule.\n\nA typical execution of the Docker image from within your own stack directory needs to mount a volume under `/workdir`\nand would look something like this:\n\n```shell\ndocker run --rm --volume $(pwd):/workdir go.jlucktay.dev/stack[:\u003ctag\u003e]\n```\n\nThe subcommands [described above](#usage) may be passed in by appending them to this command line, for example:\n\n```shell\ndocker run --rm --volume $(pwd):/workdir go.jlucktay.dev/stack[:\u003ctag\u003e] version\n```\n\n## Contributing\n\nPull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.\n\nPlease make sure to update tests as appropriate.\n\nSee also the [contributing guide](.github/CONTRIBUTING.md).\n\n## License\n\n[MIT](https://choosealicense.com/licenses/mit/)\n\n\u003c!-- Badges and associated links --\u003e\n\n[actions-badge]: https://github.com/jlucktay/stack/actions/workflows/go.yaml/badge.svg\n[actions]: https://github.com/jlucktay/stack/actions/workflows/go.yaml\n[contrib-badge]: https://img.shields.io/github/contributors/jlucktay/stack\n[contrib]: https://github.com/jlucktay/stack/tree/main/.github/CONTRIBUTING.md\n[goreportcard-badge]: https://goreportcard.com/badge/go.jlucktay.dev/stack\n[goreportcard]: https://goreportcard.com/report/go.jlucktay.dev/stack\n[pkggodev-badge]: https://pkg.go.dev/badge/go.jlucktay.dev/stack\n[pkggodev]: https://pkg.go.dev/go.jlucktay.dev/stack\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjlucktay%2Fstack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjlucktay%2Fstack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjlucktay%2Fstack/lists"}