mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-20 11:48:24 -06:00
The new decoder is more precise, and unpacks the timeout block into a single map, which ResourceTimeout.ConfigDecode was updated to handle. We however still need to work with legacy versions of terraform, with the old decoder.
690 lines
13 KiB
Go
690 lines
13 KiB
Go
package schema
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/config"
|
|
"github.com/hashicorp/terraform/configs/configschema"
|
|
"github.com/hashicorp/terraform/terraform"
|
|
)
|
|
|
|
func TestProvider_impl(t *testing.T) {
|
|
var _ terraform.ResourceProvider = new(Provider)
|
|
}
|
|
|
|
func TestProviderGetSchema(t *testing.T) {
|
|
// This functionality is already broadly tested in core_schema_test.go,
|
|
// so this is just to ensure that the call passes through correctly.
|
|
p := &Provider{
|
|
Schema: map[string]*Schema{
|
|
"bar": {
|
|
Type: TypeString,
|
|
Required: true,
|
|
},
|
|
},
|
|
ResourcesMap: map[string]*Resource{
|
|
"foo": &Resource{
|
|
Schema: map[string]*Schema{
|
|
"bar": {
|
|
Type: TypeString,
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
DataSourcesMap: map[string]*Resource{
|
|
"baz": &Resource{
|
|
Schema: map[string]*Schema{
|
|
"bur": {
|
|
Type: TypeString,
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
want := &terraform.ProviderSchema{
|
|
Provider: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"bar": &configschema.Attribute{
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
BlockTypes: map[string]*configschema.NestedBlock{},
|
|
},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"foo": testResource(&configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"bar": &configschema.Attribute{
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
BlockTypes: map[string]*configschema.NestedBlock{},
|
|
}),
|
|
},
|
|
DataSources: map[string]*configschema.Block{
|
|
"baz": testResource(&configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"bur": &configschema.Attribute{
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
BlockTypes: map[string]*configschema.NestedBlock{},
|
|
}),
|
|
},
|
|
}
|
|
got, err := p.GetSchema(&terraform.ProviderSchemaRequest{
|
|
ResourceTypes: []string{"foo", "bar"},
|
|
DataSources: []string{"baz", "bar"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error %s", err)
|
|
}
|
|
|
|
if !cmp.Equal(got, want, equateEmpty, typeComparer) {
|
|
t.Error("wrong result:\n", cmp.Diff(got, want, equateEmpty, typeComparer))
|
|
}
|
|
}
|
|
|
|
func TestProviderConfigure(t *testing.T) {
|
|
cases := []struct {
|
|
P *Provider
|
|
Config map[string]interface{}
|
|
Err bool
|
|
}{
|
|
{
|
|
P: &Provider{},
|
|
Config: nil,
|
|
Err: false,
|
|
},
|
|
|
|
{
|
|
P: &Provider{
|
|
Schema: map[string]*Schema{
|
|
"foo": &Schema{
|
|
Type: TypeInt,
|
|
Optional: true,
|
|
},
|
|
},
|
|
|
|
ConfigureFunc: func(d *ResourceData) (interface{}, error) {
|
|
if d.Get("foo").(int) == 42 {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("nope")
|
|
},
|
|
},
|
|
Config: map[string]interface{}{
|
|
"foo": 42,
|
|
},
|
|
Err: false,
|
|
},
|
|
|
|
{
|
|
P: &Provider{
|
|
Schema: map[string]*Schema{
|
|
"foo": &Schema{
|
|
Type: TypeInt,
|
|
Optional: true,
|
|
},
|
|
},
|
|
|
|
ConfigureFunc: func(d *ResourceData) (interface{}, error) {
|
|
if d.Get("foo").(int) == 42 {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("nope")
|
|
},
|
|
},
|
|
Config: map[string]interface{}{
|
|
"foo": 52,
|
|
},
|
|
Err: true,
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
c, err := config.NewRawConfig(tc.Config)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
err = tc.P.Configure(terraform.NewResourceConfig(c))
|
|
if err != nil != tc.Err {
|
|
t.Fatalf("%d: %s", i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProviderResources(t *testing.T) {
|
|
cases := []struct {
|
|
P *Provider
|
|
Result []terraform.ResourceType
|
|
}{
|
|
{
|
|
P: &Provider{},
|
|
Result: []terraform.ResourceType{},
|
|
},
|
|
|
|
{
|
|
P: &Provider{
|
|
ResourcesMap: map[string]*Resource{
|
|
"foo": nil,
|
|
"bar": nil,
|
|
},
|
|
},
|
|
Result: []terraform.ResourceType{
|
|
terraform.ResourceType{Name: "bar", SchemaAvailable: true},
|
|
terraform.ResourceType{Name: "foo", SchemaAvailable: true},
|
|
},
|
|
},
|
|
|
|
{
|
|
P: &Provider{
|
|
ResourcesMap: map[string]*Resource{
|
|
"foo": nil,
|
|
"bar": &Resource{Importer: &ResourceImporter{}},
|
|
"baz": nil,
|
|
},
|
|
},
|
|
Result: []terraform.ResourceType{
|
|
terraform.ResourceType{Name: "bar", Importable: true, SchemaAvailable: true},
|
|
terraform.ResourceType{Name: "baz", SchemaAvailable: true},
|
|
terraform.ResourceType{Name: "foo", SchemaAvailable: true},
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
actual := tc.P.Resources()
|
|
if !reflect.DeepEqual(actual, tc.Result) {
|
|
t.Fatalf("%d: %#v", i, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProviderDataSources(t *testing.T) {
|
|
cases := []struct {
|
|
P *Provider
|
|
Result []terraform.DataSource
|
|
}{
|
|
{
|
|
P: &Provider{},
|
|
Result: []terraform.DataSource{},
|
|
},
|
|
|
|
{
|
|
P: &Provider{
|
|
DataSourcesMap: map[string]*Resource{
|
|
"foo": nil,
|
|
"bar": nil,
|
|
},
|
|
},
|
|
Result: []terraform.DataSource{
|
|
terraform.DataSource{Name: "bar", SchemaAvailable: true},
|
|
terraform.DataSource{Name: "foo", SchemaAvailable: true},
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
actual := tc.P.DataSources()
|
|
if !reflect.DeepEqual(actual, tc.Result) {
|
|
t.Fatalf("%d: got %#v; want %#v", i, actual, tc.Result)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProviderValidate(t *testing.T) {
|
|
cases := []struct {
|
|
P *Provider
|
|
Config map[string]interface{}
|
|
Err bool
|
|
}{
|
|
{
|
|
P: &Provider{
|
|
Schema: map[string]*Schema{
|
|
"foo": &Schema{},
|
|
},
|
|
},
|
|
Config: nil,
|
|
Err: true,
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
c, err := config.NewRawConfig(tc.Config)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
_, es := tc.P.Validate(terraform.NewResourceConfig(c))
|
|
if len(es) > 0 != tc.Err {
|
|
t.Fatalf("%d: %#v", i, es)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProviderDiff_legacyTimeoutType(t *testing.T) {
|
|
p := &Provider{
|
|
ResourcesMap: map[string]*Resource{
|
|
"blah": &Resource{
|
|
Schema: map[string]*Schema{
|
|
"foo": {
|
|
Type: TypeInt,
|
|
Optional: true,
|
|
},
|
|
},
|
|
Timeouts: &ResourceTimeout{
|
|
Create: DefaultTimeout(10 * time.Minute),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
invalidCfg := map[string]interface{}{
|
|
"foo": 42,
|
|
"timeouts": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"create": "40m",
|
|
},
|
|
},
|
|
}
|
|
ic, err := config.NewRawConfig(invalidCfg)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
_, err = p.Diff(
|
|
&terraform.InstanceInfo{
|
|
Type: "blah",
|
|
},
|
|
nil,
|
|
terraform.NewResourceConfig(ic),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestProviderDiff_invalidTimeoutType(t *testing.T) {
|
|
p := &Provider{
|
|
ResourcesMap: map[string]*Resource{
|
|
"blah": &Resource{
|
|
Schema: map[string]*Schema{
|
|
"foo": {
|
|
Type: TypeInt,
|
|
Optional: true,
|
|
},
|
|
},
|
|
Timeouts: &ResourceTimeout{
|
|
Create: DefaultTimeout(10 * time.Minute),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
invalidCfg := map[string]interface{}{
|
|
"foo": 42,
|
|
"timeouts": []interface{}{
|
|
map[string]interface{}{
|
|
"create": "40m",
|
|
},
|
|
},
|
|
}
|
|
ic, err := config.NewRawConfig(invalidCfg)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
_, err = p.Diff(
|
|
&terraform.InstanceInfo{
|
|
Type: "blah",
|
|
},
|
|
nil,
|
|
terraform.NewResourceConfig(ic),
|
|
)
|
|
if err == nil {
|
|
t.Fatal("Expected provider.Diff to fail with invalid timeout type")
|
|
}
|
|
expectedErrMsg := "Invalid Timeout structure"
|
|
if !strings.Contains(err.Error(), expectedErrMsg) {
|
|
t.Fatalf("Unexpected error message: %q\nExpected message to contain %q",
|
|
err.Error(),
|
|
expectedErrMsg)
|
|
}
|
|
}
|
|
|
|
func TestProviderDiff_timeoutInvalidValue(t *testing.T) {
|
|
p := &Provider{
|
|
ResourcesMap: map[string]*Resource{
|
|
"blah": &Resource{
|
|
Schema: map[string]*Schema{
|
|
"foo": {
|
|
Type: TypeInt,
|
|
Optional: true,
|
|
},
|
|
},
|
|
Timeouts: &ResourceTimeout{
|
|
Create: DefaultTimeout(10 * time.Minute),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
invalidCfg := map[string]interface{}{
|
|
"foo": 42,
|
|
"timeouts": map[string]interface{}{
|
|
"create": "invalid",
|
|
},
|
|
}
|
|
ic, err := config.NewRawConfig(invalidCfg)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
_, err = p.Diff(
|
|
&terraform.InstanceInfo{
|
|
Type: "blah",
|
|
},
|
|
nil,
|
|
terraform.NewResourceConfig(ic),
|
|
)
|
|
if err == nil {
|
|
t.Fatal("Expected provider.Diff to fail with invalid timeout value")
|
|
}
|
|
expectedErrMsg := "time: invalid duration invalid"
|
|
if !strings.Contains(err.Error(), expectedErrMsg) {
|
|
t.Fatalf("Unexpected error message: %q\nExpected message to contain %q",
|
|
err.Error(),
|
|
expectedErrMsg)
|
|
}
|
|
}
|
|
|
|
func TestProviderValidateResource(t *testing.T) {
|
|
cases := []struct {
|
|
P *Provider
|
|
Type string
|
|
Config map[string]interface{}
|
|
Err bool
|
|
}{
|
|
{
|
|
P: &Provider{},
|
|
Type: "foo",
|
|
Config: nil,
|
|
Err: true,
|
|
},
|
|
|
|
{
|
|
P: &Provider{
|
|
ResourcesMap: map[string]*Resource{
|
|
"foo": &Resource{},
|
|
},
|
|
},
|
|
Type: "foo",
|
|
Config: nil,
|
|
Err: false,
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
c, err := config.NewRawConfig(tc.Config)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
_, es := tc.P.ValidateResource(tc.Type, terraform.NewResourceConfig(c))
|
|
if len(es) > 0 != tc.Err {
|
|
t.Fatalf("%d: %#v", i, es)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProviderImportState_default(t *testing.T) {
|
|
p := &Provider{
|
|
ResourcesMap: map[string]*Resource{
|
|
"foo": &Resource{
|
|
Importer: &ResourceImporter{},
|
|
},
|
|
},
|
|
}
|
|
|
|
states, err := p.ImportState(&terraform.InstanceInfo{
|
|
Type: "foo",
|
|
}, "bar")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if len(states) != 1 {
|
|
t.Fatalf("bad: %#v", states)
|
|
}
|
|
if states[0].ID != "bar" {
|
|
t.Fatalf("bad: %#v", states)
|
|
}
|
|
}
|
|
|
|
func TestProviderImportState_setsId(t *testing.T) {
|
|
var val string
|
|
stateFunc := func(d *ResourceData, meta interface{}) ([]*ResourceData, error) {
|
|
val = d.Id()
|
|
return []*ResourceData{d}, nil
|
|
}
|
|
|
|
p := &Provider{
|
|
ResourcesMap: map[string]*Resource{
|
|
"foo": &Resource{
|
|
Importer: &ResourceImporter{
|
|
State: stateFunc,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := p.ImportState(&terraform.InstanceInfo{
|
|
Type: "foo",
|
|
}, "bar")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if val != "bar" {
|
|
t.Fatal("should set id")
|
|
}
|
|
}
|
|
|
|
func TestProviderImportState_setsType(t *testing.T) {
|
|
var tVal string
|
|
stateFunc := func(d *ResourceData, meta interface{}) ([]*ResourceData, error) {
|
|
d.SetId("foo")
|
|
tVal = d.State().Ephemeral.Type
|
|
return []*ResourceData{d}, nil
|
|
}
|
|
|
|
p := &Provider{
|
|
ResourcesMap: map[string]*Resource{
|
|
"foo": &Resource{
|
|
Importer: &ResourceImporter{
|
|
State: stateFunc,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := p.ImportState(&terraform.InstanceInfo{
|
|
Type: "foo",
|
|
}, "bar")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if tVal != "foo" {
|
|
t.Fatal("should set type")
|
|
}
|
|
}
|
|
|
|
func TestProviderMeta(t *testing.T) {
|
|
p := new(Provider)
|
|
if v := p.Meta(); v != nil {
|
|
t.Fatalf("bad: %#v", v)
|
|
}
|
|
|
|
expected := 42
|
|
p.SetMeta(42)
|
|
if v := p.Meta(); !reflect.DeepEqual(v, expected) {
|
|
t.Fatalf("bad: %#v", v)
|
|
}
|
|
}
|
|
|
|
func TestProviderStop(t *testing.T) {
|
|
var p Provider
|
|
|
|
if p.Stopped() {
|
|
t.Fatal("should not be stopped")
|
|
}
|
|
|
|
// Verify stopch blocks
|
|
ch := p.StopContext().Done()
|
|
select {
|
|
case <-ch:
|
|
t.Fatal("should not be stopped")
|
|
case <-time.After(10 * time.Millisecond):
|
|
}
|
|
|
|
// Stop it
|
|
if err := p.Stop(); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify
|
|
if !p.Stopped() {
|
|
t.Fatal("should be stopped")
|
|
}
|
|
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(10 * time.Millisecond):
|
|
t.Fatal("should be stopped")
|
|
}
|
|
}
|
|
|
|
func TestProviderStop_stopFirst(t *testing.T) {
|
|
var p Provider
|
|
|
|
// Stop it
|
|
if err := p.Stop(); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify
|
|
if !p.Stopped() {
|
|
t.Fatal("should be stopped")
|
|
}
|
|
|
|
select {
|
|
case <-p.StopContext().Done():
|
|
case <-time.After(10 * time.Millisecond):
|
|
t.Fatal("should be stopped")
|
|
}
|
|
}
|
|
|
|
func TestProviderReset(t *testing.T) {
|
|
var p Provider
|
|
stopCtx := p.StopContext()
|
|
p.MetaReset = func() error {
|
|
stopCtx = p.StopContext()
|
|
return nil
|
|
}
|
|
|
|
// cancel the current context
|
|
p.Stop()
|
|
|
|
if err := p.TestReset(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// the first context should have been replaced
|
|
if err := stopCtx.Err(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// we should not get a canceled context here either
|
|
if err := p.StopContext().Err(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestProvider_InternalValidate(t *testing.T) {
|
|
cases := []struct {
|
|
P *Provider
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
P: &Provider{
|
|
Schema: map[string]*Schema{
|
|
"foo": {
|
|
Type: TypeBool,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
ExpectedErr: nil,
|
|
},
|
|
{ // Reserved resource fields should be allowed in provider block
|
|
P: &Provider{
|
|
Schema: map[string]*Schema{
|
|
"provisioner": {
|
|
Type: TypeString,
|
|
Optional: true,
|
|
},
|
|
"count": {
|
|
Type: TypeInt,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
ExpectedErr: nil,
|
|
},
|
|
{ // Reserved provider fields should not be allowed
|
|
P: &Provider{
|
|
Schema: map[string]*Schema{
|
|
"alias": {
|
|
Type: TypeString,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
ExpectedErr: fmt.Errorf("%s is a reserved field name for a provider", "alias"),
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
err := tc.P.InternalValidate()
|
|
if tc.ExpectedErr == nil {
|
|
if err != nil {
|
|
t.Fatalf("%d: Error returned (expected no error): %s", i, err)
|
|
}
|
|
continue
|
|
}
|
|
if tc.ExpectedErr != nil && err == nil {
|
|
t.Fatalf("%d: Expected error (%s), but no error returned", i, tc.ExpectedErr)
|
|
}
|
|
if err.Error() != tc.ExpectedErr.Error() {
|
|
t.Fatalf("%d: Errors don't match. Expected: %#v Given: %#v", i, tc.ExpectedErr, err)
|
|
}
|
|
}
|
|
}
|