@@ -51,6 +51,7 @@ type TFState struct {
5151 Attrs TFStateAttrs
5252 // IDs maps bundle resource key → {ID, ETag} (same as terraform.ParseResourcesState).
5353 IDs terraform.ExportedResourcesMap
54+
5455 // Lineage and Serial are the top-level state metadata used to seed the direct state.
5556 Lineage string
5657 Serial int
@@ -67,23 +68,49 @@ func ParseTFStateFull(ctx context.Context, path string) (*TFState, error) {
6768 return nil , err
6869 }
6970
70- var meta struct {
71- Lineage string `json:"lineage"`
72- Serial int `json:"serial"`
73- }
74- if err := json .Unmarshal (raw , & meta ); err != nil {
71+ // Parse once: lineage/serial live at the top level alongside the resources array,
72+ // so a single unmarshal captures everything needed for both attrs and IDs.
73+ var parsed rawTFState
74+ if err := json .Unmarshal (raw , & parsed ); err != nil {
7575 return nil , err
7676 }
7777
78- attrs , err := parseTFStateAttrsFromBytes (raw )
79- if err != nil {
80- return nil , err
81- }
78+ attrs := parseTFStateAttrsFromRaw (& parsed )
79+
8280 ids , err := terraform .ParseResourcesStateFromBytes (ctx , raw )
8381 if err != nil {
8482 return nil , err
8583 }
86- return & TFState {Attrs : attrs , IDs : ids , Lineage : meta .Lineage , Serial : meta .Serial }, nil
84+ return & TFState {Attrs : attrs , IDs : ids , Lineage : parsed .Lineage , Serial : parsed .Serial }, nil
85+ }
86+
87+ // rawTFState is the on-disk terraform state format; it captures everything we need in one parse.
88+ type rawTFState struct {
89+ Version int `json:"version"`
90+ Lineage string `json:"lineage"`
91+ Serial int `json:"serial"`
92+ Resources []struct {
93+ Type string `json:"type"`
94+ Name string `json:"name"`
95+ Mode tfjson.ResourceMode `json:"mode"`
96+ Instances []struct {
97+ Attributes json.RawMessage `json:"attributes"`
98+ } `json:"instances"`
99+ } `json:"resources"`
100+ }
101+
102+ func parseTFStateAttrsFromRaw (s * rawTFState ) TFStateAttrs {
103+ result := make (TFStateAttrs )
104+ for _ , r := range s .Resources {
105+ if r .Mode != tfjson .ManagedResourceMode || len (r .Instances ) == 0 {
106+ continue
107+ }
108+ if result [r .Type ] == nil {
109+ result [r .Type ] = make (map [string ]json.RawMessage )
110+ }
111+ result [r.Type ][r.Name ] = r .Instances [0 ].Attributes
112+ }
113+ return result
87114}
88115
89116// ParseTFStateAttrs parses the terraform state file returning full attribute JSON per resource.
@@ -98,31 +125,11 @@ func ParseTFStateAttrs(path string) (TFStateAttrs, error) {
98125}
99126
100127func parseTFStateAttrsFromBytes (raw []byte ) (TFStateAttrs , error ) {
101- var state struct {
102- Version int `json:"version"`
103- Resources []struct {
104- Type string `json:"type"`
105- Name string `json:"name"`
106- Mode tfjson.ResourceMode `json:"mode"`
107- Instances []struct {
108- Attributes json.RawMessage `json:"attributes"`
109- } `json:"instances"`
110- } `json:"resources"`
111- }
112- if err := json .Unmarshal (raw , & state ); err != nil {
128+ var s rawTFState
129+ if err := json .Unmarshal (raw , & s ); err != nil {
113130 return nil , err
114131 }
115- result := make (TFStateAttrs )
116- for _ , r := range state .Resources {
117- if r .Mode != tfjson .ManagedResourceMode || len (r .Instances ) == 0 {
118- continue
119- }
120- if result [r .Type ] == nil {
121- result [r .Type ] = make (map [string ]json.RawMessage )
122- }
123- result [r.Type ][r.Name ] = r .Instances [0 ].Attributes
124- }
125- return result , nil
132+ return parseTFStateAttrsFromRaw (& s ), nil
126133}
127134
128135// LookupTFField looks up a field from TF state attributes for a bundle resource.
0 commit comments