I am developing a custom Terraform provider.
I have all of my custom logic for creating/reading/updating/deleting resources all done but I cannot get the provider to get configured.
I have cloned this repository as a base https://github.com/hashicorp/terraform-provider-scaffolding-framework/tree/main.
And if I debug the code in that repository and just add a line to the provider.go
file at the Configure
method of the hashicupsProvider
type so that I get a breakpoint inside that method, the breakpoint never hits.
I have the exact same "skeleton" for my own provider so I do not think it is necessary to provide the whole source for that since it does not work even from the HashiCorp provided example.
I have overriden the provider address/path with .terraformrc
and can successfully generate the schema for the provider with terraform providers schema -json
for example.
Is it something I have missed in the documentation that you must do for Terraform to recognise that it should call the Configure
method for example?
Edit: Adding the code for the provider struct and partial interface implementation.
type myproviderProvider struct {
version string
}
func (p *myproviderProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) {
resp.TypeName = "myprovider"
resp.Version = p.version
}
func (p *myproviderProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
tflog.Debug(ctx, "Configure myprovider provider was called")
tflog.Info(ctx, "Creating myprovider client")
// And so on......
}
func (p *myproviderProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Interact with myprovider.",
Attributes: map[string]schema.Attribute{
"username": schema.StringAttribute{
Description: "Username for myapp API. May also be provided via myprovider_USER environment variable.",
Optional: true,
},
"password": schema.StringAttribute{
Description: "Password for myapp API. May also be provided via myprovider_PW environment variable.",
Optional: true,
},
"api_url": schema.StringAttribute{
Description: "Url for myapp API. May also be provided via myprovider_URL environment variable.",
Optional: true,
Sensitive: true,
},
"domain_id": schema.StringAttribute{
Description: "DomainId for myapp API. May also be provided via myprovider_DOMAIN_ID environment variable.",
Optional: true,
Sensitive: true,
},
},
}
}
And this is the function that is used to return the provider to the Serve
method
func New(version string) func() provider.Provider {
return func() provider.Provider {
return &myproviderProvider{
version: version,
}
}
}
The Terraform code
terraform {
required_providers {
myprov = {
source = "hashicorp.com/myuser/myprov"
version = "0.1.0"
}
}
}
provider "myprov" {
username = "myuser"
password = "verysecret"
api_url = "https://someurl.com"
domain_id = "a-guid"
}
I had a problem like this, although it's not 100% clear if it's exactly this because of missing context. My problem was:
I created my own
provider.Provider
with aConfigure()
function. This reads the configuration, instanciates ahttp.Client
and set it for use with:resp.DataSourceData = client resp.ResourceData = client
I then created a
resource.Resource
which also has aConfigure()
method that uses this client via:client, ok := req.ProviderData.(*http.Client)
When planning/applying, the value in
req.ProviderData
would benil
and my resource would fail to create. I then went into theProvider.Configure()
method and tried placing logs viatflog.Info
- which never showed up in the output.
It turns out this is expected behavior - and your resource/data source must handle it gracefully. The Resource.Configure()
method can be called several times during an apply/plan and your Provider.Configure()
method might not have been called yet.
In this case, req.ProviderData
is nil
because the configuration didn't run yet. The way to handle this correctly is to check for it and return early:
func (r *ApplicationResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
// IMPORTANT: This method is called MULTIPLE times. An initial call might not have configured the Provider yet, so we need
// to handle this gracefully. It will eventually be called with a configured provider.
return
}
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
}