Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,12 @@ func initCommands(
}, nil
},

"query": func() (cli.Command, error) {
return &command.QueryCommand{
Meta: meta,
}, nil
},

"refresh": func() (cli.Command, error) {
return &command.RefreshCommand{
Meta: meta,
Expand Down Expand Up @@ -451,12 +457,6 @@ func initCommands(
}, nil
}

Commands["query"] = func() (cli.Command, error) {
return &command.QueryCommand{
Meta: meta,
}, nil
}

Commands["test cleanup"] = func() (cli.Command, error) {
return &command.TestCleanupCommand{
Meta: meta,
Expand Down
7 changes: 4 additions & 3 deletions internal/cloud/backend_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/hashicorp/terraform/internal/genconfig"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
)

Expand Down Expand Up @@ -276,7 +277,7 @@ func (b *Cloud) cancelQueryRun(cancelCtx context.Context, op *backendrun.Operati
// formatIdentity formats the identity map into a string representation.
// It flattens the map into a string of key=value pairs, separated by commas.
func formatIdentity(identity map[string]json.RawMessage) string {
parts := make([]string, 0, len(identity))
ctyObj := make(map[string]cty.Value, len(identity))
for key, value := range identity {
ty, err := ctyjson.ImpliedType(value)
if err != nil {
Expand All @@ -286,9 +287,9 @@ func formatIdentity(identity map[string]json.RawMessage) string {
if err != nil {
continue
}
parts = append(parts, fmt.Sprintf("%s=%s", key, tfdiags.ValueToString(v)))
ctyObj[key] = v
}
return strings.Join(parts, ",")
return tfdiags.ObjectToString(cty.ObjectVal(ctyObj))
}

const queryDefaultHeader = `
Expand Down
12 changes: 10 additions & 2 deletions internal/cloud/backend_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/cli"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/backend/backendrun"
Expand Down Expand Up @@ -121,8 +122,15 @@ func TestCloud_queryJSONBasic(t *testing.T) {
outp := close(t)
gotOut := outp.Stdout()

if !strings.Contains(gotOut, "list.concept_pet.pets id=complete-gannet,legs=6 This is a complete-gannet") {
t.Fatalf("expected query results in output: %s", gotOut)
expectedOut := `list.concept_pet.pets id=large-roughy,legs=2 This is a large-roughy
list.concept_pet.pets id=able-werewolf,legs=5 This is a able-werewolf
list.concept_pet.pets id=complete-gannet,legs=6 This is a complete-gannet
list.concept_pet.pets id=charming-beagle,legs=3 This is a charming-beagle
list.concept_pet.pets id=legal-lamprey,legs=2 This is a legal-lamprey

`
if diff := cmp.Diff(expectedOut, gotOut); diff != "" {
t.Fatalf("expected query results output to be %s, got %s: diff: %s", expectedOut, gotOut, diff)
}

stateMgr, _ := b.StateMgr(testBackendSingleWorkspaceName)
Expand Down
4 changes: 3 additions & 1 deletion internal/cloud/testdata/query-json-basic/main.tfquery.hcl
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
list "concept_pet" "pets" {}
list "concept_pet" "pets" {
provider = concept
}
4 changes: 3 additions & 1 deletion internal/cloud/testdata/query/main.tfquery.hcl
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
list "null_resource" "foo" {}
list "null_resource" "foo" {
provider = null
}
30 changes: 30 additions & 0 deletions internal/command/e2etest/providers_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,21 @@ func TestProvidersSchema(t *testing.T) {
}
}
},
"list_resource_schemas": {
"simple_resource": {
"version": 0,
"block": {
"attributes": {
"value": {
"type": "string",
"description_kind": "plain",
"optional": true
}
},
"description_kind": "plain"
}
}
},
"resource_identity_schemas": {
"simple_resource": {
"version": 0,
Expand Down Expand Up @@ -213,6 +228,21 @@ func TestProvidersSchema(t *testing.T) {
}
}
},
"list_resource_schemas": {
"simple_resource": {
"version": 0,
"block": {
"attributes": {
"value": {
"type": "string",
"description_kind": "plain",
"optional": true
}
},
"description_kind": "plain"
}
}
},
"functions": {
"noop": {
"description": "noop takes any single argument and returns the same value",
Expand Down
6 changes: 3 additions & 3 deletions internal/command/jsonformat/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8071,7 +8071,7 @@ func runTestCases(t *testing.T, testCases map[string]testCase) {
return
}

jsonschemas := jsonprovider.MarshalForRenderer(tfschemas, false)
jsonschemas := jsonprovider.MarshalForRenderer(tfschemas)
change := structured.FromJsonChange(jsonchanges[0].Change, attribute_path.AlwaysMatcher())
renderer := Renderer{Colorize: color}
diff := diff{
Expand Down Expand Up @@ -8421,7 +8421,7 @@ func TestResourceChange_deferredActions(t *testing.T) {
}

renderer := Renderer{Colorize: color}
jsonschemas := jsonprovider.MarshalForRenderer(fullSchema, false)
jsonschemas := jsonprovider.MarshalForRenderer(fullSchema)
diffs := precomputeDiffs(Plan{
DeferredChanges: deferredChanges,
ProviderSchemas: jsonschemas,
Expand Down Expand Up @@ -8714,7 +8714,7 @@ func TestResourceChange_actions(t *testing.T) {
},
},
}
jsonschemas := jsonprovider.MarshalForRenderer(fullSchema, false)
jsonschemas := jsonprovider.MarshalForRenderer(fullSchema)
diffs := precomputeDiffs(Plan{
ResourceChanges: []jsonplan.ResourceChange{defaultResourceChange},
ActionInvocations: tc.actionInvocations,
Expand Down
2 changes: 1 addition & 1 deletion internal/command/jsonformat/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func TestState(t *testing.T) {
RootModule: root,
RootModuleOutputs: outputs,
ProviderFormatVersion: jsonprovider.FormatVersion,
ProviderSchemas: jsonprovider.MarshalForRenderer(tt.Schemas, false),
ProviderSchemas: jsonprovider.MarshalForRenderer(tt.Schemas),
})

result := done(t).All()
Expand Down
32 changes: 15 additions & 17 deletions internal/command/jsonprovider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,22 @@ func newProviders() *Providers {
// schema into the public structured JSON versions.
//
// This is a format that can be read by the structured plan renderer.
func MarshalForRenderer(s *terraform.Schemas, includeExperimentalSchemas bool) map[string]*Provider {
func MarshalForRenderer(s *terraform.Schemas) map[string]*Provider {
schemas := make(map[string]*Provider, len(s.Providers))
for k, v := range s.Providers {
schemas[k.String()] = marshalProvider(v, includeExperimentalSchemas)
schemas[k.String()] = marshalProvider(v)
}
return schemas
}

func Marshal(s *terraform.Schemas, includeExperimentalSchemas bool) ([]byte, error) {
func Marshal(s *terraform.Schemas) ([]byte, error) {
providers := newProviders()
providers.Schemas = MarshalForRenderer(s, includeExperimentalSchemas)
providers.Schemas = MarshalForRenderer(s)
ret, err := json.Marshal(providers)
return ret, err
}

func marshalProvider(tps providers.ProviderSchema, includeExperimentalSchemas bool) *Provider {
func marshalProvider(tps providers.ProviderSchema) *Provider {
p := &Provider{
Provider: marshalSchema(tps.Provider),
ResourceSchemas: marshalSchemas(tps.ResourceTypes),
Expand All @@ -71,20 +71,18 @@ func marshalProvider(tps providers.ProviderSchema, includeExperimentalSchemas bo
ActionSchemas: marshalActionSchemas(tps.Actions),
}

if includeExperimentalSchemas {
// List resource schemas are nested under a "config" block, so we need to
// extract that block to get the actual provider schema for the list resource.
// When getting the provider schemas, Terraform adds this extra level to
// better match the actual configuration structure.
listSchemas := make(map[string]providers.Schema, len(tps.ListResourceTypes))
for k, v := range tps.ListResourceTypes {
listSchemas[k] = providers.Schema{
Body: &v.Body.BlockTypes["config"].Block,
Version: v.Version,
}
// List resource schemas are nested under a "config" block, so we need to
// extract that block to get the actual provider schema for the list resource.
// When getting the provider schemas, Terraform adds this extra level to
// better match the actual configuration structure.
listSchemas := make(map[string]providers.Schema, len(tps.ListResourceTypes))
for k, v := range tps.ListResourceTypes {
listSchemas[k] = providers.Schema{
Body: &v.Body.BlockTypes["config"].Block,
Version: v.Version,
}
p.ListResourceSchemas = marshalSchemas(listSchemas)
}
p.ListResourceSchemas = marshalSchemas(listSchemas)

return p
}
67 changes: 14 additions & 53 deletions internal/command/jsonprovider/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,23 @@ var cmpOpts = cmpopts.IgnoreUnexported(Provider{})

func TestMarshalProvider(t *testing.T) {
tests := []struct {
Input providers.ProviderSchema
IncludeExperimental bool
Want *Provider
Input providers.ProviderSchema
Want *Provider
}{
{
providers.ProviderSchema{},
false,
&Provider{
Provider: &Schema{},
ResourceSchemas: map[string]*Schema{},
DataSourceSchemas: map[string]*Schema{},
EphemeralResourceSchemas: map[string]*Schema{},
ResourceIdentitySchemas: map[string]*IdentitySchema{},
ListResourceSchemas: map[string]*Schema{},
ActionSchemas: map[string]*ActionSchema{},
},
},
{
testProvider(),
false,
&Provider{
Provider: &Schema{
Block: &Block{
Expand Down Expand Up @@ -212,53 +210,6 @@ func TestMarshalProvider(t *testing.T) {
},
},
},
ResourceIdentitySchemas: map[string]*IdentitySchema{},
ActionSchemas: map[string]*ActionSchema{},
},
},
{
providers.ProviderSchema{
ListResourceTypes: map[string]providers.Schema{
"test_list_resource": {
Version: 1,
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"data": {
Type: cty.DynamicPseudoType,
Computed: true,
},
},
BlockTypes: map[string]*configschema.NestedBlock{
"config": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"filter": {Type: cty.String, Optional: true},
"items": {Type: cty.List(cty.String), Required: true},
},
},
Nesting: configschema.NestingSingle,
},
},
},
},
},
Actions: map[string]providers.ActionSchema{
"test_action": {
ConfigSchema: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"opt_attr": {Type: cty.String, Optional: true},
"req_attr": {Type: cty.List(cty.String), Required: true},
},
},
},
},
},
true,
&Provider{
Provider: &Schema{},
ResourceSchemas: map[string]*Schema{},
DataSourceSchemas: map[string]*Schema{},
EphemeralResourceSchemas: map[string]*Schema{},
ListResourceSchemas: map[string]*Schema{
"test_list_resource": {
Version: 1,
Expand Down Expand Up @@ -305,7 +256,7 @@ func TestMarshalProvider(t *testing.T) {

for i, test := range tests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
got := marshalProvider(test.Input, test.IncludeExperimental)
got := marshalProvider(test.Input)
if diff := cmp.Diff(test.Want, got, cmpOpts); diff != "" {
t.Fatalf("wrong result:\n %s\n", diff)
}
Expand Down Expand Up @@ -431,5 +382,15 @@ func testProvider() providers.ProviderSchema {
},
},
},
Actions: map[string]providers.ActionSchema{
"test_action": {
ConfigSchema: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"opt_attr": {Type: cty.String, Optional: true},
"req_attr": {Type: cty.List(cty.String), Required: true},
},
},
},
},
}
}
3 changes: 3 additions & 0 deletions internal/command/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,9 @@ type Meta struct {
// Used with commands which write state to allow users to write remote
// state even if the remote and local Terraform versions don't match.
ignoreRemoteVersion bool

// set to true if query files should be parsed
includeQueryFiles bool
}

type testingOverrides struct {
Expand Down
9 changes: 5 additions & 4 deletions internal/command/meta_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (m *Meta) normalizePath(path string) string {
// loadConfig reads a configuration from the given directory, which should
// contain a root module and have already have any required descendant modules
// installed.
func (m *Meta) loadConfig(rootDir string, parserOpts ...configs.Option) (*configs.Config, tfdiags.Diagnostics) {
func (m *Meta) loadConfig(rootDir string) (*configs.Config, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
rootDir = m.normalizePath(rootDir)

Expand All @@ -48,7 +48,7 @@ func (m *Meta) loadConfig(rootDir string, parserOpts ...configs.Option) (*config
return nil, diags
}

config, hclDiags := loader.LoadConfig(rootDir, parserOpts...)
config, hclDiags := loader.LoadConfig(rootDir)
diags = diags.Append(hclDiags)
return config, diags
}
Expand Down Expand Up @@ -353,8 +353,9 @@ func (m *Meta) registerSynthConfigSource(filename string, src []byte) {
func (m *Meta) initConfigLoader() (*configload.Loader, error) {
if m.configLoader == nil {
loader, err := configload.NewLoader(&configload.Config{
ModulesDir: m.modulesDir(),
Services: m.Services,
ModulesDir: m.modulesDir(),
Services: m.Services,
IncludeQueryFiles: m.includeQueryFiles,
})
if err != nil {
return nil, err
Expand Down
Loading