Skip to content

Commit

Permalink
provider/metaschema: Initial package (#562)
Browse files Browse the repository at this point in the history
Reference: #132
Reference: #326
Reference: #437
Reference: #491
Reference: #508
Reference: #532

This change introduces a new `provider/metaschema` package, which contains schema interfaces and types relevant to provider meta schemas, such as omitting `Computed`, `DeprecatedMessage`, `Sensitive`, plan modifiers, validators, blocks, schema descriptions, and schema versioning. This new schema implementation also provides strongly typed attributes and nested attributes with customizable types. Nested attributes are exposed with a separate nested object for customization.

The implementation leans heavily on the design choice of the framework being responsible for preventing provider developer runtime errors. The tailored fields no longer expose functionality that is not available for provider meta schemas.

No changes are required for data handling during requests.

Example definition:

```go
package test

import (
	"context"

	"github.com/bflad/terraform-plugin-framework-type-time/timetypes"
	"github.com/hashicorp/terraform-plugin-framework-validators/float64validator"
	"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
	"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
	"github.com/hashicorp/terraform-plugin-framework/provider"
	"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
	"github.com/hashicorp/terraform-plugin-framework/schema/validator"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

type ExampleCloudProvider struct{}

func (p ExampleCloudProvider) MetaSchema(ctx context.Context, req provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
	resp.Schema = metaschema.Schema{
		Attributes: map[string]schema.Attribute{
			"string_attribute": schema.StringAttribute{
				Required: true,
			},
			"custom_string_attribute": schema.StringAttribute{
				CustomType: timetypes.RFC3339Type,
				Optional:   true,
			},
			"list_attribute": schema.ListAttribute{
				ElementType: types.StringType,
				Optional:    true,
			},
			"list_nested_attribute": schema.ListNestedAttribute{
				NestedObject: schema.NestedAttributeObject{
					Attributes: map[string]schema.Attribute{
						"bool_attribute": schema.BoolAttribute{
							Optional: true,
						},
					},
				},
				Optional: true,
			},
			"single_nested_attribute": schema.SingleNestedAttribute{
				Attributes: map[string]schema.Attribute{
					"int64_attribute": schema.Int64Attribute{
						Optional: true,
					},
				},
				Optional: true,
			},
		},
	}
}
```

To migrate a provider meta schema:

- Add `github.com/hashicorp/terraform-plugin-framework/provider/metaschema` to the `import` statement
- Switch the `provider.ProviderWithMetaSchema` implementation `GetMetaSchema` method to `MetaSchema` whose response includes a `metaschema.Schema` from the new package.

Prior implementation:

```go
func (p ExampleCloudProvider) GetMetaSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
  return tfsdk.Schema{/* ... */}, nil
}
```

Migrated implementation:

```go
func (p ExampleCloudProvider) MetaSchema(ctx context.Context, req provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
  resp.Schema =  metaschema.Schema{/*...*/}
}
```

If the provider requires no meta schema, the method can be removed entirely.

- Switch `map[string]tfsdk.Attribute` with `map[string]metaschema.Attribute`
- Switch individual attribute  definitions. Unless the code was already taking advantage of custom attribute types (uncommon so far), the `Type` field will be removed and the map entries must declare the typed implementation, e.g. a `tfsdk.Attribute` with `Type: types.StringType` is equivalent to `metaschema.StringAttribute`. Custom attribute types can be specified via the `CustomType` field in each of the implementations.

Prior primitive type (`types.BoolType`, `types.Float64Type`, `types.Int64Type`, `types.NumberType`, `types.StringType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.StringType,
}
```

Migrated implementation:

```go
// The metaschema.XXXAttribute must be declared inside map[string]metaschema.Attribute
schema.StringAttribute{
  Required: true,
}
```

Prior collection type (`types.ListType`, `types.MapType`, `types.SetType`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Required: true,
  Type: types.ListType{
    ElemType: types.StringType,
  },
}
```

Migrated implementation:

```go
// The metaschema.XXXAttribute must be declared inside map[string]metaschema.Attribute
metaschema.ListAttribute{
  ElementType: types.StringType,
  Required: true,
}
```

Prior single nested attributes type (`tfsdk.SingleNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The metaschema.XXXAttribute must be declared inside map[string]metaschema.Attribute
metaschema.SingleNestedAttribute{
  Attributes: map[string]metaschema.Attribute{/*...*/},
  Required: true,
}
```

Prior collection nested attributes type (`tfsdk.ListNestedAttributes()`, `tfsdk.MapNestedAttributes()`, `tfsdk.SetNestedAttributes()`) attribute implementation:

```go
// The "tfsdk.Attribute" could be omitted inside a map[string]tfsdk.Attribute
tfsdk.Attribute{
  Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{/*...*/}),
  Required: true,
},
```

Migrated implementation:

```go
// The metaschema.XXXAttribute must be declared inside map[string]metaschema.Attribute
metaschema.ListNestedAttribute{
  NestedObject: metaschema.NestedAttributeObject{
    Attributes: map[string]metaschema.Attribute{/*...*/},
  },
  Required: true,
}
```
  • Loading branch information
bflad committed Nov 29, 2022
1 parent abe43b2 commit 9353b7c
Show file tree
Hide file tree
Showing 51 changed files with 8,645 additions and 184 deletions.
3 changes: 3 additions & 0 deletions .changelog/562.txt
@@ -0,0 +1,3 @@
```release-note:breaking-change
provider: The `ProviderWithMetaSchema` type `GetMetaSchema` method has been replaced with the `MetaSchema` method
```
15 changes: 9 additions & 6 deletions internal/fwserver/server.go
Expand Up @@ -315,7 +315,7 @@ func (s *Server) ProviderSchema(ctx context.Context) (fwschema.Schema, diag.Diag
// it implements the ProviderWithMetaSchema interface. The Schema and
// Diagnostics are cached on first use.
func (s *Server) ProviderMetaSchema(ctx context.Context) (fwschema.Schema, diag.Diagnostics) {
providerWithProviderMeta, ok := s.Provider.(provider.ProviderWithMetaSchema)
providerWithMetaSchema, ok := s.Provider.(provider.ProviderWithMetaSchema)

if !ok {
return nil, nil
Expand All @@ -330,12 +330,15 @@ func (s *Server) ProviderMetaSchema(ctx context.Context) (fwschema.Schema, diag.
return s.providerMetaSchema, s.providerMetaSchemaDiags
}

logging.FrameworkDebug(ctx, "Calling provider defined Provider GetMetaSchema")
providerMetaSchema, diags := providerWithProviderMeta.GetMetaSchema(ctx)
logging.FrameworkDebug(ctx, "Called provider defined Provider GetMetaSchema")
req := provider.MetaSchemaRequest{}
resp := &provider.MetaSchemaResponse{}

s.providerMetaSchema = &providerMetaSchema
s.providerMetaSchemaDiags = diags
logging.FrameworkDebug(ctx, "Calling provider defined Provider MetaSchema")
providerWithMetaSchema.MetaSchema(ctx, req, resp)
logging.FrameworkDebug(ctx, "Called provider defined Provider MetaSchema")

s.providerMetaSchema = resp.Schema
s.providerMetaSchemaDiags = resp.Diagnostics

return s.providerMetaSchema, s.providerMetaSchemaDiags
}
Expand Down
21 changes: 9 additions & 12 deletions internal/fwserver/server_getproviderschema_test.go
Expand Up @@ -12,11 +12,10 @@ import (
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
providerschema "github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/resource"
resourceschema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
)

func TestServerGetProviderSchema(t *testing.T) {
Expand Down Expand Up @@ -280,27 +279,25 @@ func TestServerGetProviderSchema(t *testing.T) {
server: &fwserver.Server{
Provider: &testprovider.ProviderWithMetaSchema{
Provider: &testprovider.Provider{},
GetMetaSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) {
return tfsdk.Schema{
Attributes: map[string]tfsdk.Attribute{
"test": {
MetaSchemaMethod: func(_ context.Context, _ provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
resp.Schema = metaschema.Schema{
Attributes: map[string]metaschema.Attribute{
"test": metaschema.StringAttribute{
Required: true,
Type: types.StringType,
},
},
}, nil
}
},
},
},
request: &fwserver.GetProviderSchemaRequest{},
expectedResponse: &fwserver.GetProviderSchemaResponse{
DataSourceSchemas: map[string]fwschema.Schema{},
Provider: providerschema.Schema{},
ProviderMeta: &tfsdk.Schema{
Attributes: map[string]tfsdk.Attribute{
"test": {
ProviderMeta: metaschema.Schema{
Attributes: map[string]metaschema.Attribute{
"test": metaschema.StringAttribute{
Required: true,
Type: types.StringType,
},
},
},
Expand Down
27 changes: 13 additions & 14 deletions internal/proto5server/server_applyresourcechange_test.go
Expand Up @@ -9,13 +9,13 @@ import (
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tftypes"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/internal/privatestate"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
)

Expand Down Expand Up @@ -57,11 +57,10 @@ func TestServerApplyResourceChange(t *testing.T) {
"test_provider_meta_attribute": tftypes.NewValue(tftypes.String, "test-provider-meta-value"),
})

testProviderMetaSchema := tfsdk.Schema{
Attributes: map[string]tfsdk.Attribute{
"test_provider_meta_attribute": {
testProviderMetaSchema := metaschema.Schema{
Attributes: map[string]metaschema.Attribute{
"test_provider_meta_attribute": metaschema.StringAttribute{
Optional: true,
Type: types.StringType,
},
},
}
Expand Down Expand Up @@ -233,8 +232,8 @@ func TestServerApplyResourceChange(t *testing.T) {
}
},
},
GetMetaSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) {
return testProviderMetaSchema, nil
MetaSchemaMethod: func(_ context.Context, _ provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
resp.Schema = testProviderMetaSchema
},
},
},
Expand Down Expand Up @@ -559,8 +558,8 @@ func TestServerApplyResourceChange(t *testing.T) {
}
},
},
GetMetaSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) {
return testProviderMetaSchema, nil
MetaSchemaMethod: func(_ context.Context, _ provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
resp.Schema = testProviderMetaSchema
},
},
},
Expand Down Expand Up @@ -946,8 +945,8 @@ func TestServerApplyResourceChange(t *testing.T) {
}
},
},
GetMetaSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) {
return testProviderMetaSchema, nil
MetaSchemaMethod: func(_ context.Context, _ provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
resp.Schema = testProviderMetaSchema
},
},
},
Expand Down Expand Up @@ -1015,8 +1014,8 @@ func TestServerApplyResourceChange(t *testing.T) {
}
},
},
GetMetaSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) {
return testProviderMetaSchema, nil
MetaSchemaMethod: func(_ context.Context, _ provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
resp.Schema = testProviderMetaSchema
},
},
},
Expand Down
15 changes: 6 additions & 9 deletions internal/proto5server/server_getproviderschema_test.go
Expand Up @@ -8,16 +8,14 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/datasource"
datasourceschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
providerschema "github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/resource"
resourceschema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tftypes"
"github.com/hashicorp/terraform-plugin-log/tfsdklogtest"
Expand Down Expand Up @@ -253,15 +251,14 @@ func TestServerGetProviderSchema(t *testing.T) {
FrameworkServer: fwserver.Server{
Provider: &testprovider.ProviderWithMetaSchema{
Provider: &testprovider.Provider{},
GetMetaSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) {
return tfsdk.Schema{
Attributes: map[string]tfsdk.Attribute{
"test": {
MetaSchemaMethod: func(_ context.Context, _ provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
resp.Schema = metaschema.Schema{
Attributes: map[string]metaschema.Attribute{
"test": metaschema.StringAttribute{
Required: true,
Type: types.StringType,
},
},
}, nil
}
},
},
},
Expand Down
23 changes: 11 additions & 12 deletions internal/proto5server/server_planresourcechange_test.go
Expand Up @@ -5,13 +5,13 @@ import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tftypes"
Expand Down Expand Up @@ -55,11 +55,10 @@ func TestServerPlanResourceChange(t *testing.T) {
"test_provider_meta_attribute": tftypes.NewValue(tftypes.String, "test-provider-meta-value"),
})

testProviderMetaSchema := tfsdk.Schema{
Attributes: map[string]tfsdk.Attribute{
"test_provider_meta_attribute": {
testProviderMetaSchema := metaschema.Schema{
Attributes: map[string]metaschema.Attribute{
"test_provider_meta_attribute": metaschema.StringAttribute{
Optional: true,
Type: types.StringType,
},
},
}
Expand Down Expand Up @@ -207,8 +206,8 @@ func TestServerPlanResourceChange(t *testing.T) {
}
},
},
GetMetaSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) {
return testProviderMetaSchema, nil
MetaSchemaMethod: func(_ context.Context, _ provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
resp.Schema = testProviderMetaSchema
},
},
},
Expand Down Expand Up @@ -473,8 +472,8 @@ func TestServerPlanResourceChange(t *testing.T) {
}
},
},
GetMetaSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) {
return testProviderMetaSchema, nil
MetaSchemaMethod: func(_ context.Context, _ provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
resp.Schema = testProviderMetaSchema
},
},
},
Expand Down Expand Up @@ -836,8 +835,8 @@ func TestServerPlanResourceChange(t *testing.T) {
}
},
},
GetMetaSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) {
return testProviderMetaSchema, nil
MetaSchemaMethod: func(_ context.Context, _ provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
resp.Schema = testProviderMetaSchema
},
},
},
Expand Down
37 changes: 24 additions & 13 deletions internal/proto5server/server_readdatasource_test.go
Expand Up @@ -7,10 +7,10 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tftypes"
Expand All @@ -33,6 +33,19 @@ func TestServerReadDataSource(t *testing.T) {

testEmptyDynamicValue := testNewDynamicValue(t, tftypes.Object{}, nil)

testProviderMetaDynamicValue := testNewDynamicValue(t,
tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"test_optional": tftypes.String,
"test_required": tftypes.String,
},
},
map[string]tftypes.Value{
"test_optional": tftypes.NewValue(tftypes.String, nil),
"test_required": tftypes.NewValue(tftypes.String, "test-config-value"),
},
)

testStateDynamicValue := testNewDynamicValue(t, testType, map[string]tftypes.Value{
"test_computed": tftypes.NewValue(tftypes.String, "test-state-value"),
"test_required": tftypes.NewValue(tftypes.String, "test-config-value"),
Expand Down Expand Up @@ -142,7 +155,7 @@ func TestServerReadDataSource(t *testing.T) {
},
ReadMethod: func(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var config struct {
TestComputed types.String `tfsdk:"test_computed"`
TestOptional types.String `tfsdk:"test_optional"`
TestRequired types.String `tfsdk:"test_required"`
}

Expand All @@ -157,26 +170,24 @@ func TestServerReadDataSource(t *testing.T) {
}
},
},
GetMetaSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) {
return tfsdk.Schema{
Attributes: map[string]tfsdk.Attribute{
"test_computed": {
Computed: true,
Type: types.StringType,
MetaSchemaMethod: func(_ context.Context, _ provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
resp.Schema = metaschema.Schema{
Attributes: map[string]metaschema.Attribute{
"test_optional": metaschema.StringAttribute{
Optional: true,
},
"test_required": {
"test_required": metaschema.StringAttribute{
Required: true,
Type: types.StringType,
},
},
}, nil
}
},
},
},
},
request: &tfprotov5.ReadDataSourceRequest{
Config: testEmptyDynamicValue,
ProviderMeta: testConfigDynamicValue,
ProviderMeta: testProviderMetaDynamicValue,
TypeName: "test_data_source",
},
expectedResponse: &tfprotov5.ReadDataSourceResponse{
Expand Down

0 comments on commit 9353b7c

Please sign in to comment.