builtin/providers: terraform remote state datasource (#18446)
* builtin/providers: implement terraform remote state datasource as providers.Interface * append and return diags separately (to match the idiomatic usage elsewhere in Terraform) * diagnostic summary style improvements * update tests to pass config to schema.CoerceValue * trust that the schema will be enforced and there is no need to check that a given attribute exists * added dataSourceRemoteStateGetSchema() (effectively replacing a function that was inappropriately removed) for consistency with other terraform providers * builtin/provider terraform test: added InternalValidate() test for dataSourceRemoteStateGetSchema
This commit is contained in:
parent
05936df0e7
commit
52ae93cf97
|
@ -3,68 +3,52 @@ package terraform
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
backendinit "github.com/hashicorp/terraform/backend/init"
|
backendinit "github.com/hashicorp/terraform/backend/init"
|
||||||
"github.com/hashicorp/terraform/config/hcl2shim"
|
"github.com/hashicorp/terraform/config/hcl2shim"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
|
"github.com/hashicorp/terraform/providers"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dataSourceRemoteState() *schema.Resource {
|
func dataSourceRemoteStateGetSchema() providers.Schema {
|
||||||
return &schema.Resource{
|
return providers.Schema{
|
||||||
Read: dataSourceRemoteStateRead,
|
Block: &configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
Schema: map[string]*schema.Schema{
|
|
||||||
"backend": {
|
"backend": {
|
||||||
Type: schema.TypeString,
|
Type: cty.String,
|
||||||
Required: true,
|
Required: true,
|
||||||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
|
|
||||||
if vStr, ok := v.(string); ok && vStr == "_local" {
|
|
||||||
ws = append(ws, "Use of the %q backend is now officially "+
|
|
||||||
"supported as %q. Please update your configuration to ensure "+
|
|
||||||
"compatibility with future versions of Terraform.",
|
|
||||||
"_local", "local")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
"config": {
|
"config": {
|
||||||
Type: schema.TypeMap,
|
Type: cty.DynamicPseudoType,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"defaults": {
|
"defaults": {
|
||||||
Type: schema.TypeMap,
|
Type: cty.DynamicPseudoType,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
"outputs": {
|
||||||
"environment": {
|
Type: cty.DynamicPseudoType,
|
||||||
Type: schema.TypeString,
|
Computed: true,
|
||||||
Optional: true,
|
|
||||||
Default: backend.DefaultStateName,
|
|
||||||
Deprecated: "Terraform environments are now called workspaces. Please use the workspace key instead.",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"workspace": {
|
"workspace": {
|
||||||
Type: schema.TypeString,
|
Type: cty.String,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: backend.DefaultStateName,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"__has_dynamic_attributes": {
|
|
||||||
Type: schema.TypeString,
|
|
||||||
Optional: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
|
func dataSourceRemoteStateRead(d *cty.Value) (cty.Value, tfdiags.Diagnostics) {
|
||||||
backendType := d.Get("backend").(string)
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
|
newState := make(map[string]cty.Value)
|
||||||
|
newState["backend"] = d.GetAttr("backend")
|
||||||
|
|
||||||
|
backendType := d.GetAttr("backend").AsString()
|
||||||
|
|
||||||
// Don't break people using the old _local syntax - but note warning above
|
// Don't break people using the old _local syntax - but note warning above
|
||||||
if backendType == "_local" {
|
if backendType == "_local" {
|
||||||
|
@ -76,67 +60,92 @@ func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
log.Printf("[DEBUG] Initializing remote state backend: %s", backendType)
|
log.Printf("[DEBUG] Initializing remote state backend: %s", backendType)
|
||||||
f := backendinit.Backend(backendType)
|
f := backendinit.Backend(backendType)
|
||||||
if f == nil {
|
if f == nil {
|
||||||
return fmt.Errorf("Unknown backend type: %s", backendType)
|
diags = diags.Append(tfdiags.AttributeValue(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Invalid backend configuration",
|
||||||
|
fmt.Sprintf("Unknown backend type: %s", backendType),
|
||||||
|
cty.Path(nil).GetAttr("backend"),
|
||||||
|
))
|
||||||
|
return cty.NilVal, diags
|
||||||
}
|
}
|
||||||
b := f()
|
b := f()
|
||||||
|
|
||||||
schema := b.ConfigSchema()
|
config := d.GetAttr("config")
|
||||||
rawConfig := d.Get("config")
|
newState["config"] = config
|
||||||
configVal := hcl2shim.HCL2ValueFromConfigValue(rawConfig)
|
|
||||||
|
|
||||||
|
schema := b.ConfigSchema()
|
||||||
// Try to coerce the provided value into the desired configuration type.
|
// Try to coerce the provided value into the desired configuration type.
|
||||||
configVal, err := schema.CoerceValue(configVal)
|
configVal, err := schema.CoerceValue(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid %s backend configuration: %s", backendType, tfdiags.FormatError(err))
|
diags = diags.Append(tfdiags.AttributeValue(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Invalid backend configuration",
|
||||||
|
fmt.Sprintf("The given configuration is not valid for backend %q: %s.", backendType,
|
||||||
|
tfdiags.FormatError(err)),
|
||||||
|
cty.Path(nil).GetAttr("config"),
|
||||||
|
))
|
||||||
|
return cty.NilVal, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
validateDiags := b.ValidateConfig(configVal)
|
validateDiags := b.ValidateConfig(configVal)
|
||||||
|
diags = diags.Append(validateDiags)
|
||||||
if validateDiags.HasErrors() {
|
if validateDiags.HasErrors() {
|
||||||
return validateDiags.Err()
|
return cty.NilVal, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
configureDiags := b.Configure(configVal)
|
configureDiags := b.Configure(configVal)
|
||||||
if configureDiags.HasErrors() {
|
if configureDiags.HasErrors() {
|
||||||
return configureDiags.Err()
|
diags = diags.Append(configureDiags.Err())
|
||||||
|
return cty.NilVal, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
// environment is deprecated in favour of workspace.
|
var name string
|
||||||
// If both keys are set workspace should win.
|
|
||||||
name := d.Get("environment").(string)
|
if workspaceVal := d.GetAttr("workspace"); !workspaceVal.IsNull() {
|
||||||
if ws, ok := d.GetOk("workspace"); ok && ws != backend.DefaultStateName {
|
newState["workspace"] = workspaceVal
|
||||||
name = ws.(string)
|
ws := workspaceVal.AsString()
|
||||||
|
if ws != backend.DefaultStateName {
|
||||||
|
name = ws
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err := b.State(name)
|
state, err := b.State(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error loading the remote state: %s", err)
|
diags = diags.Append(tfdiags.AttributeValue(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Error loading state error",
|
||||||
|
fmt.Sprintf("error loading the remote state: %s", err),
|
||||||
|
cty.Path(nil).GetAttr("backend"),
|
||||||
|
))
|
||||||
|
return cty.NilVal, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.RefreshState(); err != nil {
|
if err := state.RefreshState(); err != nil {
|
||||||
return err
|
diags = diags.Append(err)
|
||||||
|
return cty.NilVal, diags
|
||||||
}
|
}
|
||||||
d.SetId(time.Now().UTC().String())
|
|
||||||
|
|
||||||
outputMap := make(map[string]interface{})
|
outputs := make(map[string]cty.Value)
|
||||||
|
|
||||||
defaults := d.Get("defaults").(map[string]interface{})
|
if defaultsVal := d.GetAttr("defaults"); !defaultsVal.IsNull() {
|
||||||
for key, val := range defaults {
|
newState["defaults"] = defaultsVal
|
||||||
outputMap[key] = val
|
it := defaultsVal.ElementIterator()
|
||||||
|
for it.Next() {
|
||||||
|
k, v := it.Element()
|
||||||
|
outputs[k.AsString()] = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteState := state.State()
|
remoteState := state.State()
|
||||||
if remoteState.Empty() {
|
if remoteState.Empty() {
|
||||||
log.Println("[DEBUG] empty remote state")
|
log.Println("[DEBUG] empty remote state")
|
||||||
} else {
|
} else {
|
||||||
for key, val := range remoteState.RootModule().Outputs {
|
for k, os := range remoteState.RootModule().Outputs {
|
||||||
if val.Value != nil {
|
outputs[k] = hcl2shim.HCL2ValueFromConfigValue(os.Value)
|
||||||
outputMap[key] = val.Value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mappedOutputs := remoteStateFlatten(outputMap)
|
newState["outputs"] = cty.ObjectVal(outputs)
|
||||||
|
|
||||||
for key, val := range mappedOutputs {
|
return cty.ObjectVal(newState), diags
|
||||||
d.UnsafeSetFieldRaw(key, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,225 +1,135 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
backendInit "github.com/hashicorp/terraform/backend/init"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestResource(t *testing.T) {
|
||||||
|
if err := dataSourceRemoteStateGetSchema().Block.InternalValidate(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestState_basic(t *testing.T) {
|
func TestState_basic(t *testing.T) {
|
||||||
resource.UnitTest(t, resource.TestCase{
|
var tests = []struct {
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
Config cty.Value
|
||||||
Providers: testAccProviders,
|
Want cty.Value
|
||||||
Steps: []resource.TestStep{
|
Err bool
|
||||||
{
|
}{
|
||||||
Config: testAccState_basic,
|
{ // basic test
|
||||||
Check: resource.ComposeTestCheckFunc(
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
testAccCheckStateValue(
|
"backend": cty.StringVal("local"),
|
||||||
"data.terraform_remote_state.foo", "foo", "bar"),
|
"config": cty.ObjectVal(map[string]cty.Value{
|
||||||
),
|
"path": cty.StringVal("./test-fixtures/basic.tfstate"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"backend": cty.StringVal("local"),
|
||||||
|
"config": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"path": cty.StringVal("./test-fixtures/basic.tfstate"),
|
||||||
|
}),
|
||||||
|
"outputs": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.StringVal("bar"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
|
{ // complex outputs
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"backend": cty.StringVal("local"),
|
||||||
|
"config": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"path": cty.StringVal("./test-fixtures/complex_outputs.tfstate"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"backend": cty.StringVal("local"),
|
||||||
|
"config": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"path": cty.StringVal("./test-fixtures/complex_outputs.tfstate"),
|
||||||
|
}),
|
||||||
|
"outputs": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"computed_map": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"key1": cty.StringVal("value1"),
|
||||||
|
}),
|
||||||
|
"computed_set": cty.TupleVal([]cty.Value{
|
||||||
|
cty.StringVal("setval1"),
|
||||||
|
cty.StringVal("setval2"),
|
||||||
|
}),
|
||||||
|
"map": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"key": cty.StringVal("test"),
|
||||||
|
"test": cty.StringVal("test"),
|
||||||
|
}),
|
||||||
|
"set": cty.TupleVal([]cty.Value{
|
||||||
|
cty.StringVal("test1"),
|
||||||
|
cty.StringVal("test2"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
})
|
{ // null outputs
|
||||||
}
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"backend": cty.StringVal("local"),
|
||||||
func TestState_backends(t *testing.T) {
|
"config": cty.ObjectVal(map[string]cty.Value{
|
||||||
backendInit.Set("_ds_test", backendInit.Backend("local"))
|
"path": cty.StringVal("./test-fixtures/null_outputs.tfstate"),
|
||||||
defer backendInit.Set("_ds_test", nil)
|
}),
|
||||||
|
}),
|
||||||
resource.UnitTest(t, resource.TestCase{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
"backend": cty.StringVal("local"),
|
||||||
Providers: testAccProviders,
|
"config": cty.ObjectVal(map[string]cty.Value{
|
||||||
Steps: []resource.TestStep{
|
"path": cty.StringVal("./test-fixtures/null_outputs.tfstate"),
|
||||||
{
|
}),
|
||||||
Config: testAccState_backend,
|
"outputs": cty.ObjectVal(map[string]cty.Value{
|
||||||
Check: resource.ComposeTestCheckFunc(
|
"map": cty.NullVal(cty.DynamicPseudoType),
|
||||||
testAccCheckStateValue(
|
"list": cty.NullVal(cty.DynamicPseudoType),
|
||||||
"data.terraform_remote_state.foo", "foo", "bar"),
|
}),
|
||||||
),
|
}),
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
|
{ // defaults
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"backend": cty.StringVal("local"),
|
||||||
|
"config": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"path": cty.StringVal("./test-fixtures/empty.tfstate"),
|
||||||
|
}),
|
||||||
|
"defaults": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.StringVal("bar"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"backend": cty.StringVal("local"),
|
||||||
|
"config": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"path": cty.StringVal("./test-fixtures/empty.tfstate"),
|
||||||
|
}),
|
||||||
|
"defaults": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.StringVal("bar"),
|
||||||
|
}),
|
||||||
|
"outputs": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.StringVal("bar"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
schema := dataSourceRemoteStateGetSchema().Block
|
||||||
|
config, err := schema.CoerceValue(test.Config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
got, diags := dataSourceRemoteStateRead(&config)
|
||||||
|
|
||||||
|
if test.Err {
|
||||||
|
if !diags.HasErrors() {
|
||||||
|
t.Fatal("succeeded; want error")
|
||||||
|
}
|
||||||
|
} else if diags.HasErrors() {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestState_complexOutputs(t *testing.T) {
|
if !got.RawEquals(test.Want) {
|
||||||
resource.UnitTest(t, resource.TestCase{
|
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
|
||||||
Providers: testAccProviders,
|
|
||||||
Steps: []resource.TestStep{
|
|
||||||
{
|
|
||||||
Config: testAccState_complexOutputs,
|
|
||||||
Check: resource.ComposeTestCheckFunc(
|
|
||||||
testAccCheckStateValue("terraform_remote_state.foo", "backend", "local"),
|
|
||||||
// This (adding the hash) should be reverted when merged into 0.12.
|
|
||||||
// testAccCheckStateValue("terraform_remote_state.foo", "config.path", "./test-fixtures/complex_outputs.tfstate"),
|
|
||||||
testAccCheckStateValue("terraform_remote_state.foo", "config.1590222752.path", "./test-fixtures/complex_outputs.tfstate"),
|
|
||||||
testAccCheckStateValue("terraform_remote_state.foo", "computed_set.#", "2"),
|
|
||||||
testAccCheckStateValue("terraform_remote_state.foo", `map.%`, "2"),
|
|
||||||
testAccCheckStateValue("terraform_remote_state.foo", `map.key`, "test"),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// outputs should never have a null value, but don't crash if we ever encounter
|
|
||||||
// them.
|
|
||||||
func TestState_nullOutputs(t *testing.T) {
|
|
||||||
resource.UnitTest(t, resource.TestCase{
|
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
|
||||||
Providers: testAccProviders,
|
|
||||||
Steps: []resource.TestStep{
|
|
||||||
{
|
|
||||||
Config: testAccState_nullOutputs,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyState_defaults(t *testing.T) {
|
|
||||||
resource.UnitTest(t, resource.TestCase{
|
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
|
||||||
Providers: testAccProviders,
|
|
||||||
Steps: []resource.TestStep{
|
|
||||||
{
|
|
||||||
Config: testAccEmptyState_defaults,
|
|
||||||
Check: resource.ComposeTestCheckFunc(
|
|
||||||
testAccCheckStateValue(
|
|
||||||
"data.terraform_remote_state.foo", "foo", "bar"),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestState_defaults(t *testing.T) {
|
|
||||||
resource.UnitTest(t, resource.TestCase{
|
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
|
||||||
Providers: testAccProviders,
|
|
||||||
Steps: []resource.TestStep{
|
|
||||||
{
|
|
||||||
Config: testAccEmptyState_defaults,
|
|
||||||
Check: resource.ComposeTestCheckFunc(
|
|
||||||
testAccCheckStateValue(
|
|
||||||
"data.terraform_remote_state.foo", "foo", "bar"),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAccCheckStateValue(id, name, value string) resource.TestCheckFunc {
|
|
||||||
return func(s *terraform.State) error {
|
|
||||||
rs, ok := s.RootModule().Resources[id]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("Not found: %s", id)
|
|
||||||
}
|
|
||||||
if rs.Primary.ID == "" {
|
|
||||||
return fmt.Errorf("No ID is set")
|
|
||||||
}
|
|
||||||
|
|
||||||
v := rs.Primary.Attributes[name]
|
|
||||||
if v != value {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Value for %s is %s, not %s", name, v, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure that the deprecated environment field isn't overridden by the
|
|
||||||
// default value for workspace.
|
|
||||||
func TestState_deprecatedEnvironment(t *testing.T) {
|
|
||||||
resource.UnitTest(t, resource.TestCase{
|
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
|
||||||
Providers: testAccProviders,
|
|
||||||
Steps: []resource.TestStep{
|
|
||||||
{
|
|
||||||
Config: testAccState_deprecatedEnvironment,
|
|
||||||
Check: resource.ComposeTestCheckFunc(
|
|
||||||
testAccCheckStateValue(
|
|
||||||
// if the workspace default value overrides the
|
|
||||||
// environment, this will get the foo value from the
|
|
||||||
// default state.
|
|
||||||
"data.terraform_remote_state.foo", "foo", ""),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const testAccState_basic = `
|
|
||||||
data "terraform_remote_state" "foo" {
|
|
||||||
backend = "local"
|
|
||||||
|
|
||||||
config {
|
|
||||||
path = "./test-fixtures/basic.tfstate"
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
const testAccState_backend = `
|
|
||||||
data "terraform_remote_state" "foo" {
|
|
||||||
backend = "_ds_test"
|
|
||||||
|
|
||||||
config {
|
|
||||||
path = "./test-fixtures/basic.tfstate"
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
const testAccState_complexOutputs = `
|
|
||||||
resource "terraform_remote_state" "foo" {
|
|
||||||
backend = "local"
|
|
||||||
|
|
||||||
config {
|
|
||||||
path = "./test-fixtures/complex_outputs.tfstate"
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
const testAccState_nullOutputs = `
|
|
||||||
resource "terraform_remote_state" "foo" {
|
|
||||||
backend = "local"
|
|
||||||
|
|
||||||
config {
|
|
||||||
path = "./test-fixtures/null_outputs.tfstate"
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
const testAccEmptyState_defaults = `
|
|
||||||
data "terraform_remote_state" "foo" {
|
|
||||||
backend = "local"
|
|
||||||
|
|
||||||
config {
|
|
||||||
path = "./test-fixtures/empty.tfstate"
|
|
||||||
}
|
|
||||||
|
|
||||||
defaults {
|
|
||||||
foo = "bar"
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
const testAccState_defaults = `
|
|
||||||
data "terraform_remote_state" "foo" {
|
|
||||||
backend = "local"
|
|
||||||
|
|
||||||
config {
|
|
||||||
path = "./test-fixtures/basic.tfstate"
|
|
||||||
}
|
|
||||||
|
|
||||||
defaults {
|
|
||||||
foo = "not bar"
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
const testAccState_deprecatedEnvironment = `
|
|
||||||
data "terraform_remote_state" "foo" {
|
|
||||||
backend = "local"
|
|
||||||
environment = "deprecated"
|
|
||||||
|
|
||||||
config {
|
|
||||||
path = "./test-fixtures/basic.tfstate"
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
|
@ -1,21 +1,124 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"fmt"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider returns a terraform.ResourceProvider.
|
// Provider is an implementation of providers.Interface
|
||||||
func Provider() terraform.ResourceProvider {
|
type Provider struct {
|
||||||
return &schema.Provider{
|
// Provider is the schema for the provider itself.
|
||||||
ResourcesMap: map[string]*schema.Resource{
|
Schema providers.Schema
|
||||||
"terraform_remote_state": schema.DataSourceResourceShim(
|
|
||||||
"terraform_remote_state",
|
// DataSources maps the data source name to that data source's schema.
|
||||||
dataSourceRemoteState(),
|
DataSources map[string]providers.Schema
|
||||||
),
|
}
|
||||||
},
|
|
||||||
DataSourcesMap: map[string]*schema.Resource{
|
// NewProvider returns a new terraform provider
|
||||||
"terraform_remote_state": dataSourceRemoteState(),
|
func NewProvider() *Provider {
|
||||||
|
return &Provider{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSchema returns the complete schema for the provider.
|
||||||
|
func (p *Provider) GetSchema() providers.GetSchemaResponse {
|
||||||
|
return providers.GetSchemaResponse{
|
||||||
|
DataSources: map[string]providers.Schema{
|
||||||
|
"terraform_remote_state": dataSourceRemoteStateGetSchema(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateProviderConfig is used to validate the configuration values.
|
||||||
|
func (p *Provider) ValidateProviderConfig(providers.ValidateProviderConfigRequest) providers.ValidateProviderConfigResponse {
|
||||||
|
// At this moment there is nothing to configure for the terraform provider,
|
||||||
|
// so we will happily return without taking any action
|
||||||
|
var res providers.ValidateProviderConfigResponse
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateDataSourceConfig is used to validate the data source configuration values.
|
||||||
|
func (p *Provider) ValidateDataSourceConfig(providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse {
|
||||||
|
// FIXME: move the backend configuration validate call that's currently
|
||||||
|
// inside the read method into here so that we can catch provider configuration
|
||||||
|
// errors in terraform validate as well as during terraform plan.
|
||||||
|
var res providers.ValidateDataSourceConfigResponse
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure configures and initializes the provider.
|
||||||
|
func (p *Provider) Configure(providers.ConfigureRequest) providers.ConfigureResponse {
|
||||||
|
// At this moment there is nothing to configure for the terraform provider,
|
||||||
|
// so we will happily return without taking any action
|
||||||
|
var res providers.ConfigureResponse
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadDataSource returns the data source's current state.
|
||||||
|
func (p *Provider) ReadDataSource(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
||||||
|
// call function
|
||||||
|
var res providers.ReadDataSourceResponse
|
||||||
|
|
||||||
|
// This should not happen
|
||||||
|
if req.TypeName != "terraform_remote_state" {
|
||||||
|
res.Diagnostics.Append(fmt.Errorf("Error: unsupported data source %s", req.TypeName))
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
newState, diags := dataSourceRemoteStateRead(&req.Config)
|
||||||
|
|
||||||
|
res.State = newState
|
||||||
|
res.Diagnostics = diags
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop is called when the provider should halt any in-flight actions.
|
||||||
|
func (p *Provider) Stop() error {
|
||||||
|
log.Println("[DEBUG] terraform provider cannot Stop")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// All the Resource-specific functions are below.
|
||||||
|
// The terraform provider supplies a single data source, `terraform_remote_state`
|
||||||
|
// and no resources.
|
||||||
|
|
||||||
|
// UpgradeResourceState is called when the state loader encounters an
|
||||||
|
// instance state whose schema version is less than the one reported by the
|
||||||
|
// currently-used version of the corresponding provider, and the upgraded
|
||||||
|
// result is used for any further processing.
|
||||||
|
func (p *Provider) UpgradeResourceState(providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse {
|
||||||
|
panic("unimplemented - terraform_remote_state has no resources")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadResource refreshes a resource and returns its current state.
|
||||||
|
func (p *Provider) ReadResource(providers.ReadResourceRequest) providers.ReadResourceResponse {
|
||||||
|
panic("unimplemented - terraform_remote_state has no resources")
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlanResourceChange takes the current state and proposed state of a
|
||||||
|
// resource, and returns the planned final state.
|
||||||
|
func (p *Provider) PlanResourceChange(providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||||
|
panic("unimplemented - terraform_remote_state has no resources")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyResourceChange takes the planned state for a resource, which may
|
||||||
|
// yet contain unknown computed values, and applies the changes returning
|
||||||
|
// the final state.
|
||||||
|
func (p *Provider) ApplyResourceChange(providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
||||||
|
panic("unimplemented - terraform_remote_state has no resources")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportResourceState requests that the given resource be imported.
|
||||||
|
func (p *Provider) ImportResourceState(providers.ImportResourceStateRequest) providers.ImportResourceStateResponse {
|
||||||
|
panic("unimplemented - terraform_remote_state has no resources")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateResourceTypeConfig is used to to validate the resource configuration values.
|
||||||
|
func (p *Provider) ValidateResourceTypeConfig(providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse {
|
||||||
|
// At this moment there is nothing to configure for the terraform provider,
|
||||||
|
// so we will happily return without taking any action
|
||||||
|
var res providers.ValidateResourceTypeConfigResponse
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
@ -3,32 +3,21 @@ package terraform
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
backendInit "github.com/hashicorp/terraform/backend/init"
|
"github.com/hashicorp/terraform/providers"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var testAccProviders map[string]terraform.ResourceProvider
|
var testAccProviders map[string]*Provider
|
||||||
var testAccProvider *schema.Provider
|
var testAccProvider *Provider
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Initialize the backends
|
testAccProvider = NewProvider()
|
||||||
backendInit.Init(nil)
|
testAccProviders = map[string]*Provider{
|
||||||
|
|
||||||
testAccProvider = Provider().(*schema.Provider)
|
|
||||||
testAccProviders = map[string]terraform.ResourceProvider{
|
|
||||||
"terraform": testAccProvider,
|
"terraform": testAccProvider,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProvider(t *testing.T) {
|
|
||||||
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProvider_impl(t *testing.T) {
|
func TestProvider_impl(t *testing.T) {
|
||||||
var _ terraform.ResourceProvider = Provider()
|
var _ providers.Interface = NewProvider()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccPreCheck(t *testing.T) {
|
func testAccPreCheck(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue