mirror of
https://github.com/hashicorp/terraform.git
synced 2026-04-22 14:51:50 -04:00
299 lines
7.7 KiB
Go
299 lines
7.7 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package command
|
|
|
|
import (
|
|
"path"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
"github.com/hashicorp/terraform/internal/providers"
|
|
testing_provider "github.com/hashicorp/terraform/internal/providers/testing"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
func TestQuery(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
directory string
|
|
expectedOut string
|
|
expectedErr []string
|
|
initCode int
|
|
}{
|
|
{
|
|
name: "basic query",
|
|
directory: "basic",
|
|
expectedOut: `list.test_instance.example id=test-instance-1 Test Instance 1
|
|
list.test_instance.example id=test-instance-2 Test Instance 2
|
|
|
|
`,
|
|
},
|
|
{
|
|
name: "query referencing local variable",
|
|
directory: "with-locals",
|
|
expectedOut: `list.test_instance.example id=test-instance-1 Test Instance 1
|
|
list.test_instance.example id=test-instance-2 Test Instance 2
|
|
|
|
`,
|
|
},
|
|
{
|
|
name: "config with no query block",
|
|
directory: "no-list-block",
|
|
expectedOut: "",
|
|
expectedErr: []string{`
|
|
Error: No resources to query
|
|
|
|
The configuration does not contain any resources that can be queried.
|
|
`},
|
|
},
|
|
{
|
|
name: "missing query file",
|
|
directory: "missing-query-file",
|
|
expectedOut: "",
|
|
expectedErr: []string{`
|
|
Error: No resources to query
|
|
|
|
The configuration does not contain any resources that can be queried.
|
|
`},
|
|
},
|
|
{
|
|
name: "missing configuration",
|
|
directory: "missing-configuration",
|
|
expectedOut: "",
|
|
expectedErr: []string{`
|
|
Error: No configuration files
|
|
|
|
Query requires a query configuration to be present. Create a Terraform query
|
|
configuration file (.tfquery.hcl file) and try again.
|
|
`},
|
|
},
|
|
{
|
|
name: "invalid query syntax",
|
|
directory: "invalid-syntax",
|
|
expectedOut: "",
|
|
initCode: 1,
|
|
expectedErr: []string{`
|
|
Error: Unsupported block type
|
|
|
|
on query.tfquery.hcl line 11:
|
|
11: resource "test_instance" "example" {
|
|
|
|
Blocks of type "resource" are not expected here.
|
|
`},
|
|
},
|
|
}
|
|
|
|
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 != ts.initCode {
|
|
t.Fatalf("expected status code %d but got %d: %s", ts.initCode, code, output.All())
|
|
}
|
|
|
|
view, done = testView(t)
|
|
meta.View = view
|
|
|
|
c := &QueryCommand{Meta: meta}
|
|
args := []string{"-no-color"}
|
|
code = c.Run(args)
|
|
output = done(t)
|
|
actual := output.All()
|
|
if len(ts.expectedErr) == 0 {
|
|
if code != 0 {
|
|
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
|
}
|
|
|
|
// Check that we have query output
|
|
if diff := cmp.Diff(ts.expectedOut, actual); diff != "" {
|
|
t.Errorf("expected query output to contain %q, \ngot: %q, \ndiff: %s", ts.expectedOut, actual, diff)
|
|
}
|
|
|
|
} else {
|
|
for _, expected := range ts.expectedErr {
|
|
if diff := cmp.Diff(expected, actual); diff != "" {
|
|
t.Errorf("expected error message to contain '%s', \ngot: %s, \ndiff: %s", expected, actual, diff)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func queryFixtureProvider() *testing_provider.MockProvider {
|
|
p := testProvider()
|
|
instanceListSchema := &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{
|
|
"ami": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
Nesting: configschema.NestingSingle,
|
|
},
|
|
},
|
|
}
|
|
databaseListSchema := &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{
|
|
"engine": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
Nesting: configschema.NestingSingle,
|
|
},
|
|
},
|
|
}
|
|
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
|
ResourceTypes: map[string]providers.Schema{
|
|
"test_instance": {
|
|
Body: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"ami": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"test_database": {
|
|
Body: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"engine": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ListResourceTypes: map[string]providers.Schema{
|
|
"test_instance": {Body: instanceListSchema},
|
|
"test_database": {Body: databaseListSchema},
|
|
},
|
|
}
|
|
|
|
// Mock the ListResources method for query operations
|
|
p.ListResourceFn = func(request providers.ListResourceRequest) providers.ListResourceResponse {
|
|
// Check the config to determine what kind of response to return
|
|
wholeConfigMap := request.Config.AsValueMap()
|
|
|
|
configMap := wholeConfigMap["config"]
|
|
|
|
// For empty results test case //TODO: Remove?
|
|
if ami, ok := wholeConfigMap["ami"]; ok && ami.AsString() == "ami-nonexistent" {
|
|
return providers.ListResourceResponse{
|
|
Result: cty.ObjectVal(map[string]cty.Value{
|
|
"data": cty.ListVal([]cty.Value{}),
|
|
"config": configMap,
|
|
}),
|
|
}
|
|
}
|
|
|
|
switch request.TypeName {
|
|
case "test_instance":
|
|
return providers.ListResourceResponse{
|
|
Result: cty.ObjectVal(map[string]cty.Value{
|
|
"data": cty.ListVal([]cty.Value{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"identity": cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("test-instance-1"),
|
|
}),
|
|
"state": cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("test-instance-1"),
|
|
"ami": cty.StringVal("ami-12345"),
|
|
}),
|
|
"display_name": cty.StringVal("Test Instance 1"),
|
|
}),
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"identity": cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("test-instance-2"),
|
|
}),
|
|
"state": cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("test-instance-2"),
|
|
"ami": cty.StringVal("ami-67890"),
|
|
}),
|
|
"display_name": cty.StringVal("Test Instance 2"),
|
|
}),
|
|
}),
|
|
"config": configMap,
|
|
}),
|
|
}
|
|
case "test_database":
|
|
return providers.ListResourceResponse{
|
|
Result: cty.ObjectVal(map[string]cty.Value{
|
|
"data": cty.ListVal([]cty.Value{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"identity": cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("test-db-1"),
|
|
}),
|
|
"state": cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("test-db-1"),
|
|
"engine": cty.StringVal("mysql"),
|
|
}),
|
|
"display_name": cty.StringVal("Test Database 1"),
|
|
}),
|
|
}),
|
|
"config": configMap,
|
|
}),
|
|
}
|
|
default:
|
|
return providers.ListResourceResponse{
|
|
Result: cty.ObjectVal(map[string]cty.Value{
|
|
"data": cty.ListVal([]cty.Value{}),
|
|
"config": configMap,
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
return p
|
|
}
|