Skip to content

Commit

Permalink
all the new features
Browse files Browse the repository at this point in the history
- pipe input being passed through
- simple match replaced with glob
- sick outputs
- clean ansi colors from pipe
  • Loading branch information
phzietsman committed May 8, 2020
1 parent 0191b56 commit 9629db3
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 31 deletions.
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

tft_darwin_amd64

tft_linux_amd64

tft_windows_amd64.exe

plan.out
.DS_Store
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# tft
terraform targeted applies made easy, aka making easy to do bad things.
[![Build Status](https://travis-ci.org/phzietsman/tft.svg?branch=master)](https://travis-ci.org/phzietsman/tft)

terraform targeted applies made easy, aka making it easy to do dumb stuff.

![banner](docs/banner.png)

using globs in `-target` is an open issue on [terraform](https://github.com/hashicorp/terraform/issues/2182) and it is not getting a ton of love (probably because it promotes bad behaviour), but sometimes i need to be bad.

Expand Down Expand Up @@ -32,28 +36,32 @@ terraform will manage the dependencies and figure out what needs to be applied w

**ALWAYS INSPECT YOUR PLAN BEFORE APPLYING**

to match resources you can use **globs**.

```sh
# I always write out the plan to file, but you could pipe directly into tft
$ terraform plan > plan.out

# This will include all aws_s3_bucket_policy resources
$ cat plan | tft -pattern=aws_s3_bucket_policy -mode=include
$ terraform plan | tft -pattern="*aws_s3_bucket_policy*" -mode=include

# This will include all aws_s3_bucket_policy resources in module something
$ terraform plan | tft -pattern="module.something.aws_s3_bucket_policy*" -mode=include

# This will include all aws_s3_bucket_policy resources in all modules
$ terraform plan | tft -pattern="module.*.aws_s3_bucket_policy*" -mode=include

# This will exclude all aws_s3_bucket_policy resources
$ cat plan | tft -pattern=aws_s3_bucket_policy -mode=exclude
$ terraform plan | tft -pattern="*aws_s3_bucket_policy*" -mode=exclude

```

## things i'll add (maybe)

since i have little to no go knowledge and its only me using this and it is serving its purpose well, i'm not in a hurry to change things, but i'd still like to keep it alive. This is what i'm planning:

- when you pipe `terraform plan` through `tft` you only get feedback at the end when everything is done. i need to print the terraform output to terminal.
- i want to use some cool cli framework. cause. this looks like a good one: https://github.com/urfave/cli
- get a handle on the terraform commands and args. then i could run the targeted plan for you and create a better output.
- ~~get a handle on the terraform commands and args. then i could run the targeted plan for you and create a better output.~~ this is impossible.
- fix the typos in this doc and use caps.
- add some tests and error handling. this seems to be the responsible thing to do.
- maybe change the `-pattern` to accept a globs. this might be handy and it'll make we feel more like a developer.
- package this for brew


Expand Down
Binary file added docs/banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 68 additions & 20 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import (
"fmt"
"io"
"os"
"regexp"
"strings"

"github.com/ryanuber/go-glob"
)

const (
Expand All @@ -15,6 +18,19 @@ const (
Yellow = "\033[1;33m%s\033[0m"
Red = "\033[1;31m%s\033[0m"
Green = "\033[1;32m%s\033[0m"

ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"

banner = `
_______ _______ _______
|\ /|\ /|\ /|
| +---+ | +---+ | +---+ |
| | | | | | | | | |
| |t | | |f | | |t | |
| +---+ | +---+ | +---+ |
|/_____\|/_____\|/_____\| making it easy to do dumb stuff
`
)

// Expects something like:
Expand All @@ -28,37 +44,36 @@ func cleanCountResource(s string) string {
// # module.acq_mart_clean.aws_lambda_function.sf_trigger[0] will be updated in-place
func stripResource(s string) string {
words := strings.Fields(strings.TrimSpace(s))
// Something weird in how its trimming
terraformResource := words[2]
terraformResource := words[1]
return cleanCountResource(terraformResource)
}

func unique(s []string) []string {
keys := make(map[string]bool)
list := []string{}
for _, entry := range s {
if _, value := keys[entry]; !value {
keys[entry] = true
list = append(list, entry)
}
}
return list
}

func main() {

patternPtr := flag.String("pattern", "", "The pattern to filter the resources")
patternPtr := flag.String("pattern", "*", "The pattern to filter the resources")
modePtr := flag.String("mode", "include", "Either 'exclude' or 'inlcude'")
flag.Parse()

fmt.Println(os.Args)

info, _ := os.Stdin.Stat()

if info.Mode()&os.ModeCharDevice != 0 {
fmt.Println("The command is intended to work with pipes.")
fmt.Println("Usage: terraform plan | tft -pattern=aws_s3_bucket -mode=exclude")
fmt.Println("Usage: terraform plan | tft -pattern=\"module.*.aws_s3_bucket*\" -mode=exclude")
return
}

fmt.Print("Resources matching the pattern ")
fmt.Printf(Blue, *patternPtr)
fmt.Print(" will be ")
if *modePtr == "include" {
fmt.Printf(Green, *modePtr+"d")
} else {
fmt.Printf(Red, *modePtr+"d")
}
fmt.Print(" in the terraform plan/apply\n\n")

reader := bufio.NewReader(os.Stdin)
var runes []rune

Expand All @@ -68,28 +83,61 @@ func main() {
break
}
runes = append(runes, input)

// Passthrough to see the terraform plan output
fmt.Print(string(input))
}

inputStringArr := strings.Split(strings.Replace(string(runes), "\r\n", "\n", -1), "\n")
re := regexp.MustCompile(ansi)

inputStringNoColors := re.ReplaceAllString(string(runes), "")

inputStringArr := strings.Split(strings.Replace(inputStringNoColors, "\r\n", "\n", -1), "\n")
matchingResources := []string{""}
nonMatchingResources := []string{""}

for _, s := range inputStringArr {
if strings.Contains(s, "#") {

resource := stripResource(s)
if strings.Contains(resource, *patternPtr) {
if glob.Glob(*patternPtr, resource) {
matchingResources = append(matchingResources, resource)
} else {
nonMatchingResources = append(nonMatchingResources, resource)
}
}
}

// Duplicates can occur with count resources
matchingResources = unique(matchingResources)
nonMatchingResources = unique(nonMatchingResources)

fmt.Print(banner)
fmt.Print("\nResources matching ")
fmt.Printf(Blue, *patternPtr)
fmt.Print(" will be ")
if *modePtr == "include" {
fmt.Printf(Green, *modePtr+"d")
} else {
fmt.Printf(Red, *modePtr+"d")
}
fmt.Print(" when running the following command:")

if *modePtr == "include" {
fmt.Println(strings.Join(matchingResources, " -target="))
if len(matchingResources) == 1 {
fmt.Println("\n\n \xF0\x9F\x99\x88 no matches found, do nothing ")
} else {
fmt.Print("\n\nterraform plan")
fmt.Println(strings.Join(matchingResources, " -target="))
}
} else {
fmt.Println(strings.Join(nonMatchingResources, " -target="))
if len(nonMatchingResources) == 1 {
fmt.Println("\n\n \xF0\x9F\x99\x88 no matches found, do nothing ")
} else {
fmt.Print("\n\nterraform plan")
fmt.Println(strings.Join(nonMatchingResources, " -target="))
}
}
fmt.Print("\n")

}

0 comments on commit 9629db3

Please sign in to comment.