diff --git a/docs/index.md b/docs/index.md index 21e1ab0..8eecef4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -23,4 +23,4 @@ provider "scaffolding" { ### Optional -- `example` (String) Example provider attribute +- `endpoint` (String) Example provider attribute diff --git a/go.mod b/go.mod index 2dab160..858f6d1 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/hashicorp/terraform-plugin-docs v0.13.0 - github.com/hashicorp/terraform-plugin-framework v0.11.1 + github.com/hashicorp/terraform-plugin-framework v0.12.0 github.com/hashicorp/terraform-plugin-go v0.14.0 github.com/hashicorp/terraform-plugin-log v0.7.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.22.0 @@ -21,7 +21,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.8 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect diff --git a/go.sum b/go.sum index 4457a64..56be098 100644 --- a/go.sum +++ b/go.sum @@ -94,8 +94,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= @@ -138,8 +139,8 @@ github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= github.com/hashicorp/terraform-plugin-docs v0.13.0 h1:6e+VIWsVGb6jYJewfzq2ok2smPzZrt1Wlm9koLeKazY= github.com/hashicorp/terraform-plugin-docs v0.13.0/go.mod h1:W0oCmHAjIlTHBbvtppWHe8fLfZ2BznQbuv8+UD8OucQ= -github.com/hashicorp/terraform-plugin-framework v0.11.1 h1:rq8f+TLDO4tJu+n9mMYlDrcRoIdrg0gTUvV2Jr0Ya24= -github.com/hashicorp/terraform-plugin-framework v0.11.1/go.mod h1:GENReHOz6GEt8Jk3UN94vk8BdC6irEHFgN3Z9HPhPUU= +github.com/hashicorp/terraform-plugin-framework v0.12.0 h1:Bk3l5MQUaZoo5eplr+u1FomYqGS564e8Tp3rutnCfYg= +github.com/hashicorp/terraform-plugin-framework v0.12.0/go.mod h1:wcZdk4+Uef6Ng+BiBJjGAcIPlIs5bhlEV/TA1k6Xkq8= github.com/hashicorp/terraform-plugin-go v0.14.0 h1:ttnSlS8bz3ZPYbMb84DpcPhY4F5DsQtcAS7cHo8uvP4= github.com/hashicorp/terraform-plugin-go v0.14.0/go.mod h1:2nNCBeRLaenyQEi78xrGrs9hMbulveqG/zDMQSvVJTE= github.com/hashicorp/terraform-plugin-log v0.7.0 h1:SDxJUyT8TwN4l5b5/VkiTIaQgY6R+Y2BQ0sRZftGKQs= diff --git a/internal/provider/example_data_source.go b/internal/provider/example_data_source.go index daa2080..49ff1ee 100644 --- a/internal/provider/example_data_source.go +++ b/internal/provider/example_data_source.go @@ -2,22 +2,39 @@ package provider import ( "context" + "fmt" + "net/http" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" ) // Ensure provider defined types fully satisfy framework interfaces -var _ provider.DataSourceType = exampleDataSourceType{} -var _ datasource.DataSource = exampleDataSource{} +var _ datasource.DataSource = &ExampleDataSource{} -type exampleDataSourceType struct{} +func NewExampleDataSource() datasource.DataSource { + return &ExampleDataSource{} +} -func (t exampleDataSourceType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { +// ExampleDataSource defines the data source implementation. +type ExampleDataSource struct { + client *http.Client +} + +// ExampleDataSourceModel describes the data source data model. +type ExampleDataSourceModel struct { + ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` + Id types.String `tfsdk:"id"` +} + +func (d *ExampleDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_example" +} + +func (d *ExampleDataSource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ // This description is used by the documentation generator and the language server. MarkdownDescription: "Example data source", @@ -37,28 +54,31 @@ func (t exampleDataSourceType) GetSchema(ctx context.Context) (tfsdk.Schema, dia }, nil } -func (t exampleDataSourceType) NewDataSource(ctx context.Context, in provider.Provider) (datasource.DataSource, diag.Diagnostics) { - provider, diags := convertProviderType(in) +func (d *ExampleDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } - return exampleDataSource{ - provider: provider, - }, diags + client, ok := req.ProviderData.(*http.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client } -type exampleDataSourceData struct { - ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` - Id types.String `tfsdk:"id"` -} +func (d *ExampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data ExampleDataSourceModel -type exampleDataSource struct { - provider scaffoldingProvider -} - -func (d exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var data exampleDataSourceData - - diags := req.Config.Get(ctx, &data) - resp.Diagnostics.Append(diags...) + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return @@ -66,7 +86,7 @@ func (d exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, // If applicable, this is a great opportunity to initialize any necessary // provider client data and make a call using it. - // example, err := d.provider.client.ReadExample(...) + // httpResp, err := d.client.Do(httpReq) // if err != nil { // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read example, got error: %s", err)) // return @@ -80,6 +100,6 @@ func (d exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, // Documentation: https://terraform.io/plugin/log tflog.Trace(ctx, "read a data source") - diags = resp.State.Set(ctx, &data) - resp.Diagnostics.Append(diags...) + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } diff --git a/internal/provider/example_resource.go b/internal/provider/example_resource.go index cebc6f0..1ee0230 100644 --- a/internal/provider/example_resource.go +++ b/internal/provider/example_resource.go @@ -2,10 +2,11 @@ package provider import ( "context" + "fmt" + "net/http" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" @@ -13,13 +14,29 @@ import ( ) // Ensure provider defined types fully satisfy framework interfaces -var _ provider.ResourceType = exampleResourceType{} -var _ resource.Resource = exampleResource{} -var _ resource.ResourceWithImportState = exampleResource{} +var _ resource.Resource = &ExampleResource{} +var _ resource.ResourceWithImportState = &ExampleResource{} -type exampleResourceType struct{} +func NewExampleResource() resource.Resource { + return &ExampleResource{} +} -func (t exampleResourceType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { +// ExampleResource defines the resource implementation. +type ExampleResource struct { + client *http.Client +} + +// ExampleResourceModel describes the resource data model. +type ExampleResourceModel struct { + ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` + Id types.String `tfsdk:"id"` +} + +func (r *ExampleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_example" +} + +func (r *ExampleResource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ // This description is used by the documentation generator and the language server. MarkdownDescription: "Example resource", @@ -42,28 +59,31 @@ func (t exampleResourceType) GetSchema(ctx context.Context) (tfsdk.Schema, diag. }, nil } -func (t exampleResourceType) NewResource(ctx context.Context, in provider.Provider) (resource.Resource, diag.Diagnostics) { - provider, diags := convertProviderType(in) +func (r *ExampleResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } - return exampleResource{ - provider: provider, - }, diags + client, ok := req.ProviderData.(*http.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client } -type exampleResourceData struct { - ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` - Id types.String `tfsdk:"id"` -} +func (r *ExampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *ExampleResourceModel -type exampleResource struct { - provider scaffoldingProvider -} - -func (r exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var data exampleResourceData - - diags := req.Config.Get(ctx, &data) - resp.Diagnostics.Append(diags...) + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return @@ -71,7 +91,7 @@ func (r exampleResource) Create(ctx context.Context, req resource.CreateRequest, // If applicable, this is a great opportunity to initialize any necessary // provider client data and make a call using it. - // example, err := d.provider.client.CreateExample(...) + // httpResp, err := d.client.Do(httpReq) // if err != nil { // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create example, got error: %s", err)) // return @@ -85,15 +105,15 @@ func (r exampleResource) Create(ctx context.Context, req resource.CreateRequest, // Documentation: https://terraform.io/plugin/log tflog.Trace(ctx, "created a resource") - diags = resp.State.Set(ctx, &data) - resp.Diagnostics.Append(diags...) + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func (r exampleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data exampleResourceData +func (r *ExampleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *ExampleResourceModel - diags := req.State.Get(ctx, &data) - resp.Diagnostics.Append(diags...) + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return @@ -101,21 +121,21 @@ func (r exampleResource) Read(ctx context.Context, req resource.ReadRequest, res // If applicable, this is a great opportunity to initialize any necessary // provider client data and make a call using it. - // example, err := d.provider.client.ReadExample(...) + // httpResp, err := d.client.Do(httpReq) // if err != nil { // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read example, got error: %s", err)) // return // } - diags = resp.State.Set(ctx, &data) - resp.Diagnostics.Append(diags...) + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func (r exampleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var data exampleResourceData +func (r *ExampleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *ExampleResourceModel - diags := req.Plan.Get(ctx, &data) - resp.Diagnostics.Append(diags...) + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return @@ -123,21 +143,21 @@ func (r exampleResource) Update(ctx context.Context, req resource.UpdateRequest, // If applicable, this is a great opportunity to initialize any necessary // provider client data and make a call using it. - // example, err := d.provider.client.UpdateExample(...) + // httpResp, err := d.client.Do(httpReq) // if err != nil { // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update example, got error: %s", err)) // return // } - diags = resp.State.Set(ctx, &data) - resp.Diagnostics.Append(diags...) + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func (r exampleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var data exampleResourceData +func (r *ExampleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *ExampleResourceModel - diags := req.State.Get(ctx, &data) - resp.Diagnostics.Append(diags...) + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return @@ -145,13 +165,13 @@ func (r exampleResource) Delete(ctx context.Context, req resource.DeleteRequest, // If applicable, this is a great opportunity to initialize any necessary // provider client data and make a call using it. - // example, err := d.provider.client.DeleteExample(...) + // httpResp, err := d.client.Do(httpReq) // if err != nil { // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete example, got error: %s", err)) // return // } } -func (r exampleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { +func (r *ExampleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 2ad2bf9..91ab182 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -2,77 +2,42 @@ package provider import ( "context" - "fmt" + "net/http" + "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" ) -// Ensure provider defined types fully satisfy framework interfaces -var _ provider.Provider = &scaffoldingProvider{} - -// provider satisfies the tfsdk.Provider interface and usually is included -// with all Resource and DataSource implementations. -type scaffoldingProvider struct { - // client can contain the upstream provider SDK or HTTP client used to - // communicate with the upstream service. Resource and DataSource - // implementations can then make calls using this client. - // - // TODO: If appropriate, implement upstream provider SDK or HTTP client. - // client vendorsdk.ExampleClient - - // configured is set to true at the end of the Configure method. - // This can be used in Resource and DataSource implementations to verify - // that the provider was previously configured. - configured bool +// Ensure ScaffoldingProvider satisfies various provider interfaces. +var _ provider.Provider = &ScaffoldingProvider{} +var _ provider.ProviderWithMetadata = &ScaffoldingProvider{} +// ScaffoldingProvider defines the provider implementation. +type ScaffoldingProvider struct { // version is set to the provider version on release, "dev" when the // provider is built and ran locally, and "test" when running acceptance // testing. version string } -// providerData can be used to store data from the Terraform configuration. -type providerData struct { - Example types.String `tfsdk:"example"` +// ScaffoldingProviderModel describes the provider data model. +type ScaffoldingProviderModel struct { + Endpoint types.String `tfsdk:"endpoint"` } -func (p *scaffoldingProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { - var data providerData - diags := req.Config.Get(ctx, &data) - resp.Diagnostics.Append(diags...) - - if resp.Diagnostics.HasError() { - return - } - - // Configuration values are now available. - // if data.Example.Null { /* ... */ } - - // If the upstream provider SDK or HTTP client requires configuration, such - // as authentication or logging, this is a great opportunity to do so. - - p.configured = true +func (p *ScaffoldingProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "scaffolding" + resp.Version = p.version } -func (p *scaffoldingProvider) GetResources(ctx context.Context) (map[string]provider.ResourceType, diag.Diagnostics) { - return map[string]provider.ResourceType{ - "scaffolding_example": exampleResourceType{}, - }, nil -} - -func (p *scaffoldingProvider) GetDataSources(ctx context.Context) (map[string]provider.DataSourceType, diag.Diagnostics) { - return map[string]provider.DataSourceType{ - "scaffolding_example": exampleDataSourceType{}, - }, nil -} - -func (p *scaffoldingProvider) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { +func (p *ScaffoldingProvider) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ Attributes: map[string]tfsdk.Attribute{ - "example": { + "endpoint": { MarkdownDescription: "Example provider attribute", Optional: true, Type: types.StringType, @@ -81,39 +46,40 @@ func (p *scaffoldingProvider) GetSchema(ctx context.Context) (tfsdk.Schema, diag }, nil } +func (p *ScaffoldingProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + var data ScaffoldingProviderModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Configuration values are now available. + // if data.Endpoint.IsNull() { /* ... */ } + + // Example client configuration for data sources and resources + client := http.DefaultClient + resp.DataSourceData = client + resp.ResourceData = client +} + +func (p *ScaffoldingProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + NewExampleResource, + } +} + +func (p *ScaffoldingProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewExampleDataSource, + } +} + func New(version string) func() provider.Provider { return func() provider.Provider { - return &scaffoldingProvider{ + return &ScaffoldingProvider{ version: version, } } } - -// convertProviderType is a helper function for NewResource and NewDataSource -// implementations to associate the concrete provider type. Alternatively, -// this helper can be skipped and the provider type can be directly type -// asserted (e.g. provider: in.(*scaffoldingProvider)), however using this can prevent -// potential panics. -func convertProviderType(in provider.Provider) (scaffoldingProvider, diag.Diagnostics) { - var diags diag.Diagnostics - - p, ok := in.(*scaffoldingProvider) - - if !ok { - diags.AddError( - "Unexpected Provider Instance Type", - fmt.Sprintf("While creating the data source or resource, an unexpected provider type (%T) was received. This is always a bug in the provider code and should be reported to the provider developers.", p), - ) - return scaffoldingProvider{}, diags - } - - if p == nil { - diags.AddError( - "Unexpected Provider Instance Type", - "While creating the data source or resource, an unexpected empty provider instance was received. This is always a bug in the provider code and should be reported to the provider developers.", - ) - return scaffoldingProvider{}, diags - } - - return *p, diags -}