diff --git a/cmd/terraform-j2md/main.go b/cmd/terraform-j2md/main.go
index 772f9f3..3f8deae 100644
--- a/cmd/terraform-j2md/main.go
+++ b/cmd/terraform-j2md/main.go
@@ -22,12 +22,12 @@ func main() {
}
func run() int {
- planData, err := terraform.NewPlanData(os.Stdin)
+ planData, err := terraform.NewPlanData(os.Stdin, escapeHTML)
if err != nil {
fmt.Fprintf(os.Stderr, "cannot parse input as Terraform plan JSON: %v", err)
return 1
}
- if err = planData.Render(os.Stdout, escapeHTML); err != nil {
+ if err = planData.Render(os.Stdout); err != nil {
fmt.Fprintf(os.Stderr, "cannot render: %v", err)
return 1
}
diff --git a/go.mod b/go.mod
index 1e71984..1539407 100644
--- a/go.mod
+++ b/go.mod
@@ -3,14 +3,15 @@ module github.com/reproio/terraform-j2md
go 1.18
require (
- github.com/hashicorp/terraform-json v0.14.0
+ github.com/hashicorp/terraform-json v0.17.2-0.20230912071934-9901d28699bc
github.com/pmezard/go-difflib v1.0.0
)
require (
- github.com/hashicorp/go-version v1.5.0 // indirect
+ github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
+ github.com/hashicorp/go-version v1.6.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
- github.com/zclconf/go-cty v1.10.0 // indirect
- golang.org/x/text v0.3.5 // indirect
+ github.com/zclconf/go-cty v1.14.0 // indirect
+ golang.org/x/text v0.11.0 // indirect
)
diff --git a/go.sum b/go.sum
index f3fb121..87da965 100644
--- a/go.sum
+++ b/go.sum
@@ -1,22 +1,11 @@
-github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
-github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
+github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
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/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
-github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E=
-github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s=
-github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
+github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/terraform-json v0.17.2-0.20230912071934-9901d28699bc h1:ZtMfoibHiPAYJykA5nuHryaNoNDvfuREGWnIvukMb2Y=
+github.com/hashicorp/terraform-json v0.17.2-0.20230912071934-9901d28699bc/go.mod h1:0a5tk65jPDbGo2lEMmvmwwvM0qCbOhW33hXtGrJQBgc=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
@@ -24,28 +13,8 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sebdah/goldie v1.0.0 h1:9GNhIat69MSlz/ndaBg48vl9dF5fI+NBB6kfOxgfkMc=
-github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
-github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
-github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
-github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
-github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0=
-github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
+github.com/zclconf/go-cty v1.14.0 h1:/Xrd39K7DXbHzlisFP9c4pHao4yyf+/Ug9LEz+Y/yhc=
+github.com/zclconf/go-cty v1.14.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI=
-github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
-golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
+golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
diff --git a/internal/terraform/moved_block_renderer.go b/internal/terraform/moved_block_renderer.go
new file mode 100644
index 0000000..8c9b57f
--- /dev/null
+++ b/internal/terraform/moved_block_renderer.go
@@ -0,0 +1,64 @@
+package terraform
+
+import (
+ "bytes"
+ "fmt"
+ tfjson "github.com/hashicorp/terraform-json"
+ "text/template"
+)
+
+const movedBlockTemplateBody = `resource "{{.ResourceChange.Type}}" "{{.ResourceChange.Name}}" {
+{{.Attributes -}}
+}
+`
+
+// These attributes are important (https://github.com/hashicorp/terraform/blob/v1.5.6/internal/command/jsonformat/computed/renderers/block.go#L19-L23)
+var importantAttributes = []string{
+ "id",
+ "name",
+ "tags",
+}
+
+type MovedBlockRenderer struct {
+ ResourceChange *tfjson.ResourceChange
+}
+
+func NewMovedBlockRenderer(resourceChange *tfjson.ResourceChange) *MovedBlockRenderer {
+ return &MovedBlockRenderer{ResourceChange: resourceChange}
+}
+
+func (r *MovedBlockRenderer) Render() (string, error) {
+ var buff bytes.Buffer
+ t, err := template.New("plan").Parse(movedBlockTemplateBody)
+ if err != nil {
+ return "", fmt.Errorf("invalid template text: %w", err)
+ }
+
+ if err := t.Execute(&buff, r); err != nil {
+ return "", fmt.Errorf("failed to render template: %w", err)
+ }
+ return buff.String(), nil
+}
+
+func (r *MovedBlockRenderer) Header() string {
+ return fmt.Sprintf("%s has moved to %s", r.ResourceChange.PreviousAddress, r.ResourceChange.Address)
+}
+
+func (r *MovedBlockRenderer) Attributes() string {
+ var buff bytes.Buffer
+ for _, attr := range importantAttributes {
+ if v, ok := r.ResourceChange.Change.After.(map[string]interface{})[attr]; ok {
+ buff.WriteString(fmt.Sprintf(" %-*s = %s\n", 2, attr, r.value(v)))
+ }
+ }
+ return buff.String()
+}
+
+func (r *MovedBlockRenderer) value(v any) string {
+ switch v.(type) {
+ case string:
+ return fmt.Sprintf("%q", v)
+ default:
+ return fmt.Sprintf("%v", v)
+ }
+}
diff --git a/internal/terraform/plan.go b/internal/terraform/plan.go
index 0050539..2a500f6 100644
--- a/internal/terraform/plan.go
+++ b/internal/terraform/plan.go
@@ -1,17 +1,14 @@
package terraform
import (
- "bytes"
"encoding/json"
"fmt"
"github.com/hashicorp/terraform-json/sanitize"
"github.com/reproio/terraform-j2md/internal/format"
"io"
- "strings"
"text/template"
tfjson "github.com/hashicorp/terraform-json"
- "github.com/pmezard/go-difflib/difflib"
)
const planTemplateBody = `### {{len .CreatedAddresses}} to add, {{len .UpdatedAddresses}} to change, {{len .DeletedAddresses}} to destroy, {{len .ReplacedAddresses}} to replace.
@@ -31,12 +28,16 @@ const planTemplateBody = `### {{len .CreatedAddresses}} to add, {{len .UpdatedAd
- replace{{ range .ReplacedAddresses }}
- {{. -}}
{{end}}{{end}}
+{{- if .MovedAddresses}}
+- moved{{ range .MovedAddresses }}
+ - {{. -}}
+{{end}}{{end}}
{{if .ResourceChanges -}}
Change details
{{ range .ResourceChanges }}
{{codeFence}}diff
# {{.Header}}
-{{.GetUnifiedDiffString}}{{codeFence}}
+{{.Render}}{{codeFence}}
{{end}}
{{end}}`
@@ -46,87 +47,29 @@ type PlanData struct {
UpdatedAddresses []string
DeletedAddresses []string
ReplacedAddresses []string
+ MovedAddresses []string
ResourceChanges []ResourceChangeData
}
-type ResourceChangeData struct {
- ResourceChange *tfjson.ResourceChange
-}
-
-type Config struct {
- EscapeHTML bool
-}
-
-var config Config
-
-func (r ResourceChangeData) GetUnifiedDiffString() (string, error) {
- before, err := r.marshalChangeBefore()
- if err != nil {
- return "", fmt.Errorf("invalid resource changes (before): %w", err)
- }
- after, err := r.marshalChangeAfter()
- if err != nil {
- return "", fmt.Errorf("invalid resource changes (after) : %w", err)
- }
- // Try to parse JSON string in values
- replacer := strings.NewReplacer(`\n`, "\n ", `\"`, "\"")
- diff := difflib.UnifiedDiff{
- A: difflib.SplitLines(replacer.Replace(string(before))),
- B: difflib.SplitLines(replacer.Replace(string(after))),
- Context: 3,
- }
- diffText, err := difflib.GetUnifiedDiffString(diff)
- if err != nil {
- return "", fmt.Errorf("failed to create diff: %w", err)
- }
- return diffText, nil
-}
-func (r ResourceChangeData) Header() string {
- header := fmt.Sprintf("%s.%s %s", r.ResourceChange.Type, r.ResourceChange.Name, r.HeaderSuffix())
-
- if r.ResourceChange.ModuleAddress == "" {
- return header
- } else {
- return fmt.Sprintf("%s.%s", r.ResourceChange.ModuleAddress, header)
- }
+type ResourceChangeDataRenderer interface {
+ Render() (string, error)
+ Header() string
}
-func (r ResourceChangeData) marshalChangeBefore() ([]byte, error) {
- return r.marshalChange(r.ResourceChange.Change.Before)
+type ResourceChangeData struct {
+ ResourceChange *tfjson.ResourceChange
+ Renderer ResourceChangeDataRenderer
}
-func (r ResourceChangeData) marshalChangeAfter() ([]byte, error) {
- return r.marshalChange(r.ResourceChange.Change.After)
+func (r ResourceChangeData) Render() (string, error) {
+ return r.Renderer.Render()
}
-func (r ResourceChangeData) marshalChange(v any) ([]byte, error) {
- var buffer bytes.Buffer
- enc := json.NewEncoder(&buffer)
- enc.SetIndent("", " ")
- enc.SetEscapeHTML(config.EscapeHTML)
- err := enc.Encode(v)
- if err != nil {
- return nil, err
- }
- return buffer.Bytes(), nil
-}
-
-func (r ResourceChangeData) HeaderSuffix() string {
- switch {
- case r.ResourceChange.Change.Actions.Create():
- return "will be created"
- case r.ResourceChange.Change.Actions.Update():
- return "will be updated in-place"
- case r.ResourceChange.Change.Actions.Delete():
- return "will be destroyed"
- case r.ResourceChange.Change.Actions.Replace():
- return "will be replaced"
- }
- return ""
+func (r ResourceChangeData) Header() string {
+ return r.Renderer.Header()
}
-func (plan *PlanData) Render(w io.Writer, escapeHTML bool) error {
- config.EscapeHTML = escapeHTML
+func (plan *PlanData) Render(w io.Writer) error {
funcMap := template.FuncMap{
"codeFence": func() string {
return "````````"
@@ -166,7 +109,7 @@ func processPlan(plan *tfjson.Plan) (*tfjson.Plan, error) {
return plan, nil
}
-func NewPlanData(input io.Reader) (*PlanData, error) {
+func NewPlanData(input io.Reader, escapeHTML bool) (*PlanData, error) {
var err error
var plan tfjson.Plan
if err := json.NewDecoder(input).Decode(&plan); err != nil {
@@ -180,6 +123,15 @@ func NewPlanData(input io.Reader) (*PlanData, error) {
planData := PlanData{}
for _, c := range processedPlan.ResourceChanges {
+ if isMovedBlock(c) {
+ planData.MovedAddresses = append(planData.MovedAddresses, fmt.Sprintf("%s (from %s)", c.Address, c.PreviousAddress))
+ planData.ResourceChanges = append(planData.ResourceChanges, ResourceChangeData{
+ ResourceChange: c,
+ Renderer: NewMovedBlockRenderer(c),
+ })
+ continue
+ }
+
if c.Change.Actions.NoOp() || c.Change.Actions.Read() {
continue
}
@@ -196,7 +148,12 @@ func NewPlanData(input io.Reader) (*PlanData, error) {
}
planData.ResourceChanges = append(planData.ResourceChanges, ResourceChangeData{
ResourceChange: c,
+ Renderer: NewUnifiedDiffRenderer(c, escapeHTML),
})
}
return &planData, nil
}
+
+func isMovedBlock(rc *tfjson.ResourceChange) bool {
+ return rc.Change.Actions.NoOp() && rc.PreviousAddress != ""
+}
diff --git a/internal/terraform/unified_diff_renderer.go b/internal/terraform/unified_diff_renderer.go
new file mode 100644
index 0000000..293511c
--- /dev/null
+++ b/internal/terraform/unified_diff_renderer.go
@@ -0,0 +1,87 @@
+package terraform
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ tfjson "github.com/hashicorp/terraform-json"
+ "github.com/pmezard/go-difflib/difflib"
+ "strings"
+)
+
+type UnifiedDiffRenderer struct {
+ ResourceChange *tfjson.ResourceChange
+ EnableEscapeHTML bool
+}
+
+func NewUnifiedDiffRenderer(resourceChange *tfjson.ResourceChange, enableEscapeHTML bool) *UnifiedDiffRenderer {
+ return &UnifiedDiffRenderer{ResourceChange: resourceChange, EnableEscapeHTML: enableEscapeHTML}
+}
+
+func (r *UnifiedDiffRenderer) Render() (string, error) {
+ before, err := r.marshalChangeBefore()
+ if err != nil {
+ return "", fmt.Errorf("invalid resource changes (before): %w", err)
+ }
+ after, err := r.marshalChangeAfter()
+ if err != nil {
+ return "", fmt.Errorf("invalid resource changes (after) : %w", err)
+ }
+ // Try to parse JSON string in values
+ replacer := strings.NewReplacer(`\n`, "\n ", `\"`, "\"")
+ diff := difflib.UnifiedDiff{
+ A: difflib.SplitLines(replacer.Replace(string(before))),
+ B: difflib.SplitLines(replacer.Replace(string(after))),
+ Context: 3,
+ }
+ diffText, err := difflib.GetUnifiedDiffString(diff)
+ if err != nil {
+ return "", fmt.Errorf("failed to create diff: %w", err)
+ }
+
+ return diffText, nil
+}
+
+func (r *UnifiedDiffRenderer) Header() string {
+ header := fmt.Sprintf("%s.%s %s", r.ResourceChange.Type, r.ResourceChange.Name, r.headerSuffix())
+
+ if r.ResourceChange.ModuleAddress == "" {
+ return header
+ } else {
+ return fmt.Sprintf("%s.%s", r.ResourceChange.ModuleAddress, header)
+ }
+}
+
+func (r *UnifiedDiffRenderer) headerSuffix() string {
+ switch {
+ case r.ResourceChange.Change.Actions.Create():
+ return "will be created"
+ case r.ResourceChange.Change.Actions.Update():
+ return "will be updated in-place"
+ case r.ResourceChange.Change.Actions.Delete():
+ return "will be destroyed"
+ case r.ResourceChange.Change.Actions.Replace():
+ return "will be replaced"
+ }
+ return ""
+}
+
+func (r *UnifiedDiffRenderer) marshalChangeBefore() ([]byte, error) {
+ return r.marshalChange(r.ResourceChange.Change.Before)
+}
+
+func (r *UnifiedDiffRenderer) marshalChangeAfter() ([]byte, error) {
+ return r.marshalChange(r.ResourceChange.Change.After)
+}
+
+func (r *UnifiedDiffRenderer) marshalChange(v any) ([]byte, error) {
+ var buffer bytes.Buffer
+ enc := json.NewEncoder(&buffer)
+ enc.SetIndent("", " ")
+ enc.SetEscapeHTML(r.EnableEscapeHTML)
+ err := enc.Encode(v)
+ if err != nil {
+ return nil, err
+ }
+ return buffer.Bytes(), nil
+}
diff --git a/test/plan_test/plan_test.go b/test/plan_test/plan_test.go
index aa7fc11..e937b66 100644
--- a/test/plan_test/plan_test.go
+++ b/test/plan_test/plan_test.go
@@ -38,7 +38,7 @@ func Test_newPlanData(t *testing.T) {
}
defer file.Close()
- _, err = terraform.NewPlanData(file)
+ _, err = terraform.NewPlanData(file, false)
if (err != nil) != tt.wantErr {
t.Errorf("NewPlanData() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -64,6 +64,7 @@ func Test_render(t *testing.T) {
{name: "include_code_fence", wantErr: false},
{name: "include_module", wantErr: false},
{name: "known_after_apply", wantErr: false},
+ {name: "moved_block", wantErr: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -75,14 +76,14 @@ func Test_render(t *testing.T) {
}
defer file.Close()
- plan, err := terraform.NewPlanData(file)
+ plan, err := terraform.NewPlanData(file, true)
if err != nil {
t.Errorf("cannot parse JSON as plan: %v", err)
return
}
got := bytes.Buffer{}
- err = plan.Render(&got, true)
+ err = plan.Render(&got)
if (err != nil) != tt.wantErr {
t.Errorf("render() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -119,14 +120,14 @@ func Test_render(t *testing.T) {
}
defer file.Close()
- plan, err := terraform.NewPlanData(file)
+ plan, err := terraform.NewPlanData(file, false)
if err != nil {
t.Errorf("cannot parse JSON as plan: %v", err)
return
}
got := bytes.Buffer{}
- err = plan.Render(&got, false)
+ err = plan.Render(&got)
if (err != nil) != tt.wantErr {
t.Errorf("render() error = %v, wantErr %v", err, tt.wantErr)
return
diff --git a/test/testdata/moved_block/expected.md b/test/testdata/moved_block/expected.md
new file mode 100644
index 0000000..8e8ae76
--- /dev/null
+++ b/test/testdata/moved_block/expected.md
@@ -0,0 +1,13 @@
+### 0 to add, 0 to change, 0 to destroy, 0 to replace.
+- moved
+ - random_id.test2 (from random_id.test)
+Change details
+
+````````diff
+# random_id.test has moved to random_id.test2
+resource "random_id" "test2" {
+ id = "qD4MEwtJeTOwqg"
+}
+````````
+
+
diff --git a/test/testdata/moved_block/main.tf b/test/testdata/moved_block/main.tf
new file mode 100644
index 0000000..d9271e5
--- /dev/null
+++ b/test/testdata/moved_block/main.tf
@@ -0,0 +1,25 @@
+terraform {
+ required_providers {
+ env = {
+ source = "tchupp/env"
+ version = "0.0.2"
+ }
+ }
+}
+
+provider "env" {
+ # Configuration options
+}
+
+resource "env_variable" "test" {
+ name = random_id.test2.hex
+}
+
+moved {
+ from = random_id.test
+ to = random_id.test2
+}
+
+resource "random_id" "test2" {
+ byte_length = 10
+}
diff --git a/test/testdata/moved_block/plan.tfplan b/test/testdata/moved_block/plan.tfplan
new file mode 100644
index 0000000..8271099
Binary files /dev/null and b/test/testdata/moved_block/plan.tfplan differ
diff --git a/test/testdata/moved_block/show.json b/test/testdata/moved_block/show.json
new file mode 100644
index 0000000..4293a12
--- /dev/null
+++ b/test/testdata/moved_block/show.json
@@ -0,0 +1,205 @@
+{
+ "format_version": "1.2",
+ "terraform_version": "1.5.3",
+ "planned_values": {
+ "root_module": {
+ "resources": [
+ {
+ "address": "env_variable.test",
+ "mode": "managed",
+ "type": "env_variable",
+ "name": "test",
+ "provider_name": "registry.terraform.io/tchupp/env",
+ "schema_version": 0,
+ "values": {
+ "id": "a83e0c130b497933b0aa",
+ "name": "a83e0c130b497933b0aa",
+ "value": ""
+ },
+ "sensitive_values": {}
+ },
+ {
+ "address": "random_id.test2",
+ "mode": "managed",
+ "type": "random_id",
+ "name": "test2",
+ "provider_name": "registry.terraform.io/hashicorp/random",
+ "schema_version": 0,
+ "values": {
+ "b64_std": "qD4MEwtJeTOwqg==",
+ "b64_url": "qD4MEwtJeTOwqg",
+ "byte_length": 10,
+ "dec": "794502137306233594687658",
+ "hex": "a83e0c130b497933b0aa",
+ "id": "qD4MEwtJeTOwqg",
+ "keepers": null,
+ "prefix": null
+ },
+ "sensitive_values": {}
+ }
+ ]
+ }
+ },
+ "resource_changes": [
+ {
+ "address": "env_variable.test",
+ "mode": "managed",
+ "type": "env_variable",
+ "name": "test",
+ "provider_name": "registry.terraform.io/tchupp/env",
+ "change": {
+ "actions": [
+ "no-op"
+ ],
+ "before": {
+ "id": "a83e0c130b497933b0aa",
+ "name": "a83e0c130b497933b0aa",
+ "value": ""
+ },
+ "after": {
+ "id": "a83e0c130b497933b0aa",
+ "name": "a83e0c130b497933b0aa",
+ "value": ""
+ },
+ "after_unknown": {},
+ "before_sensitive": {
+ "value": true
+ },
+ "after_sensitive": {
+ "value": true
+ }
+ }
+ },
+ {
+ "address": "random_id.test2",
+ "previous_address": "random_id.test",
+ "mode": "managed",
+ "type": "random_id",
+ "name": "test2",
+ "provider_name": "registry.terraform.io/hashicorp/random",
+ "change": {
+ "actions": [
+ "no-op"
+ ],
+ "before": {
+ "b64_std": "qD4MEwtJeTOwqg==",
+ "b64_url": "qD4MEwtJeTOwqg",
+ "byte_length": 10,
+ "dec": "794502137306233594687658",
+ "hex": "a83e0c130b497933b0aa",
+ "id": "qD4MEwtJeTOwqg",
+ "keepers": null,
+ "prefix": null
+ },
+ "after": {
+ "b64_std": "qD4MEwtJeTOwqg==",
+ "b64_url": "qD4MEwtJeTOwqg",
+ "byte_length": 10,
+ "dec": "794502137306233594687658",
+ "hex": "a83e0c130b497933b0aa",
+ "id": "qD4MEwtJeTOwqg",
+ "keepers": null,
+ "prefix": null
+ },
+ "after_unknown": {},
+ "before_sensitive": {},
+ "after_sensitive": {}
+ }
+ }
+ ],
+ "prior_state": {
+ "format_version": "1.0",
+ "terraform_version": "1.5.3",
+ "values": {
+ "root_module": {
+ "resources": [
+ {
+ "address": "env_variable.test",
+ "mode": "managed",
+ "type": "env_variable",
+ "name": "test",
+ "provider_name": "registry.terraform.io/tchupp/env",
+ "schema_version": 0,
+ "values": {
+ "id": "a83e0c130b497933b0aa",
+ "name": "a83e0c130b497933b0aa",
+ "value": ""
+ },
+ "sensitive_values": {
+ "value": true
+ },
+ "depends_on": [
+ "random_id.test2"
+ ]
+ },
+ {
+ "address": "random_id.test2",
+ "mode": "managed",
+ "type": "random_id",
+ "name": "test2",
+ "provider_name": "registry.terraform.io/hashicorp/random",
+ "schema_version": 0,
+ "values": {
+ "b64_std": "qD4MEwtJeTOwqg==",
+ "b64_url": "qD4MEwtJeTOwqg",
+ "byte_length": 10,
+ "dec": "794502137306233594687658",
+ "hex": "a83e0c130b497933b0aa",
+ "id": "qD4MEwtJeTOwqg",
+ "keepers": null,
+ "prefix": null
+ },
+ "sensitive_values": {}
+ }
+ ]
+ }
+ }
+ },
+ "configuration": {
+ "provider_config": {
+ "env": {
+ "name": "env",
+ "full_name": "registry.terraform.io/tchupp/env",
+ "version_constraint": "0.0.2"
+ },
+ "random": {
+ "name": "random",
+ "full_name": "registry.terraform.io/hashicorp/random"
+ }
+ },
+ "root_module": {
+ "resources": [
+ {
+ "address": "env_variable.test",
+ "mode": "managed",
+ "type": "env_variable",
+ "name": "test",
+ "provider_config_key": "env",
+ "expressions": {
+ "name": {
+ "references": [
+ "random_id.test2.hex",
+ "random_id.test2"
+ ]
+ }
+ },
+ "schema_version": 0
+ },
+ {
+ "address": "random_id.test2",
+ "mode": "managed",
+ "type": "random_id",
+ "name": "test2",
+ "provider_config_key": "random",
+ "expressions": {
+ "byte_length": {
+ "constant_value": 10
+ }
+ },
+ "schema_version": 0
+ }
+ ]
+ }
+ },
+ "timestamp": "2023-08-29T08:27:04Z"
+}
diff --git a/test/testdata/moved_block/terraform.tfstate b/test/testdata/moved_block/terraform.tfstate
new file mode 100644
index 0000000..d6a893b
--- /dev/null
+++ b/test/testdata/moved_block/terraform.tfstate
@@ -0,0 +1,53 @@
+{
+ "version": 4,
+ "terraform_version": "1.5.3",
+ "serial": 3,
+ "lineage": "dc23aa52-bb4e-e4f3-e6e1-fdd58d30b617",
+ "outputs": {},
+ "resources": [
+ {
+ "mode": "managed",
+ "type": "env_variable",
+ "name": "test",
+ "provider": "provider[\"registry.terraform.io/tchupp/env\"]",
+ "instances": [
+ {
+ "schema_version": 0,
+ "attributes": {
+ "id": "a83e0c130b497933b0aa",
+ "name": "a83e0c130b497933b0aa",
+ "value": ""
+ },
+ "sensitive_attributes": [],
+ "private": "bnVsbA==",
+ "dependencies": [
+ "random_id.test"
+ ]
+ }
+ ]
+ },
+ {
+ "mode": "managed",
+ "type": "random_id",
+ "name": "test",
+ "provider": "provider[\"registry.terraform.io/hashicorp/random\"]",
+ "instances": [
+ {
+ "schema_version": 0,
+ "attributes": {
+ "b64_std": "qD4MEwtJeTOwqg==",
+ "b64_url": "qD4MEwtJeTOwqg",
+ "byte_length": 10,
+ "dec": "794502137306233594687658",
+ "hex": "a83e0c130b497933b0aa",
+ "id": "qD4MEwtJeTOwqg",
+ "keepers": null,
+ "prefix": null
+ },
+ "sensitive_attributes": []
+ }
+ ]
+ }
+ ],
+ "check_results": null
+}