Skip to content

Commit

Permalink
rebase
Browse files Browse the repository at this point in the history
  • Loading branch information
jamiewri committed May 18, 2022
0 parents commit e3859f7
Show file tree
Hide file tree
Showing 17 changed files with 793 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .gitignore
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/
70 changes: 70 additions & 0 deletions README.md
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.
```
18 changes: 18 additions & 0 deletions go.mod
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
)
32 changes: 32 additions & 0 deletions go.sum
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=
43 changes: 43 additions & 0 deletions internal/command/apply.go
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)
}
}
35 changes: 35 additions & 0 deletions internal/command/base.go
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)
}
}
109 changes: 109 additions & 0 deletions internal/command/cancel.go
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

}
}
}
}
43 changes: 43 additions & 0 deletions internal/command/destroy.go
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)
}
}
Loading

0 comments on commit e3859f7

Please sign in to comment.