Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider: Deprecate DataSourceType, ResourceType, Provider type GetDataSources, and Provider type GetResources #472

Merged
merged 12 commits into from Sep 12, 2022

Conversation

bflad
Copy link
Member

@bflad bflad commented Sep 8, 2022

Reference: #441
Reference: #442

The goal of this change is twofold:

  • Deprecate the separate DataSourceType and ResourceType types.
  • Enable DataSource and Resource to be self-documenting with respects to their type name.

Removing DataSourceType and ResourceType simplifies provider implementations and gives developers the choice of whether or not to configure extra data in their DataSource and Resource implementations by adding the optional Configure method. This data is no longer required to be the provider.Provider itself, but rather can be any type such as a provider-defined structure or vendor-supplied client type.

Enabling DataSource and Resource type name handling within the implementation allows provider developers to further split out their data source and resource code across package boundaries without repeated map addition logic.

Given this framework version 0.11.1 code (which generally is split across Go files/packages, but shown altogether here for brevity):

var _ provider.Provider = &ExampleProvider{}

type ExampleProvider struct{
  configured bool
  // ... additional remote client fields, etc.
}

func (p ExampleProvider) GetDataSources(ctx context.Context) (map[string]provider.DataSourceType, diag.Diagnostics) {
	return map[string]provider.DataSourceType{
		"example_thing": ThingDataSourceType{},
	}, nil
}

func (p ExampleProvider) GetResources(ctx context.Context) (map[string]provider.ResourceType, diag.Diagnostics) {
	return map[string]provider.ResourceType{
		"example_thing": ThingResourceType{},
	}, nil
}

func (p ExampleProvider) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { /* ... */ }

func (p *ExampleProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
  // ... remote client setup, etc.
  p.configured = true
}

var _ provider.DataSourceType = ThingDataSourceType{}
var _ datasource.DataSource = ThingDataSource{}

type ThingDataSourceType struct{}

type ThingDataSource struct{
  provider ExampleProvider
}

func (t ThingDataSourceType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { /* ... */ }

func (t ThingDataSourceType) NewDataSource(ctx context.Context, p provider.Provider) (datasource.DataSource, diag.Diagnostics) {
  return ThingDataSource{
    provider: p.(ExampleProvider), // unchecked type assertion shown for brevity
  }
}

func (d ThingDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
  // e.g. d.provider.Client.ReadThing()
}

var _ provider.ResourceType = ThingResourceType{}
var _ resource.Resource = ThingResource{}

type ThingResourceType struct{}

type ThingResource struct{
  provider ExampleProvider
}

func (t ThingResourceType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { /* ... */ }

func (t ThingResourceType) NewResource(ctx context.Context, p provider.Provider) (resource.Resource, diag.Diagnostics) {
  return ThingResource{
    provider: p.(ExampleProvider), // unchecked type assertion shown for brevity
  }
}

func (r ThingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
  // e.g. r.provider.Client.CreateThing()
}

func (r ThingResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
  // e.g. r.provider.Client.DeleteThing()
}

func (r ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
  // e.g. r.provider.Client.ReadThing()
}

func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
  // e.g. r.provider.Client.UpdateThing()
}

Could be migrated as:

type ExampleClient struct {
  // client/data fields OR just use the vendor client type directly instead of this new type
}

var _ provider.Provider = &ExampleProvider{}

type ExampleProvider struct{
  // setting client, etc. fields here is now completely optional,
  // if you want to configure data sources and resource with _this_ type
}

func (p ExampleProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
  // make available in DataSource and Resource type TypeName methods
  resp.TypeName = "example"
}

func (p ExampleProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
  return []func() datasource.DataSource{
		NewThingDataSource,
	}
}

func (p ExampleProvider) Resources(ctx context.Context) []func() resource.Resource {
  return []func() resource.Resource{
		NewThingResource,
	}
}

func (p ExampleProvider) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { /* ... */ }

func (p *ExampleProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
  client := &ExampleClient{} // OR existing vendor type OR p itself
  resp.DataSourceData = client
  resp.ResourceData = client
}

var _ datasource.DataSource = &ThingDataSource{}

func NewThingDataSource() datasource.DataSource {
  return &ThingDataSource{}
}

type ThingDataSource struct{
  client *ExampleClient
}

func (d *ThingDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
  resp.TypeName = req.ProviderTypeName + "_thing"
}

func (d *ThingDataSource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { /* ... */ }

func (d *ThingDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse)  {
  d.client = req.ProviderData.(*ExampleClient) // unchecked type assertion shown for brevity
}

func (d *ThingDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
  // e.g. d.client.ReadThing()
}

var _ resource.Resource = &ThingResource{}

func NewThingResource() resource.Resource {
  return &ThingResource{}
}

type ThingResource struct{
  client *ExampleClient
}

func (r *ThingResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
  resp.TypeName = req.ProviderTypeName + "_thing"
}

func (r *ThingResource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { /* ... */ }

func (r *ThingResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
  r.client = req.ProviderData.(*ExampleClient) // unchecked type assertion shown for brevity
}

func (r *ThingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
  // e.g. r.client.CreateThing()
}

func (r *ThingResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
  // e.g. r.client.DeleteThing()
}

func (r *ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
  // e.g. r.client.ReadThing()
}

func (r *ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
  // e.g. r.client.UpdateThing()
}

Some additional implementation notes:

  • The new Provider type Metadata method is called at the beginning of the GetProviderSchema RPC and populates the provider type name for data source and resource type name requests.
  • The framework server will automatically validate against duplicate type names, missing type names, and missing schemas when using the new Provider type DataSources and Resources methods.
  • The new DataSource and Resource type Configure methods are automatically called where the framework server previously called the DataSourceType type NewDataSource method and ResourceType type NewResource method.

… `GetDataSources`, and `Provider` type `GetResources`

Reference: #441
Reference: #442

The goal of this change is twofold:

- Deprecate the separate `DataSourceType` and `ResourceType` types.
- Enable `DataSource` and `Resource` to be self-documenting with respects to their type name.

Removing `DataSourceType` and `ResourceType` simplifies provider implementations and gives developers the choice of whether or not to configure extra data in their `DataSource` and `Resource` implementations by adding the optional `Configure` method. This data is no longer required to be the `provider.Provider` itself, but rather can be any type such as a provider-defined structure or vendor-supplied client type.

Enabling `DataSource` and `Resource` type name handling within the implementation allows provider developers to further split out their data source and resource code across package boundaries without repeated map addition logic.

Given this framework version 0.11.1 code (which generally is split across Go files/packages, but shown altogether here for brevity):

```go
var _ provider.Provider = &ExampleProvider{}

type ExampleProvider struct{
  configured bool
  // ... additional remote client fields, etc.
}

func (p ExampleProvider) GetDataSources(ctx context.Context) (map[string]provider.DataSourceType, diag.Diagnostics) {
	return map[string]provider.DataSourceType{
		"example_thing": ThingDataSourceType{},
	}, nil
}

func (p ExampleProvider) GetResources(ctx context.Context) (map[string]provider.ResourceType, diag.Diagnostics) {
	return map[string]provider.ResourceType{
		"example_thing": ThingResourceType{},
	}, nil
}

func (p ExampleProvider) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { /* ... */ }

func (p *ExampleProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
  // ... remote client setup, etc.
  p.configured = true
}

var _ provider.DataSourceType = ThingDataSourceType{}
var _ datasource.DataSource = ThingDataSource{}

type ThingDataSourceType struct{}

type ThingDataSource struct{
  provider ExampleProvider
}

func (t ThingDataSourceType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { /* ... */ }

func (t ThingDataSourceType) NewDataSource(ctx context.Context, p provider.Provider) (datasource.DataSource, diag.Diagnostics) {
  return ThingDataSource{
    provider: p.(ExampleProvider), // unchecked type assertion shown for brevity
  }
}

func (d ThingDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
  // e.g. d.provider.Client.ReadThing()
}

var _ provider.ResourceType = ThingResourceType{}
var _ resource.Resource = ThingResource{}

type ThingResourceType struct{}

type ThingResource struct{
  provider ExampleProvider
}

func (t ThingResourceType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { /* ... */ }

func (t ThingResourceType) NewResource(ctx context.Context, p provider.Provider) (resource.Resource, diag.Diagnostics) {
  return ThingResource{
    provider: p.(ExampleProvider), // unchecked type assertion shown for brevity
  }
}

func (r ThingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
  // e.g. r.provider.Client.CreateThing()
}

func (r ThingResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
  // e.g. r.provider.Client.DeleteThing()
}

func (r ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
  // e.g. r.provider.Client.ReadThing()
}

func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
  // e.g. r.provider.Client.UpdateThing()
}
```

Could be migrated as:

```go
type ExampleClient struct {
  // client/data fields OR just use the vendor client type directly instead of this new type
}

var _ provider.Provider = &ExampleProvider{}

type ExampleProvider struct{
  // setting client, etc. fields here is now completely optional,
  // if you want to configure data sources and resource with _this_ type
}

func (p ExampleProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
  // make available in DataSource and Resource type TypeName methods
  resp.TypeName = "example"
}

func (p ExampleProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
  return []func() datasource.DataSource{
		NewThingDataSource,
	}
}

func (p ExampleProvider) Resources(ctx context.Context) []func() resource.Resource {
  return []func() resource.Resource{
		NewThingResource,
	}
}

func (p ExampleProvider) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { /* ... */ }

func (p *ExampleProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
  client := &ExampleClient{} // OR existing vendor type OR p itself
  resp.DataSourceData = client
  resp.ResourceData = client
}

var _ datasource.DataSource = ThingDataSource{}

func NewThingDataSource() datasource.DataSource {
  return ThingDataSource{}
}

type ThingDataSource struct{
  client *ExampleClient
}

func (d ThingDataSource) TypeName(ctx context.Context, req datasource.TypeNameRequest, resp *datasource.TypeNameResponse) {
  resp.TypeName = req.ProviderTypeName + "_thing"
}

func (d ThingDataSource) GetSchema(ctx context.Context) (fwschema.Schema, diag.Diagnostics) { /* ... */ }

func (d ThingDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse)  {
  d.client = req.ProviderData.(*ExampleClient) // unchecked type assertion shown for brevity
}

func (d ThingDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
  // e.g. d.client.ReadThing()
}

var _ resource.Resource = ThingResource{}

func NewThingResource() resource.Resource {
  return ThingResource{}
}

type ThingResource struct{
  client *ExampleClient
}

func (d ThingResource) TypeName(ctx context.Context, req resource.TypeNameRequest, resp *resource.TypeNameResponse) {
  resp.TypeName = req.ProviderTypeName + "_thing"
}

func (r ThingResource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { /* ... */ }

func (r ThingResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
  r.client = req.ProviderConfigureData.(*ExampleClient) // unchecked type assertion shown for brevity
}

func (r ThingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
  // e.g. r.client.CreateThing()
}

func (r ThingResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
  // e.g. r.client.DeleteThing()
}

func (r ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
  // e.g. r.client.ReadThing()
}

func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
  // e.g. r.client.UpdateThing()
}
```

Some additional implementation notes:

- The new `Provider` type `Metadata` method is called at the beginning of the `GetProviderSchema` RPC and populates the provider type name for data source and resource type name requests.
- The framework server will automatically validate against duplicate type names, missing type names, and missing schemas when using the new `Provider` type `DataSources` and `Resources` methods.
- The new `DataSource` and `Resource` type `Configure` methods are automatically called where the framework server previously called the `DataSourceType` type `NewDataSource` method and `ResourceType` type `NewDataSource` method.
- The new `DataSource` and `Resource` type `TypeName` methods could potentially be renamed `Metadata` to match the `Provider` implementation and potentially help futureproof it, however it could be unclear to provider developers where to set the type name at first and there are no apparent use cases which would extend that type of metadata yet.
@bflad bflad added the enhancement New feature or request label Sep 8, 2022
@bflad bflad added this to the v0.12.0 milestone Sep 8, 2022
@bflad bflad marked this pull request as ready for review September 9, 2022 03:07
@bflad bflad requested a review from a team as a code owner September 9, 2022 03:07
@bflad
Copy link
Member Author

bflad commented Sep 9, 2022

hashicorp/terraform-provider-scaffolding-framework#86 shows an example migration

Copy link
Contributor

@bendbennett bendbennett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

I read the description of the refactoring and the first couple of examples, but have to confess that I didn't look at every line.

bflad added a commit that referenced this pull request Sep 9, 2022
bflad added a commit that referenced this pull request Sep 9, 2022
.changelog/472.txt Outdated Show resolved Hide resolved
@bflad bflad merged commit 7541ab1 into main Sep 12, 2022
@bflad bflad deleted the bflad/remove-provider-resource-datasource-types branch September 12, 2022 15:14
bflad added a commit that referenced this pull request Sep 13, 2022
@github-actions
Copy link

I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active contributions.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 13, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants