query: add identity_version to list_resource_found json output (#37612)

pull/37498/head
Samsondeen 5 months ago committed by GitHub
parent 871451122f
commit 7379832fb8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"path"
"regexp"
"slices"
"strings"
"testing"
@ -333,6 +334,7 @@ func queryFixtureProvider() *testing_provider.MockProvider {
},
Nesting: configschema.NestingSingle,
},
IdentityVersion: 1,
},
"test_database": {
Body: &configschema.Block{
@ -476,7 +478,8 @@ func TestQuery_JSON(t *testing.T) {
"identity": map[string]any{
"id": "test-instance-1",
},
"resource_type": "test_instance",
"identity_version": float64(1),
"resource_type": "test_instance",
"resource_object": map[string]any{
"ami": "ami-12345",
"id": "test-instance-1",
@ -493,7 +496,8 @@ func TestQuery_JSON(t *testing.T) {
"identity": map[string]any{
"id": "test-instance-2",
},
"resource_type": "test_instance",
"identity_version": float64(1),
"resource_type": "test_instance",
"resource_object": map[string]any{
"ami": "ami-67890",
"id": "test-instance-2",
@ -536,7 +540,8 @@ func TestQuery_JSON(t *testing.T) {
"identity": map[string]any{
"id": "test-instance-1",
},
"resource_type": "test_instance",
"identity_version": float64(1),
"resource_type": "test_instance",
"resource_object": map[string]any{
"ami": "ami-12345",
"id": "test-instance-1",
@ -555,7 +560,8 @@ func TestQuery_JSON(t *testing.T) {
"identity": map[string]any{
"id": "test-instance-2",
},
"resource_type": "test_instance",
"identity_version": float64(1),
"resource_type": "test_instance",
"resource_object": map[string]any{
"ami": "ami-67890",
"id": "test-instance-2",
@ -651,7 +657,8 @@ func TestQuery_JSON(t *testing.T) {
"identity": map[string]any{
"id": "test-instance-1",
},
"resource_type": "test_instance",
"identity_version": float64(1),
"resource_type": "test_instance",
"resource_object": map[string]any{
"ami": "ami-12345",
"id": "test-instance-1",
@ -668,7 +675,8 @@ func TestQuery_JSON(t *testing.T) {
"identity": map[string]any{
"id": "test-instance-2",
},
"resource_type": "test_instance",
"identity_version": float64(1),
"resource_type": "test_instance",
"resource_object": map[string]any{
"ami": "ami-67890",
"id": "test-instance-2",
@ -771,3 +779,94 @@ func TestQuery_JSON(t *testing.T) {
})
}
}
func TestQuery_JSON_Raw(t *testing.T) {
tests := []struct {
name string
directory string
expectedOut string
expectedErr []string
initCode int
args []string
}{
{
name: "basic query",
directory: "basic",
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"}
{"@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"}
{"@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"}
{"@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"}
{"@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"}
`,
},
{
name: "empty result",
directory: "empty-result",
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"}
{"@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"}
{"@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"}
{"@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"}
{"@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"}
{"@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"}
{"@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"}
`,
},
}
for _, ts := range tests {
t.Run(ts.name, func(t *testing.T) {
td := t.TempDir()
testCopyDir(t, testFixturePath(path.Join("query", ts.directory)), td)
t.Chdir(td)
providerSource, close := newMockProviderSource(t, map[string][]string{
"hashicorp/test": {"1.0.0"},
})
defer close()
p := queryFixtureProvider()
view, done := testView(t)
meta := Meta{
testingOverrides: metaOverridesForProvider(p),
View: view,
AllowExperimentalFeatures: true,
ProviderSource: providerSource,
}
init := &InitCommand{Meta: meta}
code := init.Run(nil)
output := done(t)
if code != 0 {
t.Fatalf("expected status code %d but got %d: %s", 0, code, output.All())
}
view, done = testView(t)
meta.View = view
c := &QueryCommand{Meta: meta}
args := []string{"-no-color", "-json"}
code = c.Run(args)
output = done(t)
if code != 0 {
t.Logf("query command returned non-zero code '%d' and an error: \n\n%s", code, output.All())
}
// Use regex to normalize timestamps and version numbers for comparison
timestampRegex := regexp.MustCompile(`"@timestamp":"[^"]*"`)
versionRegex := regexp.MustCompile(`"terraform":"[^"]*"`)
actualOutput := output.Stdout()
expectedOutput := ts.expectedOut
// Replace timestamps and version numbers with placeholders
actualNormalized := timestampRegex.ReplaceAllString(actualOutput, `"@timestamp":"TIMESTAMP"`)
actualNormalized = versionRegex.ReplaceAllString(actualNormalized, `"terraform":"VERSION"`)
expectedNormalized := timestampRegex.ReplaceAllString(expectedOutput, `"@timestamp":"TIMESTAMP"`)
expectedNormalized = versionRegex.ReplaceAllString(expectedNormalized, `"terraform":"VERSION"`)
if diff := cmp.Diff(expectedNormalized, actualNormalized); diff != "" {
t.Errorf("expected query output to match, diff: %s", diff)
}
})
}
}

@ -245,7 +245,7 @@ func (h *jsonHook) PreListQuery(id terraform.HookResourceIdentity, input_config
return terraform.HookActionContinue, nil
}
func (h *jsonHook) PostListQuery(id terraform.HookResourceIdentity, results plans.QueryResults) (terraform.HookAction, error) {
func (h *jsonHook) PostListQuery(id terraform.HookResourceIdentity, results plans.QueryResults, identityVersion int64) (terraform.HookAction, error) {
addr := id.Addr
data := results.Value.GetAttr("data")
iter := data.ElementIterator()
@ -257,7 +257,7 @@ func (h *jsonHook) PostListQuery(id terraform.HookResourceIdentity, results plan
generated = &results.Generated.Imports[idx]
}
result := json.NewQueryResult(addr, value, generated)
result := json.NewQueryResult(addr, value, identityVersion, generated)
h.view.log.Info(
fmt.Sprintf("%s: Result found", addr.String()),

@ -511,7 +511,7 @@ func (h *UiHook) PreListQuery(id terraform.HookResourceIdentity, input_config ct
return terraform.HookActionContinue, nil
}
func (h *UiHook) PostListQuery(id terraform.HookResourceIdentity, results plans.QueryResults) (terraform.HookAction, error) {
func (h *UiHook) PostListQuery(id terraform.HookResourceIdentity, results plans.QueryResults, identityVersion int64) (terraform.HookAction, error) {
addr := id.Addr
data := results.Value.GetAttr("data")

@ -19,13 +19,14 @@ type QueryStart struct {
}
type QueryResult struct {
Address string `json:"address"`
DisplayName string `json:"display_name"`
Identity map[string]json.RawMessage `json:"identity"`
ResourceType string `json:"resource_type"`
ResourceObject map[string]json.RawMessage `json:"resource_object,omitempty"`
Config string `json:"config,omitempty"`
ImportConfig string `json:"import_config,omitempty"`
Address string `json:"address"`
DisplayName string `json:"display_name"`
Identity map[string]json.RawMessage `json:"identity"`
IdentityVersion int64 `json:"identity_version"`
ResourceType string `json:"resource_type"`
ResourceObject map[string]json.RawMessage `json:"resource_object,omitempty"`
Config string `json:"config,omitempty"`
ImportConfig string `json:"import_config,omitempty"`
}
type QueryComplete struct {
@ -34,21 +35,22 @@ type QueryComplete struct {
Total int `json:"total"`
}
func NewQueryStart(addr addrs.AbsResourceInstance, input_config cty.Value) QueryStart {
func NewQueryStart(addr addrs.AbsResourceInstance, inputConfig cty.Value) QueryStart {
return QueryStart{
Address: addr.String(),
ResourceType: addr.Resource.Resource.Type,
InputConfig: marshalValues(input_config),
InputConfig: marshalValues(inputConfig),
}
}
func NewQueryResult(listAddr addrs.AbsResourceInstance, value cty.Value, generated *genconfig.ResourceImport) QueryResult {
func NewQueryResult(listAddr addrs.AbsResourceInstance, value cty.Value, identityVersion int64, generated *genconfig.ResourceImport) QueryResult {
result := QueryResult{
Address: listAddr.String(),
DisplayName: value.GetAttr("display_name").AsString(),
Identity: marshalValues(value.GetAttr("identity")),
ResourceType: listAddr.Resource.Resource.Type,
ResourceObject: marshalValues(value.GetAttr("state")),
Address: listAddr.String(),
DisplayName: value.GetAttr("display_name").AsString(),
Identity: marshalValues(value.GetAttr("identity")),
IdentityVersion: identityVersion,
ResourceType: listAddr.Resource.Resource.Type,
ResourceObject: marshalValues(value.GetAttr("state")),
}
if generated != nil {

@ -117,8 +117,8 @@ type Hook interface {
// PreListQuery and PostListQuery are called during a query operation before and after
// resources are queried from the provider.
PreListQuery(id HookResourceIdentity, input_config cty.Value) (HookAction, error)
PostListQuery(id HookResourceIdentity, results plans.QueryResults) (HookAction, error)
PreListQuery(id HookResourceIdentity, inputConfig cty.Value) (HookAction, error)
PostListQuery(id HookResourceIdentity, results plans.QueryResults, identityVersion int64) (HookAction, error)
// StartAction, ProgressAction, and CompleteAction are called during the
// lifecycle of an action invocation.
@ -236,7 +236,7 @@ func (h *NilHook) PreListQuery(id HookResourceIdentity, input_config cty.Value)
return HookActionContinue, nil
}
func (h *NilHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults) (HookAction, error) {
func (h *NilHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults, identityVersion int64) (HookAction, error) {
return HookActionContinue, nil
}

@ -383,7 +383,7 @@ func (h *MockHook) PreListQuery(id HookResourceIdentity, input_config cty.Value)
return h.PreListQueryReturn, h.PreListQueryReturnError
}
func (h *MockHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults) (HookAction, error) {
func (h *MockHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults, identityVersion int64) (HookAction, error) {
h.Lock()
defer h.Unlock()

@ -102,7 +102,7 @@ func (h *stopHook) PreListQuery(id HookResourceIdentity, input_config cty.Value)
return h.hook()
}
func (h *stopHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults) (HookAction, error) {
func (h *stopHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults, identityVersion int64) (HookAction, error) {
return h.hook()
}

@ -176,7 +176,7 @@ func (h *testHook) PreListQuery(id HookResourceIdentity, input_config cty.Value)
return HookActionContinue, nil
}
func (h *testHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults) (HookAction, error) {
func (h *testHook) PostListQuery(id HookResourceIdentity, results plans.QueryResults, identityVersion int64) (HookAction, error) {
h.mu.Lock()
defer h.mu.Unlock()
h.Calls = append(h.Calls, &testHookCall{"PostListQuery", id.Addr.String()})

@ -7,6 +7,7 @@ import (
"fmt"
"log"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/tfdiags"
@ -113,8 +114,10 @@ func (n *NodePlannableResourceInstance) listResourceExecute(ctx EvalContext) (di
}
}
identityVersion := providerSchema.SchemaForResourceType(addrs.ManagedResourceMode, addr.Resource.Resource.Type).IdentityVersion
ctx.Hook(func(h Hook) (HookAction, error) {
return h.PostListQuery(rId, results)
return h.PostListQuery(rId, results, identityVersion)
})
diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config, n.Addr.String()))
if diags.HasErrors() {

Loading…
Cancel
Save