-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e3859f7
Showing
17 changed files
with
793 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Binaries for programs and plugins | ||
*.exe | ||
*.exe~ | ||
*.dll | ||
*.so | ||
*.dylib | ||
.vscode | ||
.vscode/launch.json | ||
# Test binary, built with `go test -c` | ||
*.test | ||
|
||
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out | ||
|
||
# Dependency directories (remove the comment below to include it) | ||
# vendor/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# tfctl | ||
|
||
## Purpose | ||
This CLI tool is used to orchestrate Terraform Cloud runs. Its goal is not to cover the entire administation of Terraform Cloud, but instead to solve a very simple workflow. | ||
|
||
That workflow is to trigger common actions against workspaces by referencing tags only. | ||
|
||
For example, I would like to create a destroy run on all workspace that match the tag 'demo', or I would like to cancel all runs on workspaces that match the tags 'dev'. | ||
|
||
If you are looking for a complete CLI tool that orchestrates with Terraform Cloud, I'd recommend that you check out [tecli](https://github.com/awslabs/tecli) and [tfx](https://github.com/straubt1/tfx). | ||
|
||
## Requirements | ||
Required environment variables | ||
| Env | Description | | ||
|-----|-------------| | ||
| TFC_ORGANIZATION | The name of your Terraform Cloud organization | | ||
| TFC_TEAM_TOKEN | [Terraform Cloud team token](https://www.terraform.io/cloud-docs/users-teams-organizations/users#api-tokens) | | ||
|
||
|
||
## Example Usage | ||
List all workspaces that match the tag `test`. | ||
```bash | ||
$ tfctl search -t test | ||
Searching organization: devopstower | ||
tfc-aws-network-dev | ||
tfc-aws-virtual-machine-dev | ||
tfc-aws-virtual-machine-prod | ||
tfc-aws-network-prod | ||
``` | ||
|
||
Run a plan on all workspaces that match the tag `test`. | ||
```bash | ||
$ tfctl plan -t test | ||
Searching organization: devopstower | ||
Targeting the following workspaces [tfc-aws-network-dev] | ||
Plan only started: ws-BvDUwoSB4cELmfmc - run-pt5NodM5GTckfMor | ||
``` | ||
|
||
Run a destroy on all workspaces that match the tag `test`. | ||
```bash | ||
$ tfctl destroy -t test | ||
Searching organization: devopstower | ||
Targeting the following workspaces [tfc-aws-network-dev] | ||
Destroy run started: ws-BvDUwoSB4cELmfmc - run-kF62JTa21eqQUdjt | ||
``` | ||
|
||
Cancel (or discard) all runs on all workspaces that match both tags `azure` and `demo`. | ||
```bash | ||
$ tfctl cancel -t azure,demo | ||
Searching organization: devopstower | ||
Targeting the following workspaces [tfc-azure-webapp] | ||
Run cancelled: run-Z7kVEntjzwz3ddMw | ||
Run discarded: run-KUyRJxwJE5fWxEWX | ||
``` | ||
|
||
## Help Menu | ||
```bash | ||
$ tfctl --help | ||
Usage: | ||
tfctl [command] | ||
|
||
Available Commands: | ||
apply Start an apply run, this run will automatically apply. | ||
cancel Cancel all run on any workspace that matches the supplied tag. | ||
completion Generate the autocompletion script for the specified shell | ||
destroy Start a destroy run, auto-apply disable by default. | ||
help Help about any command | ||
plan Start a plan only run, auto-apply disable by default. | ||
search List workspaces that match the supplied tags. | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
module github.com/jamiewri/tfctl | ||
|
||
go 1.18 | ||
|
||
require github.com/hashicorp/go-tfe v1.2.0 | ||
|
||
require ( | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/google/go-querystring v1.1.0 // indirect | ||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect | ||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect | ||
github.com/hashicorp/go-slug v0.8.0 // indirect | ||
github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d // indirect | ||
github.com/inconshreveable/mousetrap v1.0.0 // indirect | ||
github.com/spf13/cobra v1.4.0 // indirect | ||
github.com/spf13/pflag v1.0.5 // indirect | ||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= | ||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= | ||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= | ||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= | ||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= | ||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= | ||
github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= | ||
github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= | ||
github.com/hashicorp/go-slug v0.8.0 h1:h7AGtXVAI/cJ/Wwa/JQQaftQnWQmZbAzkzgZeZVVmLw= | ||
github.com/hashicorp/go-slug v0.8.0/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4= | ||
github.com/hashicorp/go-tfe v1.2.0 h1:L29LCo/qIjOqBUjfiUsZSAzBdxmsOLzwnwZpA+68WW8= | ||
github.com/hashicorp/go-tfe v1.2.0/go.mod h1:tJF/OlAXzVbmjiimAPLplSLgwg6kZDUOy0MzHuMwvF4= | ||
github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d h1:9ARUJJ1VVynB176G1HCwleORqCaXm/Vx0uUi0dL26I0= | ||
github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d/go.mod h1:Yog5+CPEM3c99L1CL2CFCYoSzgWm5vTU58idbRUaLik= | ||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= | ||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= | ||
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= | ||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= | ||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package command | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/jamiewri/tfctl/internal/repository" | ||
"github.com/jamiewri/tfctl/internal/util" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func applyCommand(tfc repository.TerraformCloud) *cobra.Command { | ||
|
||
var tags []string | ||
|
||
c := &cobra.Command{ | ||
Use: "apply", | ||
Short: "Start an apply run, this run will automatically apply.", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
runApplyCommand(tfc, tags) | ||
}, | ||
} | ||
|
||
c.Flags().StringSliceVarP(&tags, "tags", "t", []string{}, "") | ||
|
||
return c | ||
} | ||
|
||
func runApplyCommand(tfc repository.TerraformCloud, tags []string) { | ||
|
||
// Use tags to list to find list of workspaces | ||
wl, err := tfc.GetWorkspacesFromTags(tags) | ||
if err != nil { | ||
fmt.Println(err) | ||
} | ||
|
||
// Tell the user what workspaces we are targeting. | ||
util.PrintWorkspaceNames(wl) | ||
|
||
// Run a plan on every workspace | ||
for _, w := range wl.Workspaces { | ||
tfc.StartApply(w) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package command | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/jamiewri/tfctl/internal/config" | ||
"github.com/jamiewri/tfctl/internal/repository" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
// BaseCmd represents the base command when called without any subcommands | ||
var BaseCommand = &cobra.Command{ | ||
Use: "tfctl", | ||
Short: "Terraform Cloud orchestration", | ||
Long: "This CLI tool is used to orchestrate Terraform Cloud and Terraform Enterprise.", | ||
} | ||
|
||
// Execute adds all child commands to the root command and sets flags appropriately. | ||
func Execute(appConfig *config.AppConfig, tfc repository.TerraformCloud) { | ||
|
||
// Output expected behaviour | ||
fmt.Println("Searching organization:", appConfig.TfcOrg) | ||
|
||
BaseCommand.AddCommand(searchCommand(tfc)) | ||
BaseCommand.AddCommand(planCommand(tfc)) | ||
BaseCommand.AddCommand(applyCommand(tfc)) | ||
BaseCommand.AddCommand(destroyCommand(tfc)) | ||
BaseCommand.AddCommand(cancelCommand(tfc)) | ||
|
||
if err := BaseCommand.Execute(); err != nil { | ||
fmt.Fprintln(os.Stderr, err) | ||
os.Exit(1) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package command | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/jamiewri/tfctl/internal/repository" | ||
"github.com/jamiewri/tfctl/internal/util" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func cancelCommand(tfc repository.TerraformCloud) *cobra.Command { | ||
|
||
var tags []string | ||
|
||
c := &cobra.Command{ | ||
Use: "cancel", | ||
Short: "Cancel all run on any workspace that matches the supplied tag.", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
runCancelCommand(tfc, tags) | ||
}, | ||
} | ||
|
||
c.Flags().StringSliceVarP(&tags, "tags", "t", []string{}, "") | ||
|
||
return c | ||
} | ||
|
||
|
||
// runCancelComand either discards or cancels run based on status | ||
// Unsure what to do so do nothing. | ||
// confirmed | ||
// cost_estimated | ||
// fetching | ||
// planned | ||
// policy_soft_failed | ||
// post_plan_running | ||
// post_plan_completed | ||
|
||
// No Action | ||
// - applied | ||
// - errored | ||
// - discarded | ||
// - cancelled | ||
// - planned_and_finished | ||
|
||
|
||
// Cancel | ||
// - planning | ||
// - plan_queued | ||
// - apply_queued | ||
// - pending | ||
// - cost_estimating | ||
// - applying | ||
// - policy_checking | ||
|
||
// Discard | ||
// - policy_checked | ||
// - policy_override | ||
|
||
|
||
func runCancelCommand(tfc repository.TerraformCloud, tags []string) { | ||
|
||
// Use tags to list to find list of workspaces | ||
wl, err := tfc.GetWorkspacesFromTags(tags) | ||
if err != nil { | ||
fmt.Println(err) | ||
} | ||
|
||
// Tell the user what workspaces we are targeting. | ||
util.PrintWorkspaceNames(wl) | ||
|
||
// Run a plan on every workspace | ||
for i := range wl.Workspaces { | ||
|
||
//find all runs in workspace | ||
rl, err := tfc.GetRunsFromWorkspace(wl.Workspaces[i]) | ||
if err != nil { | ||
fmt.Println(err) | ||
} | ||
|
||
for i := range rl.Runs { | ||
|
||
s := rl.Runs[i].Status | ||
|
||
// Discard run | ||
if s == "policy_checked" || | ||
s == "policy_override" { | ||
|
||
tfc.DiscardRun(rl.Runs[i]) | ||
continue | ||
|
||
} | ||
|
||
// Cancel run | ||
if s == "planning" || | ||
s == "plan_queued" || | ||
s == "apply_queued" || | ||
s == "pending" || | ||
s == "cost_estimating" || | ||
s == "applying" || | ||
s == "policy_checking" { | ||
|
||
tfc.CancelRun(rl.Runs[i]) | ||
continue | ||
|
||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package command | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/jamiewri/tfctl/internal/repository" | ||
"github.com/jamiewri/tfctl/internal/util" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func destroyCommand(tfc repository.TerraformCloud) *cobra.Command { | ||
|
||
var tags []string | ||
|
||
c := &cobra.Command{ | ||
Use: "destroy", | ||
Short: "Start a destroy run, auto-apply disable by default.", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
runDestroyCommand(tfc, tags) | ||
}, | ||
} | ||
|
||
c.Flags().StringSliceVarP(&tags, "tags", "t", []string{}, "") | ||
|
||
return c | ||
} | ||
|
||
func runDestroyCommand(tfc repository.TerraformCloud, tags []string) { | ||
|
||
// Use tags to list to find list of workspaces | ||
wl, err := tfc.GetWorkspacesFromTags(tags) | ||
if err != nil { | ||
fmt.Println(err) | ||
} | ||
|
||
// Tell the user what workspaces we are targeting. | ||
util.PrintWorkspaceNames(wl) | ||
|
||
// Run a destroy on every workspace | ||
for _, w := range wl.Workspaces { | ||
tfc.StartDestroy(w) | ||
} | ||
} |
Oops, something went wrong.