mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-20 11:48:24 -06:00
In order to parse provider, resource and data source configuration from HCL2 config files, we need to know the relevant configuration schema. This new method allows Terraform Core to request these from a provider. This is a breaking change to this interface, so all of its implementers in this package are updated too. This includes concrete implementations of the new method in helper/schema that use the schema conversion code added in an earlier commit to produce a configschema.Block automatically. Plugins compiled against prior versions of helper/schema will not have support for this method, and so calls to them will fail. Callers of this new method will therefore need to sniff for support using the SchemaAvailable field added to both ResourceType and DataSource. This careful handling will need to persist until next time we increment the plugin protocol version, at which point we can make the breaking change of requiring this information to be available.
553 lines
10 KiB
Go
553 lines
10 KiB
Go
package schema
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/config"
|
|
"github.com/hashicorp/terraform/config/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": &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": &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 !reflect.DeepEqual(got, want) {
|
|
t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want))
|
|
}
|
|
}
|
|
|
|
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 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)
|
|
}
|
|
}
|
|
}
|