mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-21 22:22:58 -06:00
31349a9c3a
This is part of a general effort to move all of Terraform's non-library package surface under internal in order to reinforce that these are for internal use within Terraform only. If you were previously importing packages under this prefix into an external codebase, you could pin to an earlier release tag as an interim solution until you've make a plan to achieve the same functionality some other way.
621 lines
12 KiB
Go
621 lines
12 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/internal/configs/configschema"
|
|
"github.com/hashicorp/terraform/internal/legacy/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 := terraform.NewResourceConfigRaw(tc.Config)
|
|
err := tc.P.Configure(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 := terraform.NewResourceConfigRaw(tc.Config)
|
|
_, es := tc.P.Validate(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": []interface{}{
|
|
map[string]interface{}{
|
|
"create": "40m",
|
|
},
|
|
},
|
|
}
|
|
ic := terraform.NewResourceConfigRaw(invalidCfg)
|
|
_, err := p.Diff(
|
|
&terraform.InstanceInfo{
|
|
Type: "blah",
|
|
},
|
|
nil,
|
|
ic,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
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 := terraform.NewResourceConfigRaw(invalidCfg)
|
|
_, err := p.Diff(
|
|
&terraform.InstanceInfo{
|
|
Type: "blah",
|
|
},
|
|
nil,
|
|
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 := terraform.NewResourceConfigRaw(tc.Config)
|
|
_, es := tc.P.ValidateResource(tc.Type, 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)
|
|
}
|
|
}
|
|
}
|