Skip to content

Commit

Permalink
Feat: support terragrunt (#147)
Browse files Browse the repository at this point in the history
* Feat: support terragrunt

* refactor based PR comments

* fixed rename
  • Loading branch information
TomerHeber authored Jun 18, 2022
1 parent 44faf46 commit 3723fa7
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 57 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ Icon
Network Trash Folder
Temporary Items
.apdisk
hcl
tf
31 changes: 23 additions & 8 deletions cli/args.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
package cli

import (
"errors"
"flag"
"log"
"fmt"
"os"

"github.com/env0/terratag/internal/common"
)

type Args struct {
Tags string
Dir string
Filter string
Type string
IsSkipTerratagFiles bool
Verbose bool
Rename bool
}

func InitArgs() (Args, bool) {
func validate(args Args) error {
if args.Tags == "" {
return errors.New("missing tags")
}
if args.Type != string(common.Terraform) && args.Type != string(common.Terragrunt) {
return fmt.Errorf("invalid type %s, must be either 'terratag' or 'terragrunt'", args.Type)
}
return nil
}

func InitArgs() (Args, error) {
args := Args{}
isMissingArg := false
programName := os.Args[0]
programArgs := os.Args[1:]

Expand All @@ -29,13 +42,15 @@ func InitArgs() (Args, bool) {
fs.StringVar(&args.Filter, "filter", ".*", "Only apply tags to the selected resource types (regex)")
fs.BoolVar(&args.Verbose, "verbose", false, "Enable verbose logging")
fs.BoolVar(&args.Rename, "rename", true, "Keep the original filename or replace it with <basename>.terratag.tf")
fs.StringVar(&args.Type, "type", string(common.Terraform), "The IAC type. Valid values: terraform or terragrunt")

err := fs.Parse(programArgs)
if err := fs.Parse(programArgs); err != nil {
return args, err
}

if err != nil || args.Tags == "" {
log.Println("Usage: terratag -tags='{ \"some_tag\": \"value\" }' [-dir=\".\"]")
isMissingArg = true
if err := validate(args); err != nil {
return args, err
}

return args, isMissingArg
return args, nil
}
10 changes: 7 additions & 3 deletions cmd/terratag/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ import (
)

func main() {
args, isMissingArg := cli.InitArgs()
if isMissingArg {
args, err := cli.InitArgs()
if err != nil {
log.Println(err)
log.Println("Usage: terratag -tags='{ \"some_tag\": \"value\" }' [-dir=\".\"]")
return
}
initLogFiltering(args.Verbose)

terratag.Terratag(args)
if err := terratag.Terratag(args); err != nil {
log.Printf("[ERROR] execution failed due to an error\n%v", err)
}
}

func initLogFiltering(verbose bool) {
Expand Down
31 changes: 31 additions & 0 deletions internal/common/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package common

import "github.com/hashicorp/hcl/v2/hclwrite"

type IACType string

const (
Terraform IACType = "terraform"
Terragrunt IACType = "terragrunt"
)

type Version struct {
Major int
Minor int
}

type TaggingArgs struct {
Filter string
Dir string
Tags string
Matches []string
IsSkipTerratagFiles bool
Rename bool
IACType IACType
TFVersion Version
}

type TerratagLocal struct {
Found map[string]hclwrite.Tokens
Added string
}
18 changes: 5 additions & 13 deletions internal/convert/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"log"
"strings"

"github.com/env0/terratag/internal/common"
"github.com/env0/terratag/internal/tag_keys"
"github.com/env0/terratag/internal/utils"
"github.com/hashicorp/hcl/v2/hclsyntax"
Expand All @@ -14,7 +15,7 @@ import (
"github.com/zclconf/go-cty/cty"
)

func GetExistingTagsExpression(tokens hclwrite.Tokens, tfVersion Version) string {
func GetExistingTagsExpression(tokens hclwrite.Tokens, tfVersion common.Version) string {
// NOTE: consider removing buildMapExpression in case tf v0.11.0 support is removed.
if isHclMap(tokens) && tfVersion.Major == 0 && tfVersion.Minor <= 11 {
return buildMapExpression(tokens, tfVersion)
Expand Down Expand Up @@ -51,7 +52,7 @@ func trimTokens(tokens hclwrite.Tokens) hclwrite.Tokens {
return tokens[startIndex : len(tokens)-endIndex]
}

func buildMapExpression(tokens hclwrite.Tokens, tfVersion Version) string {
func buildMapExpression(tokens hclwrite.Tokens, tfVersion common.Version) string {
if tfVersion.Major == 0 && tfVersion.Minor >= 15 || tfVersion.Major == 1 {
mapContent := strings.TrimSpace(string(tokens.Bytes()))
return "tomap(" + mapContent + ")"
Expand Down Expand Up @@ -115,7 +116,7 @@ func stringifyExpression(tokens hclwrite.Tokens) string {
return expression
}

func AppendLocalsBlock(file *hclwrite.File, filename string, terratag TerratagLocal) {
func AppendLocalsBlock(file *hclwrite.File, filename string, terratag common.TerratagLocal) {
key := tag_keys.GetTerratagAddedKey(filename)

// If there's an existings terratag locals replace it with the merged locals.
Expand Down Expand Up @@ -176,7 +177,7 @@ func UnquoteTagsAttribute(swappedTagsStrings []string, text string) string {
return text
}

func MoveExistingTags(filename string, terratag TerratagLocal, block *hclwrite.Block, tagId string) (bool, error) {
func MoveExistingTags(filename string, terratag common.TerratagLocal, block *hclwrite.Block, tagId string) (bool, error) {
var existingTags hclwrite.Tokens

// First we try to find tags as attribute
Expand Down Expand Up @@ -256,12 +257,3 @@ func quoteAttributeKeys(tagsAttribute *hclwrite.Attribute) hclwrite.Tokens {

return newTags
}

type TerratagLocal struct {
Found map[string]hclwrite.Tokens
Added string
}
type Version struct {
Major int
Minor int
}
5 changes: 3 additions & 2 deletions internal/tagging/tagging.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tagging
import (
"log"

"github.com/env0/terratag/internal/common"
"github.com/env0/terratag/internal/convert"
"github.com/env0/terratag/internal/tag_keys"
"github.com/env0/terratag/internal/terraform"
Expand Down Expand Up @@ -80,9 +81,9 @@ type TagBlockArgs struct {
Filename string
Block *hclwrite.Block
Tags string
Terratag convert.TerratagLocal
Terratag common.TerratagLocal
TagId string
TfVersion convert.Version
TfVersion common.Version
}

type TagResourceFn func(args TagBlockArgs) (*Result, error)
Expand Down
57 changes: 48 additions & 9 deletions internal/terraform/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"io/ioutil"
"log"
"os"
Expand All @@ -14,12 +15,12 @@ import (
"strings"

"github.com/bmatcuk/doublestar"
"github.com/env0/terratag/internal/convert"
"github.com/env0/terratag/internal/common"
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/thoas/go-funk"
)

func GetTerraformVersion() (*convert.Version, error) {
func GetTerraformVersion() (*common.Version, error) {
output, err := exec.Command("terraform", "version").Output()
if err != nil {
return nil, err
Expand All @@ -46,7 +47,7 @@ func GetTerraformVersion() (*convert.Version, error) {
return nil, fmt.Errorf("terratag only supports Terraform from version 0.11.x and up to 1.2.x - your version says %s", outputAsString)
}

return &convert.Version{Major: majorVersion, Minor: minorVersion}, nil
return &common.Version{Major: majorVersion, Minor: minorVersion}, nil
}

type VersionPart int
Expand All @@ -73,21 +74,59 @@ func GetResourceType(resource hclwrite.Block) string {
return resource.Labels()[0]
}

func ValidateTerraformInitRun(dir string) error {
_, err := os.Stat(dir + "/.terraform")
func getRootDir(iacType string) string {
if iacType == string(common.Terragrunt) {
return "/.terragrunt-cache"
} else {
return "/.terraform"
}
}

if err != nil {
func ValidateInitRun(dir string, iacType string) error {
path := dir + getRootDir(iacType)

if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return errors.New("terraform init must run before running terratag")
return fmt.Errorf("%s init must run before running terratag", iacType)
}

return fmt.Errorf("couldn't determine if terraform init has run: %v", err)
return fmt.Errorf("couldn't determine if %s init has run: %v", iacType, err)
}

return nil
}

func GetTerraformFilePaths(rootDir string) ([]string, error) {
func GetFilePaths(dir string, iacType string) ([]string, error) {
if iacType == string(common.Terragrunt) {
return getTerragruntFilePath(dir)
} else {
return getTerraformFilePaths(dir)
}
}

func getTerragruntFilePath(rootDir string) ([]string, error) {
rootDir += getRootDir(string(common.Terragrunt))

var tfFiles []string
if err := filepath.WalkDir(rootDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
log.Printf("[WARN] skipping %s due to an error: %v", path, err)
return filepath.SkipDir
}

if strings.HasSuffix(path, ".tf") {
tfFiles = append(tfFiles, path)
}

return nil
}); err != nil {
return nil, err
}

return tfFiles, nil
}

func getTerraformFilePaths(rootDir string) ([]string, error) {
const tfFileMatcher = "/*.tf"

tfFiles, err := doublestar.Glob(rootDir + tfFileMatcher)
Expand Down
35 changes: 32 additions & 3 deletions internal/tfschema/tfschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package tfschema

import (
"fmt"
"io/fs"
"log"
"path/filepath"
"strings"
"sync"

"github.com/env0/terratag/internal/common"
"github.com/env0/terratag/internal/providers"
"github.com/env0/terratag/internal/tagging"
"github.com/env0/terratag/internal/terraform"
Expand All @@ -21,13 +24,13 @@ var providerToClientMapLock sync.Mutex

var customSupportedProviderNames = [...]string{"google-beta"}

func IsTaggable(dir string, resource hclwrite.Block) (bool, error) {
func IsTaggable(dir string, iacType common.IACType, resource hclwrite.Block) (bool, error) {
var isTaggable bool
resourceType := terraform.GetResourceType(resource)

if providers.IsSupportedResource(resourceType) {
providerName, _ := detectProviderName(resource)
client, err := getClient(providerName, dir)
client, err := getClient(providerName, dir, iacType)
if err != nil {
return false, err
}
Expand Down Expand Up @@ -84,7 +87,33 @@ func detectProviderName(resource hclwrite.Block) (string, error) {
return extractProviderNameFromResourceType(terraform.GetResourceType(resource))
}

func getClient(providerName string, dir string) (tfschema.Client, error) {
func getTerragruntPluginPath(dir string) string {
dir += "/.terragrunt-cache"
ret := dir
found := false

filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if found || err != nil {
return filepath.SkipDir
}

// E.g. ./.terragrunt-cache/yHtqnMrVQOISIYxobafVvZbAAyU/ThyYwttwki6d6AS3aD5OwoyqIWA/.terraform
if strings.HasSuffix(path, "/.terraform") {
ret = strings.TrimSuffix(path, "/.terraform")
found = true
}

return nil
})

return ret
}

func getClient(providerName string, dir string, iacType common.IACType) (tfschema.Client, error) {
if iacType == common.Terragrunt {
dir = getTerragruntPluginPath(dir)
}

providerToClientMapLock.Lock()
defer providerToClientMapLock.Unlock()

Expand Down
Loading

0 comments on commit 3723fa7

Please sign in to comment.