Search code examples
goterraform

Terraform provider method Configure not getting called


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 Servemethod

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"
}

Solution

  • 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 a Configure() function. This reads the configuration, instanciates a http.Client and set it for use with:

    resp.DataSourceData = client
    resp.ResourceData = client
    

    I then created a resource.Resource which also has a Configure() method that uses this client via:

    client, ok := req.ProviderData.(*http.Client)
    

    When planning/applying, the value in req.ProviderData would be nil and my resource would fail to create. I then went into the Provider.Configure() method and tried placing logs via tflog.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
    }