From 3faa2d712ff50acd09e02d8088581740e469bfec Mon Sep 17 00:00:00 2001 From: Ray Myers Date: Sat, 22 Jan 2022 12:57:08 -0600 Subject: [PATCH 1/5] Rename test_data dir --- refactor/mv.go | 5 --- refactor/mv_test.go | 44 +++++++++---------- .../case_all_data_blocks/from/a.tf | 0 .../case_all_data_blocks/from/a2.tf | 0 .../case_all_data_blocks/to/a.tf | 0 .../case_all_data_blocks/to/a2.tf | 0 .../case_all_data_blocks/to/data.tf | 0 .../{tf_org => mv}/case_all_locals/from/a.tf | 0 .../{tf_org => mv}/case_all_locals/from/b.tf | 0 .../{tf_org => mv}/case_all_locals/to/a.tf | 0 .../{tf_org => mv}/case_all_locals/to/b.tf | 0 .../case_all_locals/to/locals.tf | 0 .../{tf_org => mv}/case_all_outputs/from/a.tf | 0 .../{tf_org => mv}/case_all_outputs/to/a.tf | 0 .../case_all_outputs/to/outputs.tf | 0 .../{tf_org => mv}/case_all_vars/from/a.tf | 0 .../{tf_org => mv}/case_all_vars/to/a.tf | 0 .../case_all_vars/to/variables.tf | 0 .../case_data_single_block/from/a.tf | 0 .../case_data_single_block/from/data.tf | 0 .../case_data_single_block/to/a.tf | 0 .../case_data_single_block/to/data.tf | 0 .../case_data_single_block_new_file/from/a.tf | 0 .../case_data_single_block_new_file/to/a.tf | 0 .../to/data.tf | 0 .../case_resource_type/from/a.tf | 0 .../{tf_org => mv}/case_resource_type/to/a.tf | 0 .../case_resource_type/to/dest.tf | 0 .../case_single_local/from/a.tf | 0 .../{tf_org => mv}/case_single_local/to/a.tf | 0 .../case_single_local/to/locals.tf | 0 .../{tf_org => mv}/case_single_var/from/a.tf | 0 .../{tf_org => mv}/case_single_var/to/a.tf | 0 .../case_single_var/to/variables.tf | 0 34 files changed, 22 insertions(+), 27 deletions(-) rename refactor/test_data/{tf_org => mv}/case_all_data_blocks/from/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_all_data_blocks/from/a2.tf (100%) rename refactor/test_data/{tf_org => mv}/case_all_data_blocks/to/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_all_data_blocks/to/a2.tf (100%) rename refactor/test_data/{tf_org => mv}/case_all_data_blocks/to/data.tf (100%) rename refactor/test_data/{tf_org => mv}/case_all_locals/from/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_all_locals/from/b.tf (100%) rename refactor/test_data/{tf_org => mv}/case_all_locals/to/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_all_locals/to/b.tf (100%) rename refactor/test_data/{tf_org => mv}/case_all_locals/to/locals.tf (100%) rename refactor/test_data/{tf_org => mv}/case_all_outputs/from/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_all_outputs/to/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_all_outputs/to/outputs.tf (100%) rename refactor/test_data/{tf_org => mv}/case_all_vars/from/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_all_vars/to/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_all_vars/to/variables.tf (100%) rename refactor/test_data/{tf_org => mv}/case_data_single_block/from/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_data_single_block/from/data.tf (100%) rename refactor/test_data/{tf_org => mv}/case_data_single_block/to/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_data_single_block/to/data.tf (100%) rename refactor/test_data/{tf_org => mv}/case_data_single_block_new_file/from/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_data_single_block_new_file/to/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_data_single_block_new_file/to/data.tf (100%) rename refactor/test_data/{tf_org => mv}/case_resource_type/from/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_resource_type/to/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_resource_type/to/dest.tf (100%) rename refactor/test_data/{tf_org => mv}/case_single_local/from/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_single_local/to/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_single_local/to/locals.tf (100%) rename refactor/test_data/{tf_org => mv}/case_single_var/from/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_single_var/to/a.tf (100%) rename refactor/test_data/{tf_org => mv}/case_single_var/to/variables.tf (100%) diff --git a/refactor/mv.go b/refactor/mv.go index 49bf01f..6756d2e 100644 --- a/refactor/mv.go +++ b/refactor/mv.go @@ -3,7 +3,6 @@ package refactor import ( "errors" "fmt" - "io/ioutil" "os" "path/filepath" @@ -75,10 +74,6 @@ func findOrCreateLocalsBlock(parsedFile *hclwrite.File) *hclwrite.Block { return parsedFile.Body().AppendNewBlock("locals", []string{}) } -func writeParsedFile(parsedFile *hclwrite.File, toFile string) error { - return ioutil.WriteFile(toFile, parsedFile.Bytes(), 0644) -} - func moveLocals(parsedInFile, parsedOutFile *hclwrite.File) { for _, block := range parsedInFile.Body().Blocks() { diff --git a/refactor/mv_test.go b/refactor/mv_test.go index 5056b3c..7654c2f 100644 --- a/refactor/mv_test.go +++ b/refactor/mv_test.go @@ -25,78 +25,78 @@ func TestMv(t *testing.T) { name: "case_data_single_block", args: []string{"data", "data.tf"}, ok: true, - from: "test_data/tf_org/case_data_single_block/from", - to: "test_data/tf_org/case_data_single_block/to", + from: "test_data/mv/case_data_single_block/from", + to: "test_data/mv/case_data_single_block/to", }, { name: "case_data_single_block_qualified", args: []string{"data.a", "data.tf"}, ok: true, - from: "test_data/tf_org/case_data_single_block/from", - to: "test_data/tf_org/case_data_single_block/to", + from: "test_data/mv/case_data_single_block/from", + to: "test_data/mv/case_data_single_block/to", }, { name: "case_data_single_block_fully_qualified", args: []string{"data.a.b", "data.tf"}, ok: true, - from: "test_data/tf_org/case_data_single_block/from", - to: "test_data/tf_org/case_data_single_block/to", + from: "test_data/mv/case_data_single_block/from", + to: "test_data/mv/case_data_single_block/to", }, { name: "case_data_single_block_new_file", args: []string{"data", "data.tf"}, ok: true, - from: "test_data/tf_org/case_data_single_block_new_file/from", - to: "test_data/tf_org/case_data_single_block_new_file/to", + from: "test_data/mv/case_data_single_block_new_file/from", + to: "test_data/mv/case_data_single_block_new_file/to", }, { name: "case_all_data_blocks", args: []string{"data", "data.tf"}, ok: true, - from: "test_data/tf_org/case_all_data_blocks/from", - to: "test_data/tf_org/case_all_data_blocks/to", + from: "test_data/mv/case_all_data_blocks/from", + to: "test_data/mv/case_all_data_blocks/to", }, { name: "case_all_vars", args: []string{"variable", "variables.tf"}, ok: true, - from: "test_data/tf_org/case_all_vars/from", - to: "test_data/tf_org/case_all_vars/to", + from: "test_data/mv/case_all_vars/from", + to: "test_data/mv/case_all_vars/to", }, { name: "case_single_var", args: []string{"variable.a", "variables.tf"}, ok: true, - from: "test_data/tf_org/case_single_var/from", - to: "test_data/tf_org/case_single_var/to", + from: "test_data/mv/case_single_var/from", + to: "test_data/mv/case_single_var/to", }, { name: "case_all_locals", args: []string{"locals", "locals.tf"}, ok: true, - from: "test_data/tf_org/case_all_locals/from", - to: "test_data/tf_org/case_all_locals/to", + from: "test_data/mv/case_all_locals/from", + to: "test_data/mv/case_all_locals/to", }, { name: "case_single_local", args: []string{"local.b", "locals.tf"}, ok: true, - from: "test_data/tf_org/case_single_local/from", - to: "test_data/tf_org/case_single_local/to", + from: "test_data/mv/case_single_local/from", + to: "test_data/mv/case_single_local/to", }, { name: "case_resource_type", args: []string{"resource.a", "dest.tf"}, ok: true, - from: "test_data/tf_org/case_resource_type/from", - to: "test_data/tf_org/case_resource_type/to", + from: "test_data/mv/case_resource_type/from", + to: "test_data/mv/case_resource_type/to", }, { name: "case_all_outputs", args: []string{"output", "outputs.tf"}, ok: true, - from: "test_data/tf_org/case_all_outputs/from", - to: "test_data/tf_org/case_all_outputs/to", + from: "test_data/mv/case_all_outputs/from", + to: "test_data/mv/case_all_outputs/to", }, } diff --git a/refactor/test_data/tf_org/case_all_data_blocks/from/a.tf b/refactor/test_data/mv/case_all_data_blocks/from/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_data_blocks/from/a.tf rename to refactor/test_data/mv/case_all_data_blocks/from/a.tf diff --git a/refactor/test_data/tf_org/case_all_data_blocks/from/a2.tf b/refactor/test_data/mv/case_all_data_blocks/from/a2.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_data_blocks/from/a2.tf rename to refactor/test_data/mv/case_all_data_blocks/from/a2.tf diff --git a/refactor/test_data/tf_org/case_all_data_blocks/to/a.tf b/refactor/test_data/mv/case_all_data_blocks/to/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_data_blocks/to/a.tf rename to refactor/test_data/mv/case_all_data_blocks/to/a.tf diff --git a/refactor/test_data/tf_org/case_all_data_blocks/to/a2.tf b/refactor/test_data/mv/case_all_data_blocks/to/a2.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_data_blocks/to/a2.tf rename to refactor/test_data/mv/case_all_data_blocks/to/a2.tf diff --git a/refactor/test_data/tf_org/case_all_data_blocks/to/data.tf b/refactor/test_data/mv/case_all_data_blocks/to/data.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_data_blocks/to/data.tf rename to refactor/test_data/mv/case_all_data_blocks/to/data.tf diff --git a/refactor/test_data/tf_org/case_all_locals/from/a.tf b/refactor/test_data/mv/case_all_locals/from/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_locals/from/a.tf rename to refactor/test_data/mv/case_all_locals/from/a.tf diff --git a/refactor/test_data/tf_org/case_all_locals/from/b.tf b/refactor/test_data/mv/case_all_locals/from/b.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_locals/from/b.tf rename to refactor/test_data/mv/case_all_locals/from/b.tf diff --git a/refactor/test_data/tf_org/case_all_locals/to/a.tf b/refactor/test_data/mv/case_all_locals/to/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_locals/to/a.tf rename to refactor/test_data/mv/case_all_locals/to/a.tf diff --git a/refactor/test_data/tf_org/case_all_locals/to/b.tf b/refactor/test_data/mv/case_all_locals/to/b.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_locals/to/b.tf rename to refactor/test_data/mv/case_all_locals/to/b.tf diff --git a/refactor/test_data/tf_org/case_all_locals/to/locals.tf b/refactor/test_data/mv/case_all_locals/to/locals.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_locals/to/locals.tf rename to refactor/test_data/mv/case_all_locals/to/locals.tf diff --git a/refactor/test_data/tf_org/case_all_outputs/from/a.tf b/refactor/test_data/mv/case_all_outputs/from/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_outputs/from/a.tf rename to refactor/test_data/mv/case_all_outputs/from/a.tf diff --git a/refactor/test_data/tf_org/case_all_outputs/to/a.tf b/refactor/test_data/mv/case_all_outputs/to/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_outputs/to/a.tf rename to refactor/test_data/mv/case_all_outputs/to/a.tf diff --git a/refactor/test_data/tf_org/case_all_outputs/to/outputs.tf b/refactor/test_data/mv/case_all_outputs/to/outputs.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_outputs/to/outputs.tf rename to refactor/test_data/mv/case_all_outputs/to/outputs.tf diff --git a/refactor/test_data/tf_org/case_all_vars/from/a.tf b/refactor/test_data/mv/case_all_vars/from/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_vars/from/a.tf rename to refactor/test_data/mv/case_all_vars/from/a.tf diff --git a/refactor/test_data/tf_org/case_all_vars/to/a.tf b/refactor/test_data/mv/case_all_vars/to/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_vars/to/a.tf rename to refactor/test_data/mv/case_all_vars/to/a.tf diff --git a/refactor/test_data/tf_org/case_all_vars/to/variables.tf b/refactor/test_data/mv/case_all_vars/to/variables.tf similarity index 100% rename from refactor/test_data/tf_org/case_all_vars/to/variables.tf rename to refactor/test_data/mv/case_all_vars/to/variables.tf diff --git a/refactor/test_data/tf_org/case_data_single_block/from/a.tf b/refactor/test_data/mv/case_data_single_block/from/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_data_single_block/from/a.tf rename to refactor/test_data/mv/case_data_single_block/from/a.tf diff --git a/refactor/test_data/tf_org/case_data_single_block/from/data.tf b/refactor/test_data/mv/case_data_single_block/from/data.tf similarity index 100% rename from refactor/test_data/tf_org/case_data_single_block/from/data.tf rename to refactor/test_data/mv/case_data_single_block/from/data.tf diff --git a/refactor/test_data/tf_org/case_data_single_block/to/a.tf b/refactor/test_data/mv/case_data_single_block/to/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_data_single_block/to/a.tf rename to refactor/test_data/mv/case_data_single_block/to/a.tf diff --git a/refactor/test_data/tf_org/case_data_single_block/to/data.tf b/refactor/test_data/mv/case_data_single_block/to/data.tf similarity index 100% rename from refactor/test_data/tf_org/case_data_single_block/to/data.tf rename to refactor/test_data/mv/case_data_single_block/to/data.tf diff --git a/refactor/test_data/tf_org/case_data_single_block_new_file/from/a.tf b/refactor/test_data/mv/case_data_single_block_new_file/from/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_data_single_block_new_file/from/a.tf rename to refactor/test_data/mv/case_data_single_block_new_file/from/a.tf diff --git a/refactor/test_data/tf_org/case_data_single_block_new_file/to/a.tf b/refactor/test_data/mv/case_data_single_block_new_file/to/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_data_single_block_new_file/to/a.tf rename to refactor/test_data/mv/case_data_single_block_new_file/to/a.tf diff --git a/refactor/test_data/tf_org/case_data_single_block_new_file/to/data.tf b/refactor/test_data/mv/case_data_single_block_new_file/to/data.tf similarity index 100% rename from refactor/test_data/tf_org/case_data_single_block_new_file/to/data.tf rename to refactor/test_data/mv/case_data_single_block_new_file/to/data.tf diff --git a/refactor/test_data/tf_org/case_resource_type/from/a.tf b/refactor/test_data/mv/case_resource_type/from/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_resource_type/from/a.tf rename to refactor/test_data/mv/case_resource_type/from/a.tf diff --git a/refactor/test_data/tf_org/case_resource_type/to/a.tf b/refactor/test_data/mv/case_resource_type/to/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_resource_type/to/a.tf rename to refactor/test_data/mv/case_resource_type/to/a.tf diff --git a/refactor/test_data/tf_org/case_resource_type/to/dest.tf b/refactor/test_data/mv/case_resource_type/to/dest.tf similarity index 100% rename from refactor/test_data/tf_org/case_resource_type/to/dest.tf rename to refactor/test_data/mv/case_resource_type/to/dest.tf diff --git a/refactor/test_data/tf_org/case_single_local/from/a.tf b/refactor/test_data/mv/case_single_local/from/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_single_local/from/a.tf rename to refactor/test_data/mv/case_single_local/from/a.tf diff --git a/refactor/test_data/tf_org/case_single_local/to/a.tf b/refactor/test_data/mv/case_single_local/to/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_single_local/to/a.tf rename to refactor/test_data/mv/case_single_local/to/a.tf diff --git a/refactor/test_data/tf_org/case_single_local/to/locals.tf b/refactor/test_data/mv/case_single_local/to/locals.tf similarity index 100% rename from refactor/test_data/tf_org/case_single_local/to/locals.tf rename to refactor/test_data/mv/case_single_local/to/locals.tf diff --git a/refactor/test_data/tf_org/case_single_var/from/a.tf b/refactor/test_data/mv/case_single_var/from/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_single_var/from/a.tf rename to refactor/test_data/mv/case_single_var/from/a.tf diff --git a/refactor/test_data/tf_org/case_single_var/to/a.tf b/refactor/test_data/mv/case_single_var/to/a.tf similarity index 100% rename from refactor/test_data/tf_org/case_single_var/to/a.tf rename to refactor/test_data/mv/case_single_var/to/a.tf diff --git a/refactor/test_data/tf_org/case_single_var/to/variables.tf b/refactor/test_data/mv/case_single_var/to/variables.tf similarity index 100% rename from refactor/test_data/tf_org/case_single_var/to/variables.tf rename to refactor/test_data/mv/case_single_var/to/variables.tf From 2a45cfd29546026596f0b57f363b8b2774205afa Mon Sep 17 00:00:00 2001 From: Ray Myers Date: Sat, 22 Jan 2022 15:52:48 -0600 Subject: [PATCH 2/5] Refactor allowing update plan to span directories --- refactor/mv.go | 19 ++-- refactor/mv_test.go | 231 ++++++++++++---------------------------- refactor/test_helper.go | 106 ++++++++++++++++++ 3 files changed, 185 insertions(+), 171 deletions(-) create mode 100644 refactor/test_helper.go diff --git a/refactor/mv.go b/refactor/mv.go index 6756d2e..a1b6036 100644 --- a/refactor/mv.go +++ b/refactor/mv.go @@ -14,18 +14,18 @@ func Mv(fromAddressString, toFile, configPath string) (*UpdatePlan, error) { if err != nil { return nil, err } - if err != nil { - return nil, err - } plan := newUpdatePlan() var parsedOutFile *hclwrite.File - if _, err := os.Stat(toFile); errors.Is(err, os.ErrNotExist) { - parsedOutFile, err = ParseHclBytes([]byte{}, toFile) + + // Assume toFile is relative to config path. Will this always be true? + toFilePath := filepath.Join(configPath, toFile) + if _, err := os.Stat(toFilePath); errors.Is(err, os.ErrNotExist) { + parsedOutFile, err = ParseHclBytes([]byte{}, toFilePath) if err != nil { return nil, err } } else { - parsedOutFile, err = ParseHclFile(toFile) + parsedOutFile, err = ParseHclFile(toFilePath) if err != nil { return nil, err } @@ -34,8 +34,7 @@ func Mv(fromAddressString, toFile, configPath string) (*UpdatePlan, error) { beforeOutText := string(parsedOutFile.Bytes()) for _, filename := range filenames { fromPath, _ := filepath.Abs(filename) - toPath, _ := filepath.Abs(toFile) - if fromPath != "" && fromPath != toPath { + if fromPath != "" && fromPath != toFilePath { parsedInFile, err := ParseHclFile(filename) if err != nil { return nil, err @@ -60,8 +59,8 @@ func Mv(fromAddressString, toFile, configPath string) (*UpdatePlan, error) { afterOutText := string(parsedOutFile.Bytes()) diffText, err := diffText(beforeOutText, afterOutText, 3) if len(diffText) > 0 { - fmt.Printf("Diff for %v\n%v\n", toFile, diffText) - plan.addFileUpdate(&FileUpdate{toFile, beforeOutText, afterOutText}) + fmt.Printf("Diff for %v\n%v\n", toFilePath, diffText) + plan.addFileUpdate(&FileUpdate{toFilePath, beforeOutText, afterOutText}) } return &plan, nil } diff --git a/refactor/mv_test.go b/refactor/mv_test.go index 7654c2f..17b22f8 100644 --- a/refactor/mv_test.go +++ b/refactor/mv_test.go @@ -1,16 +1,7 @@ package refactor import ( - "bytes" - "io" - "io/ioutil" - "log" - "os" - "path/filepath" - "strings" "testing" - - "github.com/stretchr/testify/assert" ) func TestMv(t *testing.T) { @@ -28,76 +19,76 @@ func TestMv(t *testing.T) { from: "test_data/mv/case_data_single_block/from", to: "test_data/mv/case_data_single_block/to", }, - { - name: "case_data_single_block_qualified", - args: []string{"data.a", "data.tf"}, - ok: true, - from: "test_data/mv/case_data_single_block/from", - to: "test_data/mv/case_data_single_block/to", - }, - { - name: "case_data_single_block_fully_qualified", - args: []string{"data.a.b", "data.tf"}, - ok: true, - from: "test_data/mv/case_data_single_block/from", - to: "test_data/mv/case_data_single_block/to", - }, - { - name: "case_data_single_block_new_file", - args: []string{"data", "data.tf"}, - ok: true, - from: "test_data/mv/case_data_single_block_new_file/from", - to: "test_data/mv/case_data_single_block_new_file/to", - }, - { - name: "case_all_data_blocks", - args: []string{"data", "data.tf"}, - ok: true, - from: "test_data/mv/case_all_data_blocks/from", - to: "test_data/mv/case_all_data_blocks/to", - }, - { - name: "case_all_vars", - args: []string{"variable", "variables.tf"}, - ok: true, - from: "test_data/mv/case_all_vars/from", - to: "test_data/mv/case_all_vars/to", - }, - { - name: "case_single_var", - args: []string{"variable.a", "variables.tf"}, - ok: true, - from: "test_data/mv/case_single_var/from", - to: "test_data/mv/case_single_var/to", - }, - { - name: "case_all_locals", - args: []string{"locals", "locals.tf"}, - ok: true, - from: "test_data/mv/case_all_locals/from", - to: "test_data/mv/case_all_locals/to", - }, - { - name: "case_single_local", - args: []string{"local.b", "locals.tf"}, - ok: true, - from: "test_data/mv/case_single_local/from", - to: "test_data/mv/case_single_local/to", - }, - { - name: "case_resource_type", - args: []string{"resource.a", "dest.tf"}, - ok: true, - from: "test_data/mv/case_resource_type/from", - to: "test_data/mv/case_resource_type/to", - }, - { - name: "case_all_outputs", - args: []string{"output", "outputs.tf"}, - ok: true, - from: "test_data/mv/case_all_outputs/from", - to: "test_data/mv/case_all_outputs/to", - }, + // { + // name: "case_data_single_block_qualified", + // args: []string{"data.a", "data.tf"}, + // ok: true, + // from: "test_data/mv/case_data_single_block/from", + // to: "test_data/mv/case_data_single_block/to", + // }, + // { + // name: "case_data_single_block_fully_qualified", + // args: []string{"data.a.b", "data.tf"}, + // ok: true, + // from: "test_data/mv/case_data_single_block/from", + // to: "test_data/mv/case_data_single_block/to", + // }, + // { + // name: "case_data_single_block_new_file", + // args: []string{"data", "data.tf"}, + // ok: true, + // from: "test_data/mv/case_data_single_block_new_file/from", + // to: "test_data/mv/case_data_single_block_new_file/to", + // }, + // { + // name: "case_all_data_blocks", + // args: []string{"data", "data.tf"}, + // ok: true, + // from: "test_data/mv/case_all_data_blocks/from", + // to: "test_data/mv/case_all_data_blocks/to", + // }, + // { + // name: "case_all_vars", + // args: []string{"variable", "variables.tf"}, + // ok: true, + // from: "test_data/mv/case_all_vars/from", + // to: "test_data/mv/case_all_vars/to", + // }, + // { + // name: "case_single_var", + // args: []string{"variable.a", "variables.tf"}, + // ok: true, + // from: "test_data/mv/case_single_var/from", + // to: "test_data/mv/case_single_var/to", + // }, + // { + // name: "case_all_locals", + // args: []string{"locals", "locals.tf"}, + // ok: true, + // from: "test_data/mv/case_all_locals/from", + // to: "test_data/mv/case_all_locals/to", + // }, + // { + // name: "case_single_local", + // args: []string{"local.b", "locals.tf"}, + // ok: true, + // from: "test_data/mv/case_single_local/from", + // to: "test_data/mv/case_single_local/to", + // }, + // { + // name: "case_resource_type", + // args: []string{"resource.a", "dest.tf"}, + // ok: true, + // from: "test_data/mv/case_resource_type/from", + // to: "test_data/mv/case_resource_type/to", + // }, + // { + // name: "case_all_outputs", + // args: []string{"output", "outputs.tf"}, + // ok: true, + // from: "test_data/mv/case_all_outputs/from", + // to: "test_data/mv/case_all_outputs/to", + // }, } for _, tc := range cases { @@ -110,85 +101,3 @@ func TestMv(t *testing.T) { }) } } - -func copyFile(src string, dest string) { - sourceFile, err := os.Open(src) - if err != nil { - log.Fatal(err) - } - defer sourceFile.Close() - - // Create new file - newFile, err := os.Create(dest) - if err != nil { - log.Fatal(err) - } - defer newFile.Close() - - _, err = io.Copy(newFile, sourceFile) - if err != nil { - log.Fatal(err) - } -} - -func fileNames(vs []os.FileInfo) []string { - vsm := make([]string, len(vs)) - for i, v := range vs { - vsm[i] = v.Name() - } - return vsm -} - -// assertMockCmd is a high-level test helper to run a given mock command with -// arguments and check if an error and its stdout are expected. -func assertMockCmdFileOutput(t *testing.T, name string, from string, to string, plan *UpdatePlan) { - expectedFiles, err := ioutil.ReadDir(to) - if err != nil { - log.Fatal(err) - } - startingFiles, err := ioutil.ReadDir(from) - - filenameToContents := make(map[string]string) - for _, file := range startingFiles { - filePath := filepath.Join(from, file.Name()) - buf, err := ioutil.ReadFile(filePath) - if err != nil { - log.Fatalf("Loading %v - %v", filePath, err) - } - filenameToContents[filepath.Base(file.Name())] = string(buf) - } - for _, update := range plan.FileUpdates { - filenameToContents[filepath.Base(update.Filename)] = update.AfterText - } - - if len(expectedFiles) != len(filenameToContents) { - actualFilenames := []string{} - for k := range filenameToContents { - actualFilenames = append(actualFilenames, k) - } - t.Fatalf("Expected files to be %v, but found %v", fileNames(expectedFiles), actualFilenames) - } - for _, file := range expectedFiles { - assertFileHasContents(t, to+"/"+file.Name(), filenameToContents[filepath.Base(file.Name())]) - } -} - -func assertFileHasContents(t *testing.T, expectedFile string, actualContents string) { - expectedBuf, err := ioutil.ReadFile(expectedFile) - if err != nil { - log.Fatalf("Loading %v - %v", expectedFile, err) - } - expected := strings.TrimSpace(string(normalizeNewlines(expectedBuf))) - actual := strings.TrimSpace(string(normalizeNewlines([]byte(actualContents)))) - assert.Equal(t, expected, actual, "File %v", expectedFile) -} - -// NormalizeNewlines normalizes \r\n (windows) and \r (mac) -// into \n (unix) -func normalizeNewlines(d []byte) []byte { - // replace CR LF \r\n (windows) with LF \n (unix) - d = bytes.Replace(d, []byte{13, 10}, []byte{10}, -1) - // replace CF \r (mac) with LF \n (unix) - d = bytes.Replace(d, []byte{13}, []byte{10}, -1) - return d -} diff --git a/refactor/test_helper.go b/refactor/test_helper.go new file mode 100644 index 0000000..cbaa166 --- /dev/null +++ b/refactor/test_helper.go @@ -0,0 +1,106 @@ +package refactor + +import ( + "bytes" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func fileNames(vs []os.FileInfo) []string { + vsm := make([]string, len(vs)) + for i, v := range vs { + vsm[i] = v.Name() + } + return vsm +} + +// assertMockCmd is a high-level test helper to run a given mock command with +// arguments and check if an error and its stdout are expected. +func assertMockCmdFileOutput(t *testing.T, name string, from string, to string, plan *UpdatePlan) { + // from, err := filepath.Abs(from) + // if err != nil { + // log.Fatal(err) + // } + + expectedFiles := []string{} + err := filepath.Walk(to, + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + rel := relativise(to, path) + expectedFiles = append(expectedFiles, rel) + } + return nil + }) + if err != nil { + log.Println(err) + } + // expectedFiles, err := ioutil.ReadDir(to) + // if err != nil { + // log.Fatal(err) + // } + startingFiles, err := ioutil.ReadDir(from) + + filenameToContents := make(map[string]string) + for _, file := range startingFiles { + filePath := filepath.Join(from, file.Name()) + buf, err := ioutil.ReadFile(filePath) + if err != nil { + log.Fatalf("Loading %v - %v", filePath, err) + } + filenameToContents[file.Name()] = string(buf) + } + for _, update := range plan.FileUpdates { + filenameToContents[relativise(from, update.Filename)] = update.AfterText + } + + if len(expectedFiles) != len(filenameToContents) { + actualFilenames := []string{} + for k := range filenameToContents { + actualFilenames = append(actualFilenames, k) + } + t.Fatalf("Expected files to be %v, but found %v", expectedFiles, actualFilenames) + } + for _, file := range expectedFiles { + if actualContents, ok := filenameToContents[file]; ok { + assertFileHasContents(t, filepath.Join(to, file), actualContents) + } else { + t.Fatalf("Didn't find found %v in:\n%v", file, filenameToContents) + } + } +} + +func relativise(from, name string) string { + absUpdatePath, _ := filepath.Abs(name) + absFromPath, _ := filepath.Abs(from) + relUpdatePath, _ := filepath.Rel(absFromPath, absUpdatePath) + return relUpdatePath +} + +func assertFileHasContents(t *testing.T, expectedFile string, actualContents string) { + expectedBuf, err := ioutil.ReadFile(expectedFile) + if err != nil { + log.Fatalf("Loading %v - %v", expectedFile, err) + } + expected := strings.TrimSpace(string(normalizeNewlines(expectedBuf))) + actual := strings.TrimSpace(string(normalizeNewlines([]byte(actualContents)))) + assert.Equal(t, expected, actual, "File %v", expectedFile) +} + +// NormalizeNewlines normalizes \r\n (windows) and \r (mac) +// into \n (unix) +func normalizeNewlines(d []byte) []byte { + // replace CR LF \r\n (windows) with LF \n (unix) + d = bytes.Replace(d, []byte{13, 10}, []byte{10}, -1) + // replace CF \r (mac) with LF \n (unix) + d = bytes.Replace(d, []byte{13}, []byte{10}, -1) + return d +} From fd04f65908ac51aa51fa1bcb0dad32ce602824b6 Mon Sep 17 00:00:00 2001 From: Ray Myers Date: Sat, 22 Jan 2022 16:15:01 -0600 Subject: [PATCH 3/5] refactor --- refactor/mv.go | 33 +++++++++++++++++++-------------- refactor/rename.go | 14 +++++++++----- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/refactor/mv.go b/refactor/mv.go index a1b6036..ba71d2a 100644 --- a/refactor/mv.go +++ b/refactor/mv.go @@ -73,7 +73,7 @@ func findOrCreateLocalsBlock(parsedFile *hclwrite.File) *hclwrite.Block { return parsedFile.Body().AppendNewBlock("locals", []string{}) } -func moveLocals(parsedInFile, parsedOutFile *hclwrite.File) { +func moveLocals(parsedInFile, parsedOutFile *hclwrite.File) bool { for _, block := range parsedInFile.Body().Blocks() { if block.Type() == "locals" { @@ -87,12 +87,13 @@ func moveLocals(parsedInFile, parsedOutFile *hclwrite.File) { if !parsedInFile.Body().RemoveBlock(block) { fmt.Printf("WARN locals block could not be removed\n") } + return true } } + return false } -func moveLocal(localName string, parsedInFile, parsedOutFile *hclwrite.File) { - fmt.Printf("moveLocal %v\n", localName) +func moveLocal(localName string, parsedInFile, parsedOutFile *hclwrite.File) bool { for _, block := range parsedInFile.Body().Blocks() { if block.Type() == "locals" { @@ -100,11 +101,15 @@ func moveLocal(localName string, parsedInFile, parsedOutFile *hclwrite.File) { if attr != nil { toLocalsBlock := findOrCreateLocalsBlock(parsedOutFile) toLocalsBlock.Body().AppendUnstructuredTokens(attr.BuildTokens(nil)) + + block.Body().RemoveAttribute(localName) + // This can leave an empty block. Maybe check for that. + return true } - block.Body().RemoveAttribute(localName) - // This can leave an empty block. Maybe check for that. + } } + return false } func labelsEqual(a, b []string) bool { @@ -126,30 +131,30 @@ func min(a, b int) int { return b } -func moveBlock(addr *Address, parsedInFile, parsedOutFile *hclwrite.File) { +func moveBlock(addr *Address, parsedInFile, parsedOutFile *hclwrite.File) bool { + found := false addrLabels := addr.labels for _, block := range parsedInFile.Body().Blocks() { blockLabelsLimited := block.Labels()[0:min(len(addrLabels), len(block.Labels()))] if string(addr.BlockType()) == block.Type() && matchLabels(addr.labels, blockLabelsLimited) { - fmt.Printf("## Block matched %v %v\n", block.Type(), block.Labels()) + // fmt.Printf("## Block matched %v %v\n", block.Type(), block.Labels()) parsedOutFile.Body().AppendNewline() parsedOutFile.Body().AppendBlock(block) if !parsedInFile.Body().RemoveBlock(block) { fmt.Printf("WARN locals block could not be removed\n") } + found = true } } + return found } -func moveAddrToFile(addr *Address, parsedInFile, parsedOutFile *hclwrite.File) error { +func moveAddrToFile(addr *Address, parsedInFile, parsedOutFile *hclwrite.File) bool { if addr.elementType == TypeLocal && len(addr.labels) == 0 { - moveLocals(parsedInFile, parsedOutFile) + return moveLocals(parsedInFile, parsedOutFile) } else if addr.elementType == TypeLocal { localName := addr.labels[0] - moveLocal(localName, parsedInFile, parsedOutFile) - } else { - moveBlock(addr, parsedInFile, parsedOutFile) + return moveLocal(localName, parsedInFile, parsedOutFile) } - - return nil + return moveBlock(addr, parsedInFile, parsedOutFile) } diff --git a/refactor/rename.go b/refactor/rename.go index 2f8931f..91e8d6d 100644 --- a/refactor/rename.go +++ b/refactor/rename.go @@ -68,11 +68,7 @@ func RenameInFile(filename string, file *hclwrite.File, fromAddress, toAddress * block.SetType(string(toAddress.BlockType())) block.SetLabels(toAddress.labels) if fromAddress.elementType == TypeResource && toAddress.elementType == TypeResource { - file.Body().AppendNewline() - movedBlock := file.Body().AppendNewBlock("moved", []string{}) - - movedBlock.Body().SetAttributeTraversal("from", createTraversal(fromAddress.labels)) - movedBlock.Body().SetAttributeTraversal("to", createTraversal(toAddress.labels)) + AddMovedBlock(file, fromAddress, toAddress) } } } @@ -81,6 +77,14 @@ func RenameInFile(filename string, file *hclwrite.File, fromAddress, toAddress * return nil } +func AddMovedBlock(file *hclwrite.File, fromAddress, toAddress *Address) { + file.Body().AppendNewline() + movedBlock := file.Body().AppendNewBlock("moved", []string{}) + + movedBlock.Body().SetAttributeTraversal("from", createTraversal(fromAddress.labels)) + movedBlock.Body().SetAttributeTraversal("to", createTraversal(toAddress.labels)) +} + func RenameLocalInFile(filename string, file *hclwrite.File, fromAddress, toAddress *Address) error { fromName := fromAddress.labels[0] toName := toAddress.labels[0] From bd8fa570d1afad8698af0adebe3ae01bb38bddb0 Mon Sep 17 00:00:00 2001 From: Ray Myers Date: Sat, 22 Jan 2022 16:48:57 -0600 Subject: [PATCH 4/5] Basic extract module, not reference aware --- refactor/extract_module.go | 91 +++++++++++++++++++ refactor/extract_module_test.go | 33 +++++++ refactor/rename.go | 16 +++- .../case_one_resource/from/main.tf | 5 + .../case_one_resource/to/main.tf | 12 +++ .../case_one_resource/to/mymodule/main.tf | 2 + refactor/test_helper.go | 9 -- 7 files changed, 154 insertions(+), 14 deletions(-) create mode 100644 refactor/extract_module.go create mode 100644 refactor/extract_module_test.go create mode 100644 refactor/test_data/extract_module/case_one_resource/from/main.tf create mode 100644 refactor/test_data/extract_module/case_one_resource/to/main.tf create mode 100644 refactor/test_data/extract_module/case_one_resource/to/mymodule/main.tf diff --git a/refactor/extract_module.go b/refactor/extract_module.go new file mode 100644 index 0000000..64dc5b5 --- /dev/null +++ b/refactor/extract_module.go @@ -0,0 +1,91 @@ +package refactor + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/raymyers/hcl/v2/hclwrite" + "github.com/zclconf/go-cty/cty" +) + +func Extract(addresses []string, toFolder, configPath string) (*UpdatePlan, error) { + filenames, err := filepath.Glob(configPath + "/*.tf") + if err != nil { + return nil, err + } + if err != nil { + return nil, err + } + plan := newUpdatePlan() + var parsedOutFile *hclwrite.File + toFile := filepath.Join(configPath, toFolder, "main.tf") + if _, err := os.Stat(toFile); errors.Is(err, os.ErrNotExist) { + parsedOutFile, err = ParseHclBytes([]byte{}, toFile) + if err != nil { + return nil, err + } + } else { + parsedOutFile, err = ParseHclFile(toFile) + if err != nil { + return nil, err + } + } + + beforeOutText := string(parsedOutFile.Bytes()) + for _, filename := range filenames { + fromPath, _ := filepath.Abs(filename) + toPath, _ := filepath.Abs(toFile) + if fromPath != "" && fromPath != toPath { + parsedInFile, err := ParseHclFile(filename) + beforeText := string(parsedInFile.Bytes()) + for _, fromAddressText := range addresses { + fromAddress := ParseAddress(fromAddressText) + + if err != nil { + return nil, err + } + + if err != nil { + return nil, err + } + if moveAddrToFile(fromAddress, parsedInFile, parsedOutFile) { + if fromAddress.elementType == TypeResource { + moduleName := filepath.Base(toFolder) + toAddress := ParseAddress("module." + moduleName + "." + strings.Join(fromAddress.labels, ".")) + AddModuleBlock(parsedInFile, moduleName, toFolder) + AddMovedBlock(parsedInFile, fromAddress, toAddress) + } + } + } + afterText := string(parsedInFile.Bytes()) + if err != nil { + return nil, err + } + + diffText, err := diffText(beforeText, afterText, 3) + if len(diffText) > 0 { + fmt.Printf("Diff for %v\n%v\n", filename, diffText) + plan.addFileUpdate(&FileUpdate{filename, beforeText, afterText}) + } + + } + + } + afterOutText := string(parsedOutFile.Bytes()) + diffText, err := diffText(beforeOutText, afterOutText, 3) + if len(diffText) > 0 { + fmt.Printf("Diff for %v\n%v\n", toFile, diffText) + plan.addFileUpdate(&FileUpdate{toFile, beforeOutText, afterOutText}) + } + return &plan, nil +} + +func AddModuleBlock(file *hclwrite.File, moduleName, toFolder string) { + file.Body().AppendNewline() + movedBlock := file.Body().AppendNewBlock("module", []string{moduleName}) + + movedBlock.Body().SetAttributeValue("source", cty.StringVal(toFolder)) +} diff --git a/refactor/extract_module_test.go b/refactor/extract_module_test.go new file mode 100644 index 0000000..61a807e --- /dev/null +++ b/refactor/extract_module_test.go @@ -0,0 +1,33 @@ +package refactor + +import ( + "testing" +) + +func TestExtract(t *testing.T) { + cases := []struct { + name string + args []string + ok bool + from string + to string + }{ + { + name: "case_data_single_block", + args: []string{"a.a", "mymodule"}, + ok: true, + from: "test_data/extract_module/case_one_resource/from", + to: "test_data/extract_module/case_one_resource/to", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + plan, err := Extract(tc.args[0:], tc.args[len(tc.args)-1], tc.from) + if (err == nil) != tc.ok { + + } + assertMockCmdFileOutput(t, tc.name, tc.from, tc.to, plan) + }) + } +} diff --git a/refactor/rename.go b/refactor/rename.go index 91e8d6d..62d3a05 100644 --- a/refactor/rename.go +++ b/refactor/rename.go @@ -42,13 +42,19 @@ func Rename(fromAddressString, toAddressString, configPath string) (*UpdatePlan, return &plan, nil } -func createTraversal(labels []string) (traversal hcl.Traversal) { +func createTraversal(address *Address) (traversal hcl.Traversal) { + labels := address.labels[1:] + root := address.labels[0] + if address.elementType == TypeModule { + root = "module" + labels = address.labels + } traversal = hcl.Traversal{ hcl.TraverseRoot{ - Name: labels[0], + Name: root, }, } - for _, label := range labels[1:] { + for _, label := range labels { traversal = append(traversal, hcl.TraverseAttr{ Name: label, }) @@ -81,8 +87,8 @@ func AddMovedBlock(file *hclwrite.File, fromAddress, toAddress *Address) { file.Body().AppendNewline() movedBlock := file.Body().AppendNewBlock("moved", []string{}) - movedBlock.Body().SetAttributeTraversal("from", createTraversal(fromAddress.labels)) - movedBlock.Body().SetAttributeTraversal("to", createTraversal(toAddress.labels)) + movedBlock.Body().SetAttributeTraversal("from", createTraversal(fromAddress)) + movedBlock.Body().SetAttributeTraversal("to", createTraversal(toAddress)) } func RenameLocalInFile(filename string, file *hclwrite.File, fromAddress, toAddress *Address) error { diff --git a/refactor/test_data/extract_module/case_one_resource/from/main.tf b/refactor/test_data/extract_module/case_one_resource/from/main.tf new file mode 100644 index 0000000..b3d0e35 --- /dev/null +++ b/refactor/test_data/extract_module/case_one_resource/from/main.tf @@ -0,0 +1,5 @@ +resource "a" "a" { +} + +resource "a" "b" { +} diff --git a/refactor/test_data/extract_module/case_one_resource/to/main.tf b/refactor/test_data/extract_module/case_one_resource/to/main.tf new file mode 100644 index 0000000..52ded8f --- /dev/null +++ b/refactor/test_data/extract_module/case_one_resource/to/main.tf @@ -0,0 +1,12 @@ + +resource "a" "b" { +} + +module "mymodule" { + source = "mymodule" +} + +moved { + from = a.a + to = module.mymodule.a.a +} diff --git a/refactor/test_data/extract_module/case_one_resource/to/mymodule/main.tf b/refactor/test_data/extract_module/case_one_resource/to/mymodule/main.tf new file mode 100644 index 0000000..798e517 --- /dev/null +++ b/refactor/test_data/extract_module/case_one_resource/to/mymodule/main.tf @@ -0,0 +1,2 @@ +resource "a" "a" { +} diff --git a/refactor/test_helper.go b/refactor/test_helper.go index cbaa166..9e7d73c 100644 --- a/refactor/test_helper.go +++ b/refactor/test_helper.go @@ -23,11 +23,6 @@ func fileNames(vs []os.FileInfo) []string { // assertMockCmd is a high-level test helper to run a given mock command with // arguments and check if an error and its stdout are expected. func assertMockCmdFileOutput(t *testing.T, name string, from string, to string, plan *UpdatePlan) { - // from, err := filepath.Abs(from) - // if err != nil { - // log.Fatal(err) - // } - expectedFiles := []string{} err := filepath.Walk(to, func(path string, info os.FileInfo, err error) error { @@ -43,10 +38,6 @@ func assertMockCmdFileOutput(t *testing.T, name string, from string, to string, if err != nil { log.Println(err) } - // expectedFiles, err := ioutil.ReadDir(to) - // if err != nil { - // log.Fatal(err) - // } startingFiles, err := ioutil.ReadDir(from) filenameToContents := make(map[string]string) From 88999a7c0055a002b593f3096ae37f8cf1170676 Mon Sep 17 00:00:00 2001 From: Ray Myers Date: Sun, 23 Jan 2022 00:13:12 -0600 Subject: [PATCH 5/5] Utils to find all references --- refactor/extract_module.go | 2 - refactor/mv_test.go | 140 ++++++++++++++++++------------------ refactor/references.go | 43 +++++++++++ refactor/references_test.go | 92 ++++++++++++++++++++++++ refactor/test_helper.go | 2 +- 5 files changed, 206 insertions(+), 73 deletions(-) create mode 100644 refactor/references.go create mode 100644 refactor/references_test.go diff --git a/refactor/extract_module.go b/refactor/extract_module.go index 64dc5b5..582479a 100644 --- a/refactor/extract_module.go +++ b/refactor/extract_module.go @@ -70,9 +70,7 @@ func Extract(addresses []string, toFolder, configPath string) (*UpdatePlan, erro fmt.Printf("Diff for %v\n%v\n", filename, diffText) plan.addFileUpdate(&FileUpdate{filename, beforeText, afterText}) } - } - } afterOutText := string(parsedOutFile.Bytes()) diffText, err := diffText(beforeOutText, afterOutText, 3) diff --git a/refactor/mv_test.go b/refactor/mv_test.go index 17b22f8..177d328 100644 --- a/refactor/mv_test.go +++ b/refactor/mv_test.go @@ -19,76 +19,76 @@ func TestMv(t *testing.T) { from: "test_data/mv/case_data_single_block/from", to: "test_data/mv/case_data_single_block/to", }, - // { - // name: "case_data_single_block_qualified", - // args: []string{"data.a", "data.tf"}, - // ok: true, - // from: "test_data/mv/case_data_single_block/from", - // to: "test_data/mv/case_data_single_block/to", - // }, - // { - // name: "case_data_single_block_fully_qualified", - // args: []string{"data.a.b", "data.tf"}, - // ok: true, - // from: "test_data/mv/case_data_single_block/from", - // to: "test_data/mv/case_data_single_block/to", - // }, - // { - // name: "case_data_single_block_new_file", - // args: []string{"data", "data.tf"}, - // ok: true, - // from: "test_data/mv/case_data_single_block_new_file/from", - // to: "test_data/mv/case_data_single_block_new_file/to", - // }, - // { - // name: "case_all_data_blocks", - // args: []string{"data", "data.tf"}, - // ok: true, - // from: "test_data/mv/case_all_data_blocks/from", - // to: "test_data/mv/case_all_data_blocks/to", - // }, - // { - // name: "case_all_vars", - // args: []string{"variable", "variables.tf"}, - // ok: true, - // from: "test_data/mv/case_all_vars/from", - // to: "test_data/mv/case_all_vars/to", - // }, - // { - // name: "case_single_var", - // args: []string{"variable.a", "variables.tf"}, - // ok: true, - // from: "test_data/mv/case_single_var/from", - // to: "test_data/mv/case_single_var/to", - // }, - // { - // name: "case_all_locals", - // args: []string{"locals", "locals.tf"}, - // ok: true, - // from: "test_data/mv/case_all_locals/from", - // to: "test_data/mv/case_all_locals/to", - // }, - // { - // name: "case_single_local", - // args: []string{"local.b", "locals.tf"}, - // ok: true, - // from: "test_data/mv/case_single_local/from", - // to: "test_data/mv/case_single_local/to", - // }, - // { - // name: "case_resource_type", - // args: []string{"resource.a", "dest.tf"}, - // ok: true, - // from: "test_data/mv/case_resource_type/from", - // to: "test_data/mv/case_resource_type/to", - // }, - // { - // name: "case_all_outputs", - // args: []string{"output", "outputs.tf"}, - // ok: true, - // from: "test_data/mv/case_all_outputs/from", - // to: "test_data/mv/case_all_outputs/to", - // }, + { + name: "case_data_single_block_qualified", + args: []string{"data.a", "data.tf"}, + ok: true, + from: "test_data/mv/case_data_single_block/from", + to: "test_data/mv/case_data_single_block/to", + }, + { + name: "case_data_single_block_fully_qualified", + args: []string{"data.a.b", "data.tf"}, + ok: true, + from: "test_data/mv/case_data_single_block/from", + to: "test_data/mv/case_data_single_block/to", + }, + { + name: "case_data_single_block_new_file", + args: []string{"data", "data.tf"}, + ok: true, + from: "test_data/mv/case_data_single_block_new_file/from", + to: "test_data/mv/case_data_single_block_new_file/to", + }, + { + name: "case_all_data_blocks", + args: []string{"data", "data.tf"}, + ok: true, + from: "test_data/mv/case_all_data_blocks/from", + to: "test_data/mv/case_all_data_blocks/to", + }, + { + name: "case_all_vars", + args: []string{"variable", "variables.tf"}, + ok: true, + from: "test_data/mv/case_all_vars/from", + to: "test_data/mv/case_all_vars/to", + }, + { + name: "case_single_var", + args: []string{"variable.a", "variables.tf"}, + ok: true, + from: "test_data/mv/case_single_var/from", + to: "test_data/mv/case_single_var/to", + }, + { + name: "case_all_locals", + args: []string{"locals", "locals.tf"}, + ok: true, + from: "test_data/mv/case_all_locals/from", + to: "test_data/mv/case_all_locals/to", + }, + { + name: "case_single_local", + args: []string{"local.b", "locals.tf"}, + ok: true, + from: "test_data/mv/case_single_local/from", + to: "test_data/mv/case_single_local/to", + }, + { + name: "case_resource_type", + args: []string{"resource.a", "dest.tf"}, + ok: true, + from: "test_data/mv/case_resource_type/from", + to: "test_data/mv/case_resource_type/to", + }, + { + name: "case_all_outputs", + args: []string{"output", "outputs.tf"}, + ok: true, + from: "test_data/mv/case_all_outputs/from", + to: "test_data/mv/case_all_outputs/to", + }, } for _, tc := range cases { diff --git a/refactor/references.go b/refactor/references.go new file mode 100644 index 0000000..29bf347 --- /dev/null +++ b/refactor/references.go @@ -0,0 +1,43 @@ +package refactor + +import ( + "strings" + + "github.com/raymyers/hcl/v2/hclwrite" +) + +func findReferencingExpresssions(b *hclwrite.Body, address *Address) []*hclwrite.Expression { + var matched []*hclwrite.Expression + addReferencingExpresssions(b, address, &matched) + return matched +} + +func addReferencingExpresssions(body *hclwrite.Body, address *Address, matched *[]*hclwrite.Expression) { + addressRef := address.RefNameArray() + for _, attr := range body.Attributes() { + expr := attr.Expr() + for _, varTrav := range expr.Variables() { + travLabelsToMatch := traversalLabels(varTrav) + println("====") + println(strings.Join(travLabelsToMatch, "::")) + println(strings.Join(addressRef, "::")) + if len(travLabelsToMatch) > len(addressRef) { + travLabelsToMatch = travLabelsToMatch[0:len(addressRef)] + } + if matchLabels(addressRef, travLabelsToMatch) { + *matched = append(*matched, expr) + } + } + } + for _, block := range body.Blocks() { + addReferencingExpresssions(block.Body(), address, matched) + } +} + +func traversalLabels(trav *hclwrite.Traversal) []string { + // hclwrite currently has almost no API for Traversal, working around. + buf := strings.Builder{} + trav.BuildTokens(nil).WriteTo(&buf) + // This won't work in all cases, like arrays. + return strings.Split(strings.TrimSpace(buf.String()), ".") +} diff --git a/refactor/references_test.go b/refactor/references_test.go new file mode 100644 index 0000000..4b0d07f --- /dev/null +++ b/refactor/references_test.go @@ -0,0 +1,92 @@ +package refactor + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReferences(t *testing.T) { + cases := []struct { + name string + addr string + src string + count int + }{ + { + name: "one_ref", + addr: "a.b.c", + src: ` + b { + a = a.b.c + b = a.b.d + } + `, + count: 1, + }, + { + name: "two_refs", + addr: "a.b.c", + src: ` + b { + a = a.b.c + } + d { + z = a.b.c + } + `, + count: 2, + }, + { + name: "ref_in_nested_block", + addr: "a.b.c", + src: ` + b { + d { + z = a.b.c + } + } + `, + count: 1, + }, + { + name: "addr_shorter", + addr: "a.b", + src: ` + b { + y = a.b.d + z = a.b.c + } + `, + count: 2, + }, + { + name: "addr_longer", + addr: "a.b.c.d", + src: ` + b { + z = a.b.c + } + `, + count: 0, + }, + { + name: "interpolation", + addr: "a.b", + src: ` + b { + z = "prefix${a.b.c}" + } + `, + count: 1, + }, + } + for _, tc := range cases { + hclFile, err := ParseHclBytes([]byte(tc.src), "test.tf") + if err != nil { + t.Fatalf("unexpected err = %s", err) + } + found := findReferencingExpresssions(hclFile.Body(), ParseAddress(tc.addr)) + assert.Equal(t, tc.count, len(found), "Case %v", tc.name) + } +} diff --git a/refactor/test_helper.go b/refactor/test_helper.go index 9e7d73c..c19c634 100644 --- a/refactor/test_helper.go +++ b/refactor/test_helper.go @@ -29,7 +29,7 @@ func assertMockCmdFileOutput(t *testing.T, name string, from string, to string, if err != nil { return err } - if !info.IsDir() { + if !info.IsDir() && filepath.Ext(info.Name()) == ".tf" { rel := relativise(to, path) expectedFiles = append(expectedFiles, rel) }