Skip to content

Commit 7379832

Browse files
authored
query: add identity_version to list_resource_found json output (#37612)
1 parent 8714511 commit 7379832

File tree

9 files changed

+135
-31
lines changed

9 files changed

+135
-31
lines changed

internal/command/query_test.go

Lines changed: 105 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/json"
88
"fmt"
99
"path"
10+
"regexp"
1011
"slices"
1112
"strings"
1213
"testing"
@@ -333,6 +334,7 @@ func queryFixtureProvider() *testing_provider.MockProvider {
333334
},
334335
Nesting: configschema.NestingSingle,
335336
},
337+
IdentityVersion: 1,
336338
},
337339
"test_database": {
338340
Body: &configschema.Block{
@@ -476,7 +478,8 @@ func TestQuery_JSON(t *testing.T) {
476478
"identity": map[string]any{
477479
"id": "test-instance-1",
478480
},
479-
"resource_type": "test_instance",
481+
"identity_version": float64(1),
482+
"resource_type": "test_instance",
480483
"resource_object": map[string]any{
481484
"ami": "ami-12345",
482485
"id": "test-instance-1",
@@ -493,7 +496,8 @@ func TestQuery_JSON(t *testing.T) {
493496
"identity": map[string]any{
494497
"id": "test-instance-2",
495498
},
496-
"resource_type": "test_instance",
499+
"identity_version": float64(1),
500+
"resource_type": "test_instance",
497501
"resource_object": map[string]any{
498502
"ami": "ami-67890",
499503
"id": "test-instance-2",
@@ -536,7 +540,8 @@ func TestQuery_JSON(t *testing.T) {
536540
"identity": map[string]any{
537541
"id": "test-instance-1",
538542
},
539-
"resource_type": "test_instance",
543+
"identity_version": float64(1),
544+
"resource_type": "test_instance",
540545
"resource_object": map[string]any{
541546
"ami": "ami-12345",
542547
"id": "test-instance-1",
@@ -555,7 +560,8 @@ func TestQuery_JSON(t *testing.T) {
555560
"identity": map[string]any{
556561
"id": "test-instance-2",
557562
},
558-
"resource_type": "test_instance",
563+
"identity_version": float64(1),
564+
"resource_type": "test_instance",
559565
"resource_object": map[string]any{
560566
"ami": "ami-67890",
561567
"id": "test-instance-2",
@@ -651,7 +657,8 @@ func TestQuery_JSON(t *testing.T) {
651657
"identity": map[string]any{
652658
"id": "test-instance-1",
653659
},
654-
"resource_type": "test_instance",
660+
"identity_version": float64(1),
661+
"resource_type": "test_instance",
655662
"resource_object": map[string]any{
656663
"ami": "ami-12345",
657664
"id": "test-instance-1",
@@ -668,7 +675,8 @@ func TestQuery_JSON(t *testing.T) {
668675
"identity": map[string]any{
669676
"id": "test-instance-2",
670677
},
671-
"resource_type": "test_instance",
678+
"identity_version": float64(1),
679+
"resource_type": "test_instance",
672680
"resource_object": map[string]any{
673681
"ami": "ami-67890",
674682
"id": "test-instance-2",
@@ -771,3 +779,94 @@ func TestQuery_JSON(t *testing.T) {
771779
})
772780
}
773781
}
782+
783+
func TestQuery_JSON_Raw(t *testing.T) {
784+
785+
tests := []struct {
786+
name string
787+
directory string
788+
expectedOut string
789+
expectedErr []string
790+
initCode int
791+
args []string
792+
}{
793+
{
794+
name: "basic query",
795+
directory: "basic",
796+
expectedOut: `{"@level":"info","@message":"Terraform 1.14.0-dev","@module":"terraform.ui","@timestamp":"2025-09-12T16:52:57.596469+02:00","terraform":"1.14.0-dev","type":"version","ui":"1.2"}
797+
{"@level":"info","@message":"list.test_instance.example: Starting query...","@module":"terraform.ui","@timestamp":"2025-09-12T16:52:57.600609+02:00","list_start":{"address":"list.test_instance.example","resource_type":"test_instance","input_config":{"ami":"ami-12345","foo":null}},"type":"list_start"}
798+
{"@level":"info","@message":"list.test_instance.example: Result found","@module":"terraform.ui","@timestamp":"2025-09-12T16:52:57.600729+02:00","list_resource_found":{"address":"list.test_instance.example","display_name":"Test Instance 1","identity":{"id":"test-instance-1"},"identity_version":1,"resource_type":"test_instance","resource_object":{"ami":"ami-12345","id":"test-instance-1"}},"type":"list_resource_found"}
799+
{"@level":"info","@message":"list.test_instance.example: Result found","@module":"terraform.ui","@timestamp":"2025-09-12T16:52:57.600759+02:00","list_resource_found":{"address":"list.test_instance.example","display_name":"Test Instance 2","identity":{"id":"test-instance-2"},"identity_version":1,"resource_type":"test_instance","resource_object":{"ami":"ami-67890","id":"test-instance-2"}},"type":"list_resource_found"}
800+
{"@level":"info","@message":"list.test_instance.example: List complete","@module":"terraform.ui","@timestamp":"2025-09-12T16:52:57.600770+02:00","list_complete":{"address":"list.test_instance.example","resource_type":"test_instance","total":2},"type":"list_complete"}
801+
`,
802+
},
803+
{
804+
name: "empty result",
805+
directory: "empty-result",
806+
expectedOut: `{"@level":"info","@message":"Terraform 1.14.0-dev","@module":"terraform.ui","@timestamp":"2025-09-12T16:52:57.596469+02:00","terraform":"1.14.0-dev","type":"version","ui":"1.2"}
807+
{"@level":"info","@message":"list.test_instance.example: Starting query...","@module":"terraform.ui","@timestamp":"2025-09-12T16:52:57.600609+02:00","list_start":{"address":"list.test_instance.example","resource_type":"test_instance","input_config":{"ami":"ami-12345","foo":null}},"type":"list_start"}
808+
{"@level":"info","@message":"list.test_instance.example: Result found","@module":"terraform.ui","@timestamp":"2025-09-12T16:52:57.600729+02:00","list_resource_found":{"address":"list.test_instance.example","display_name":"Test Instance 1","identity":{"id":"test-instance-1"},"identity_version":1,"resource_type":"test_instance","resource_object":{"ami":"ami-12345","id":"test-instance-1"}},"type":"list_resource_found"}
809+
{"@level":"info","@message":"list.test_instance.example: Result found","@module":"terraform.ui","@timestamp":"2025-09-12T16:52:57.600759+02:00","list_resource_found":{"address":"list.test_instance.example","display_name":"Test Instance 2","identity":{"id":"test-instance-2"},"identity_version":1,"resource_type":"test_instance","resource_object":{"ami":"ami-67890","id":"test-instance-2"}},"type":"list_resource_found"}
810+
{"@level":"info","@message":"list.test_instance.example: List complete","@module":"terraform.ui","@timestamp":"2025-09-12T16:52:57.600770+02:00","list_complete":{"address":"list.test_instance.example","resource_type":"test_instance","total":2},"type":"list_complete"}
811+
{"@level":"info","@message":"list.test_instance.example2: Starting query...","@module":"terraform.ui","@timestamp":"2025-09-12T16:52:57.600609+02:00","list_start":{"address":"list.test_instance.example2","resource_type":"test_instance","input_config":{"ami":"ami-nonexistent","foo":"test-instance-1"}},"type":"list_start"}
812+
{"@level":"info","@message":"list.test_instance.example2: List complete","@module":"terraform.ui","@timestamp":"2025-09-12T16:52:57.600770+02:00","list_complete":{"address":"list.test_instance.example2","resource_type":"test_instance","total":0},"type":"list_complete"}
813+
`,
814+
},
815+
}
816+
817+
for _, ts := range tests {
818+
t.Run(ts.name, func(t *testing.T) {
819+
td := t.TempDir()
820+
testCopyDir(t, testFixturePath(path.Join("query", ts.directory)), td)
821+
t.Chdir(td)
822+
providerSource, close := newMockProviderSource(t, map[string][]string{
823+
"hashicorp/test": {"1.0.0"},
824+
})
825+
defer close()
826+
827+
p := queryFixtureProvider()
828+
view, done := testView(t)
829+
meta := Meta{
830+
testingOverrides: metaOverridesForProvider(p),
831+
View: view,
832+
AllowExperimentalFeatures: true,
833+
ProviderSource: providerSource,
834+
}
835+
836+
init := &InitCommand{Meta: meta}
837+
code := init.Run(nil)
838+
output := done(t)
839+
if code != 0 {
840+
t.Fatalf("expected status code %d but got %d: %s", 0, code, output.All())
841+
}
842+
843+
view, done = testView(t)
844+
meta.View = view
845+
846+
c := &QueryCommand{Meta: meta}
847+
args := []string{"-no-color", "-json"}
848+
code = c.Run(args)
849+
output = done(t)
850+
if code != 0 {
851+
t.Logf("query command returned non-zero code '%d' and an error: \n\n%s", code, output.All())
852+
}
853+
854+
// Use regex to normalize timestamps and version numbers for comparison
855+
timestampRegex := regexp.MustCompile(`"@timestamp":"[^"]*"`)
856+
versionRegex := regexp.MustCompile(`"terraform":"[^"]*"`)
857+
858+
actualOutput := output.Stdout()
859+
expectedOutput := ts.expectedOut
860+
861+
// Replace timestamps and version numbers with placeholders
862+
actualNormalized := timestampRegex.ReplaceAllString(actualOutput, `"@timestamp":"TIMESTAMP"`)
863+
actualNormalized = versionRegex.ReplaceAllString(actualNormalized, `"terraform":"VERSION"`)
864+
865+
expectedNormalized := timestampRegex.ReplaceAllString(expectedOutput, `"@timestamp":"TIMESTAMP"`)
866+
expectedNormalized = versionRegex.ReplaceAllString(expectedNormalized, `"terraform":"VERSION"`)
867+
if diff := cmp.Diff(expectedNormalized, actualNormalized); diff != "" {
868+
t.Errorf("expected query output to match, diff: %s", diff)
869+
}
870+
})
871+
}
872+
}

internal/command/views/hook_json.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ func (h *jsonHook) PreListQuery(id terraform.HookResourceIdentity, input_config
245245
return terraform.HookActionContinue, nil
246246
}
247247

248-
func (h *jsonHook) PostListQuery(id terraform.HookResourceIdentity, results plans.QueryResults) (terraform.HookAction, error) {
248+
func (h *jsonHook) PostListQuery(id terraform.HookResourceIdentity, results plans.QueryResults, identityVersion int64) (terraform.HookAction, error) {
249249
addr := id.Addr
250250
data := results.Value.GetAttr("data")
251251
iter := data.ElementIterator()
@@ -257,7 +257,7 @@ func (h *jsonHook) PostListQuery(id terraform.HookResourceIdentity, results plan
257257
generated = &results.Generated.Imports[idx]
258258
}
259259

260-
result := json.NewQueryResult(addr, value, generated)
260+
result := json.NewQueryResult(addr, value, identityVersion, generated)
261261

262262
h.view.log.Info(
263263
fmt.Sprintf("%s: Result found", addr.String()),

internal/command/views/hook_ui.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ func (h *UiHook) PreListQuery(id terraform.HookResourceIdentity, input_config ct
511511
return terraform.HookActionContinue, nil
512512
}
513513

514-
func (h *UiHook) PostListQuery(id terraform.HookResourceIdentity, results plans.QueryResults) (terraform.HookAction, error) {
514+
func (h *UiHook) PostListQuery(id terraform.HookResourceIdentity, results plans.QueryResults, identityVersion int64) (terraform.HookAction, error) {
515515
addr := id.Addr
516516
data := results.Value.GetAttr("data")
517517

internal/command/views/json/query.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ type QueryStart struct {
1919
}
2020

2121
type QueryResult struct {
22-
Address string `json:"address"`
23-
DisplayName string `json:"display_name"`
24-
Identity map[string]json.RawMessage `json:"identity"`
25-
ResourceType string `json:"resource_type"`
26-
ResourceObject map[string]json.RawMessage `json:"resource_object,omitempty"`
27-
Config string `json:"config,omitempty"`
28-
ImportConfig string `json:"import_config,omitempty"`
22+
Address string `json:"address"`
23+
DisplayName string `json:"display_name"`
24+
Identity map[string]json.RawMessage `json:"identity"`
25+
IdentityVersion int64 `json:"identity_version"`
26+
ResourceType string `json:"resource_type"`
27+
ResourceObject map[string]json.RawMessage `json:"resource_object,omitempty"`
28+
Config string `json:"config,omitempty"`
29+
ImportConfig string `json:"import_config,omitempty"`
2930
}
3031

3132
type QueryComplete struct {
@@ -34,21 +35,22 @@ type QueryComplete struct {
3435
Total int `json:"total"`
3536
}
3637

37-
func NewQueryStart(addr addrs.AbsResourceInstance, input_config cty.Value) QueryStart {
38+
func NewQueryStart(addr addrs.AbsResourceInstance, inputConfig cty.Value) QueryStart {
3839
return QueryStart{
3940
Address: addr.String(),
4041
ResourceType: addr.Resource.Resource.Type,
41-
InputConfig: marshalValues(input_config),
42+
InputConfig: marshalValues(inputConfig),
4243
}
4344
}
4445

45-
func NewQueryResult(listAddr addrs.AbsResourceInstance, value cty.Value, generated *genconfig.ResourceImport) QueryResult {
46+
func NewQueryResult(listAddr addrs.AbsResourceInstance, value cty.Value, identityVersion int64, generated *genconfig.ResourceImport) QueryResult {
4647
result := QueryResult{
47-
Address: listAddr.String(),
48-
DisplayName: value.GetAttr("display_name").AsString(),
49-
Identity: marshalValues(value.GetAttr("identity")),
50-
ResourceType: listAddr.Resource.Resource.Type,
51-
ResourceObject: marshalValues(value.GetAttr("state")),
48+
Address: listAddr.String(),
49+
DisplayName: value.GetAttr("display_name").AsString(),
50+
Identity: marshalValues(value.GetAttr("identity")),
51+
IdentityVersion: identityVersion,
52+
ResourceType: listAddr.Resource.Resource.Type,
53+
ResourceObject: marshalValues(value.GetAttr("state")),
5254
}
5355

5456
if generated != nil {

internal/terraform/hook.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ type Hook interface {
117117

118118
// PreListQuery and PostListQuery are called during a query operation before and after
119119
// resources are queried from the provider.
120-
PreListQuery(id HookResourceIdentity, input_config cty.Value) (HookAction, error)
121-
PostListQuery(id HookResourceIdentity, results plans.QueryResults) (HookAction, error)
120+
PreListQuery(id HookResourceIdentity, inputConfig cty.Value) (HookAction, error)
121+
PostListQuery(id HookResourceIdentity, results plans.QueryResults, identityVersion int64) (HookAction, error)
122122

123123
// StartAction, ProgressAction, and CompleteAction are called during the
124124
// lifecycle of an action invocation.
@@ -236,7 +236,7 @@ func (h *NilHook) PreListQuery(id HookResourceIdentity, input_config cty.Value)
236236
return HookActionContinue, nil
237237
}
238238

239-
func (h *NilHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults) (HookAction, error) {
239+
func (h *NilHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults, identityVersion int64) (HookAction, error) {
240240
return HookActionContinue, nil
241241
}
242242

internal/terraform/hook_mock.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ func (h *MockHook) PreListQuery(id HookResourceIdentity, input_config cty.Value)
383383
return h.PreListQueryReturn, h.PreListQueryReturnError
384384
}
385385

386-
func (h *MockHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults) (HookAction, error) {
386+
func (h *MockHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults, identityVersion int64) (HookAction, error) {
387387
h.Lock()
388388
defer h.Unlock()
389389

internal/terraform/hook_stop.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func (h *stopHook) PreListQuery(id HookResourceIdentity, input_config cty.Value)
102102
return h.hook()
103103
}
104104

105-
func (h *stopHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults) (HookAction, error) {
105+
func (h *stopHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults, identityVersion int64) (HookAction, error) {
106106
return h.hook()
107107
}
108108

internal/terraform/hook_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ func (h *testHook) PreListQuery(id HookResourceIdentity, input_config cty.Value)
176176
return HookActionContinue, nil
177177
}
178178

179-
func (h *testHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults) (HookAction, error) {
179+
func (h *testHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults, identityVersion int64) (HookAction, error) {
180180
h.mu.Lock()
181181
defer h.mu.Unlock()
182182
h.Calls = append(h.Calls, &testHookCall{"PostListQuery", id.Addr.String()})

internal/terraform/node_resource_plan_instance_query.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"log"
99

10+
"github.com/hashicorp/terraform/internal/addrs"
1011
"github.com/hashicorp/terraform/internal/plans"
1112
"github.com/hashicorp/terraform/internal/providers"
1213
"github.com/hashicorp/terraform/internal/tfdiags"
@@ -113,8 +114,10 @@ func (n *NodePlannableResourceInstance) listResourceExecute(ctx EvalContext) (di
113114
}
114115
}
115116

117+
identityVersion := providerSchema.SchemaForResourceType(addrs.ManagedResourceMode, addr.Resource.Resource.Type).IdentityVersion
118+
116119
ctx.Hook(func(h Hook) (HookAction, error) {
117-
return h.PostListQuery(rId, results)
120+
return h.PostListQuery(rId, results, identityVersion)
118121
})
119122
diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config, n.Addr.String()))
120123
if diags.HasErrors() {

0 commit comments

Comments
 (0)