Skip to content

Commit

Permalink
fix: state parsing string index keys (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
leg100 authored Jul 15, 2024
1 parent 142dde7 commit dd0b91f
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 43 deletions.
2 changes: 1 addition & 1 deletion internal/state/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type (
}

StateFileResourceInstance struct {
IndexKey *int `json:"index_key"`
IndexKey any `json:"index_key"`
Status StateFileResourceInstanceStatus
Attributes json.RawMessage
}
Expand Down
16 changes: 12 additions & 4 deletions internal/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"
"io"
"log/slog"
"strconv"
"strings"

"github.com/leg100/pug/internal/resource"
Expand Down Expand Up @@ -59,11 +58,20 @@ func newState(ws resource.Resource, r io.Reader) (*State, error) {
b.WriteString(res.Type)
b.WriteRune('.')
b.WriteString(res.Name)

if instance.IndexKey != nil {
b.WriteRune('[')
b.WriteString(strconv.Itoa(*instance.IndexKey))
b.WriteRune(']')
switch key := instance.IndexKey.(type) {
case int:
b.WriteString(fmt.Sprintf("[%d]", int(key)))
case float64:
b.WriteString(fmt.Sprintf("[%d]", int(key)))
case string:
b.WriteString(fmt.Sprintf(`["%s"]`, string(key)))
default:
return nil, fmt.Errorf("invalid index key: %#v", instance.IndexKey)
}
}

addr := ResourceAddress(b.String())
var err error
m[addr], err = newResource(state, addr, instance.Attributes)
Expand Down
98 changes: 60 additions & 38 deletions internal/state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,75 @@ import (
"github.com/stretchr/testify/require"
)

func Test_newState(t *testing.T) {
func TestState(t *testing.T) {
mod := module.New(internal.NewTestWorkdir(t), module.Options{Path: "a/b/c"})
ws, err := workspace.New(mod, "dev")
require.NoError(t, err)

// Mimic response from terraform state pull
f, err := os.Open("./testdata/with_mods/terraform.tfstate.d/dev/terraform.tfstate")
require.NoError(t, err)
t.Cleanup(func() {
f.Close()
t.Run("new state", func(t *testing.T) {
// Mimic response from terraform state pull
f, err := os.Open("./testdata/with_mods/terraform.tfstate.d/dev/terraform.tfstate")
require.NoError(t, err)
t.Cleanup(func() {
f.Close()
})

got, err := newState(ws, f)
require.NoError(t, err)

assert.Len(t, got.Resources, 17)

assert.Contains(t, got.Resources, ResourceAddress("random_pet.pet[0]"))
assert.Contains(t, got.Resources, ResourceAddress("random_integer.suffix"))
assert.Contains(t, got.Resources, ResourceAddress("module.child1.random_pet.pet"))
assert.Contains(t, got.Resources, ResourceAddress("module.child1.random_integer.suffix"))
assert.Contains(t, got.Resources, ResourceAddress("module.child2.random_integer.suffix"))
assert.Contains(t, got.Resources, ResourceAddress("module.child2.module.child3.random_integer.suffix"))
assert.Contains(t, got.Resources, ResourceAddress("module.child2.module.child3.random_pet.pet"))

wantAttrs := map[string]any{
"id": "next-thrush",
"keepers": map[string]any{"now": "2024-05-17T17:23:31Z"},
"length": float64(2),
"prefix": nil,
"separator": "-",
}
assert.Equal(t, wantAttrs, got.Resources["random_pet.pet[3]"].Attributes)

assert.True(t, got.Resources["random_pet.pet[3]"].Tainted)
})

got, err := newState(ws, f)
require.NoError(t, err)
t.Run("empty", func(t *testing.T) {
// Mimic empty response from terraform state pull
f := new(bytes.Buffer)

assert.Len(t, got.Resources, 17)

assert.Contains(t, got.Resources, ResourceAddress("random_pet.pet[0]"))
assert.Contains(t, got.Resources, ResourceAddress("random_integer.suffix"))
assert.Contains(t, got.Resources, ResourceAddress("module.child1.random_pet.pet"))
assert.Contains(t, got.Resources, ResourceAddress("module.child1.random_integer.suffix"))
assert.Contains(t, got.Resources, ResourceAddress("module.child2.random_integer.suffix"))
assert.Contains(t, got.Resources, ResourceAddress("module.child2.module.child3.random_integer.suffix"))
assert.Contains(t, got.Resources, ResourceAddress("module.child2.module.child3.random_pet.pet"))

wantAttrs := map[string]any{
"id": "next-thrush",
"keepers": map[string]any{"now": "2024-05-17T17:23:31Z"},
"length": float64(2),
"prefix": nil,
"separator": "-",
}
assert.Equal(t, wantAttrs, got.Resources["random_pet.pet[3]"].Attributes)

assert.True(t, got.Resources["random_pet.pet[3]"].Tainted)
}
got, err := newState(ws, f)
require.NoError(t, err)

func Test_newState_empty(t *testing.T) {
mod := module.New(internal.NewTestWorkdir(t), module.Options{Path: "a/b/c"})
ws, err := workspace.New(mod, "dev")
require.NoError(t, err)
assert.Len(t, got.Resources, 0)
})

// Mimic empty response from terraform state pull
f := new(bytes.Buffer)
t.Run("state resource with string index key", func(t *testing.T) {
// Mimic response from terraform state pull
f, err := os.Open("./testdata/state_with_for_each.json")
require.NoError(t, err)
t.Cleanup(func() {
f.Close()
})

got, err := newState(ws, f)
require.NoError(t, err)
got, err := newState(ws, f)
require.NoError(t, err)

assert.Len(t, got.Resources, 1)

assert.Contains(t, got.Resources, ResourceAddress(`time_sleep.wait_three_seconds["duration"]`))
wantAttrs := map[string]any{
"id": "2024-07-15T06:12:24Z",
"destroy_duration": nil,
"create_duration": "3s",
"triggers": nil,
}
assert.Equal(t, wantAttrs, got.Resources[`time_sleep.wait_three_seconds["duration"]`].Attributes)
})

assert.Len(t, got.Resources, 0)
}
29 changes: 29 additions & 0 deletions internal/state/testdata/state_with_for_each.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"version": 4,
"terraform_version": "1.8.2",
"serial": 1,
"lineage": "b4352e07-940e-38cb-2a4a-2d8144aa32ed",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "time_sleep",
"name": "wait_three_seconds",
"provider": "provider[\"registry.terraform.io/hashicorp/time\"]",
"instances": [
{
"index_key": "duration",
"schema_version": 0,
"attributes": {
"create_duration": "3s",
"destroy_duration": null,
"id": "2024-07-15T06:12:24Z",
"triggers": null
},
"sensitive_attributes": []
}
]
}
],
"check_results": null
}

0 comments on commit dd0b91f

Please sign in to comment.