-
Notifications
You must be signed in to change notification settings - Fork 643
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
provider: add example framework provider, resource and data source
- Loading branch information
1 parent
9e6b224
commit c15d540
Showing
7 changed files
with
580 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package framework | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/cloudflare/cloudflare-go" | ||
"github.com/hashicorp/terraform-plugin-log/tflog" | ||
) | ||
|
||
type Config struct { | ||
Email string | ||
APIKey string | ||
APIUserServiceKey string | ||
APIToken string | ||
Options []cloudflare.Option | ||
} | ||
|
||
func (c *Config) Client() (*cloudflare.API, error) { | ||
var err error | ||
var client *cloudflare.API | ||
ctx := context.Background() | ||
|
||
if c.APIUserServiceKey != "" { | ||
client, err = cloudflare.NewWithUserServiceKey(c.APIUserServiceKey, c.Options...) | ||
} else if c.APIToken != "" { | ||
client, err = cloudflare.NewWithAPIToken(c.APIToken, c.Options...) | ||
} else if c.APIKey != "" { | ||
client, err = cloudflare.New(c.APIKey, c.Email, c.Options...) | ||
} else { | ||
return nil, errors.New("no credentials detected") | ||
} | ||
|
||
if err != nil { | ||
return nil, fmt.Errorf("error creating new Cloudflare client: %w", err) | ||
} | ||
|
||
tflog.Info(ctx, fmt.Sprintf("cloudflare Client configured for user: %s", c.Email)) | ||
return client, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package framework | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/datasource" | ||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
"github.com/hashicorp/terraform-plugin-log/tflog" | ||
) | ||
|
||
// Ensure provider defined types fully satisfy framework interfaces | ||
var _ datasource.DataSource = &ExampleDataSource{} | ||
|
||
func NewExampleDataSource() datasource.DataSource { | ||
return &ExampleDataSource{} | ||
} | ||
|
||
// 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) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { | ||
resp.Schema = schema.Schema{ | ||
// This description is used by the documentation generator and the language server. | ||
MarkdownDescription: "Example data source", | ||
|
||
Attributes: map[string]schema.Attribute{ | ||
"configurable_attribute": schema.StringAttribute{ | ||
MarkdownDescription: "Example configurable attribute", | ||
Optional: true, | ||
}, | ||
"id": schema.StringAttribute{ | ||
MarkdownDescription: "Example identifier", | ||
Computed: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
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 | ||
} | ||
|
||
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 | ||
} | ||
|
||
func (d *ExampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { | ||
var data ExampleDataSourceModel | ||
|
||
// Read Terraform configuration data into the model | ||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) | ||
|
||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
// If applicable, this is a great opportunity to initialize any necessary | ||
// provider client data and make a call using it. | ||
// 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 | ||
// } | ||
|
||
// For the purposes of this example code, hardcoding a response value to | ||
// save into the Terraform state. | ||
data.Id = types.StringValue("example-id") | ||
|
||
// Write logs using the tflog package | ||
// Documentation: https://terraform.io/plugin/log | ||
tflog.Trace(ctx, "read a data source") | ||
|
||
// Save data into Terraform state | ||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package framework | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
) | ||
|
||
func TestAccExampleDataSource(t *testing.T) { | ||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, | ||
Steps: []resource.TestStep{ | ||
// Read testing | ||
{ | ||
Config: testAccExampleDataSourceConfig, | ||
Check: resource.ComposeAggregateTestCheckFunc( | ||
resource.TestCheckResourceAttr("data.scaffolding_example.test", "id", "example-id"), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
const testAccExampleDataSourceConfig = ` | ||
data "scaffolding_example" "test" { | ||
configurable_attribute = "example" | ||
} | ||
` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
package framework | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/cloudflare/cloudflare-go" | ||
"github.com/hashicorp/terraform-plugin-framework/datasource" | ||
"github.com/hashicorp/terraform-plugin-framework/provider" | ||
"github.com/hashicorp/terraform-plugin-framework/provider/schema" | ||
"github.com/hashicorp/terraform-plugin-framework/resource" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
) | ||
|
||
// Ensure CloudflareProvider satisfies various provider interfaces. | ||
var _ provider.Provider = &CloudflareProvider{} | ||
|
||
// CloudflareProvider defines the provider implementation. | ||
type CloudflareProvider 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 | ||
} | ||
|
||
// CloudflareProviderModel describes the provider data model. | ||
type CloudflareProviderModel struct { | ||
APIKey types.String `tfsdk:"api_key"` | ||
APIUserServiceKey types.String `tfsdk:"api_user_service_key"` | ||
Email types.String `tfsdk:"email"` | ||
MinBackOff types.Int64 `tfsdk:"min_backoff"` | ||
RPS types.Int64 `tfsdk:"rps"` | ||
AccountID types.String `tfsdk:"account_id"` | ||
APIBasePath types.String `tfsdk:"api_base_path"` | ||
APIToken types.String `tfsdk:"api_token"` | ||
Retries types.Int64 `tfsdk:"retries"` | ||
MaxBackoff types.Int64 `tfsdk:"max_backoff"` | ||
APIClientLogging types.Bool `tfsdk:"api_client_logging"` | ||
APIHostname types.String `tfsdk:"api_hostname"` | ||
} | ||
|
||
func (p *CloudflareProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { | ||
resp.TypeName = "cloudflare" | ||
resp.Version = p.version | ||
} | ||
|
||
func (p *CloudflareProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { | ||
resp.Schema = schema.Schema{ | ||
Attributes: map[string]schema.Attribute{ | ||
"email": schema.StringAttribute{ | ||
Optional: true, | ||
MarkdownDescription: "A registered Cloudflare email address. Alternatively, can be configured using the `CLOUDFLARE_EMAIL` environment variable.", | ||
}, | ||
|
||
"api_key": schema.StringAttribute{ | ||
Optional: true, | ||
MarkdownDescription: "The API key for operations. Alternatively, can be configured using the `CLOUDFLARE_API_KEY` environment variable. API keys are [now considered legacy by Cloudflare](https://developers.cloudflare.com/api/keys/#limitations), API tokens should be used instead.", | ||
}, | ||
|
||
"api_token": schema.StringAttribute{ | ||
Optional: true, | ||
MarkdownDescription: "The API Token for operations. Alternatively, can be configured using the `CLOUDFLARE_API_TOKEN` environment variable.", | ||
}, | ||
|
||
"api_user_service_key": schema.StringAttribute{ | ||
Optional: true, | ||
MarkdownDescription: "A special Cloudflare API key good for a restricted set of endpoints. Alternatively, can be configured using the `CLOUDFLARE_API_USER_SERVICE_KEY` environment variable.", | ||
}, | ||
|
||
"rps": schema.Int64Attribute{ | ||
Optional: true, | ||
MarkdownDescription: "RPS limit to apply when making calls to the API. Alternatively, can be configured using the `CLOUDFLARE_RPS` environment variable.", | ||
}, | ||
|
||
"retries": schema.Int64Attribute{ | ||
Optional: true, | ||
MarkdownDescription: "Maximum number of retries to perform when an API request fails. Alternatively, can be configured using the `CLOUDFLARE_RETRIES` environment variable.", | ||
}, | ||
|
||
"min_backoff": schema.Int64Attribute{ | ||
Optional: true, | ||
MarkdownDescription: "Minimum backoff period in seconds after failed API calls. Alternatively, can be configured using the `CLOUDFLARE_MIN_BACKOFF` environment variable.", | ||
}, | ||
|
||
"max_backoff": schema.Int64Attribute{ | ||
Optional: true, | ||
MarkdownDescription: "Maximum backoff period in seconds after failed API calls. Alternatively, can be configured using the `CLOUDFLARE_MAX_BACKOFF` environment variable.", | ||
}, | ||
|
||
"api_client_logging": schema.BoolAttribute{ | ||
Optional: true, | ||
MarkdownDescription: "Whether to print logs from the API client (using the default log library logger). Alternatively, can be configured using the `CLOUDFLARE_API_CLIENT_LOGGING` environment variable.", | ||
}, | ||
|
||
"account_id": schema.StringAttribute{ | ||
Optional: true, | ||
MarkdownDescription: "Configure API client to always use a specific account. Alternatively, can be configured using the `CLOUDFLARE_ACCOUNT_ID` environment variable.", | ||
DeprecationMessage: "Use resource specific `account_id` attributes instead.", | ||
}, | ||
|
||
"api_hostname": schema.StringAttribute{ | ||
Optional: true, | ||
MarkdownDescription: "Configure the hostname used by the API client. Alternatively, can be configured using the `CLOUDFLARE_API_HOSTNAME` environment variable.", | ||
}, | ||
|
||
"api_base_path": schema.StringAttribute{ | ||
Optional: true, | ||
MarkdownDescription: "Configure the base path used by the API client. Alternatively, can be configured using the `CLOUDFLARE_API_BASE_PATH` environment variable.", | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func (p *CloudflareProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { | ||
var data CloudflareProviderModel | ||
|
||
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 | ||
options := []cloudflare.Option{} | ||
|
||
options = append(options, cloudflare.BaseURL("https://api.cloudflare.com/client/v4")) | ||
config := Config{Options: options} | ||
|
||
client, _ := config.Client() | ||
// if err != nil { | ||
// resp.Diagnostics.Append(err...) | ||
// return | ||
// } | ||
resp.DataSourceData = client | ||
resp.ResourceData = client | ||
} | ||
|
||
func (p *CloudflareProvider) Resources(ctx context.Context) []func() resource.Resource { | ||
return []func() resource.Resource{ | ||
NewExampleResource, | ||
} | ||
} | ||
|
||
func (p *CloudflareProvider) DataSources(ctx context.Context) []func() datasource.DataSource { | ||
return []func() datasource.DataSource{ | ||
NewExampleDataSource, | ||
} | ||
} | ||
|
||
func New(version string) func() provider.Provider { | ||
return func() provider.Provider { | ||
return &CloudflareProvider{ | ||
version: version, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package framework | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/providerserver" | ||
"github.com/hashicorp/terraform-plugin-go/tfprotov6" | ||
) | ||
|
||
// testAccProtoV6ProviderFactories are used to instantiate a provider during | ||
// acceptance testing. The factory function will be invoked for every Terraform | ||
// CLI command executed to create a provider server to which the CLI can | ||
// reattach. | ||
var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ | ||
"scaffolding": providerserver.NewProtocol6WithError(New("test")()), | ||
} | ||
|
||
func testAccPreCheck(t *testing.T) { | ||
// You can add code here to run prior to any test case execution, for example assertions | ||
// about the appropriate environment variables being set are common to see in a pre-check | ||
// function. | ||
} |
Oops, something went wrong.