Virtual Event
Join us for the next HashiConf Digital October 12-15, 2020 Register for Free

Call APIs with Terraform Providers

Debug a Terraform Provider

In this tutorial, you will add custom error messages to a Terraform provider that interacts with the API of a fictional coffee-shop application, HashiCups and view detailed Terraform provider logs.

»Prerequisites

To follow this tutorial, you need:

Navigate to your terraform-provider-hashicups directory. Then, checkout the implement-complex-read branch. This step is optional but recommended to insure that you've accurately completed the previous steps.

$ git checkout implement-complex-read

Your directory should have the following structure.

$ tree -L 2
.
├── Makefile
├── README.md
├── docker_compose
│   ├── conf.json
│   └── docker-compose.yml
├── examples
│   ├── coffee
│   ├── main.tf
├── go.mod
├── go.sum
├── hashicups
│   ├── data_source_coffee.go
│   └── provider.go
├── main.go
└── vendor

If you’re stuck, refer to the debug-tf-provider branch to see the changes implemented in this tutorial.

»Update error messages

In your hashicups/provider.go file, the providerConfigure function returns an interface{} and a diag.Diagnostics type. diag.Diagnostics can return multiple errors and warnings to Terraform, giving users more robust error and warning messages.

func providerConfigure(...) (interface{}, diag.Diagnostics) {
  ...
}

The diag.FromError() function casts standard Go errors to diag.Diagnostics type notifying the user whenever the provider errors.

Replace the return nil, diag.FromError(err) line in your providerConfigure function with the following code snippet. There should be two replacements — one where the user's HashiCups credentials provided and the other where its not. Notice how this appends a diag.Diagnostic type to the existing diags variable; this allows your provider to return multiple error message. This will produce an error message containing Summary and Detail contents.

diags = append(diags, diag.Diagnostic{
  Severity: diag.Error,
  Summary:  "Unable to create HashiCups client",
  Detail:   "Unable to auth user for authenticated HashiCups client",
})

return nil, diags

Your providerConfigure function should look like the following.

func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
  username := d.Get("username").(string)
  password := d.Get("password").(string)
  
  // Warning or errors can be collected in a slice type
  var diags diag.Diagnostics

  if (username != "") && (password != "") {
    c, err := hashicups.NewClient(nil, &username, &password)
    if err != nil {
-     return nil, diag.FromErr(err)
+     diags = append(diags, diag.Diagnostic{
+       Severity: diag.Error,
+       Summary:  "Unable to create HashiCups client",
+       Detail:   "Unable to auth user for authenticated HashiCups client",
+     })
+     return nil, diags
    }

    return c, diags
  }
  
  c, err := hashicups.NewClient(nil, nil, nil)
  if err != nil {
-   return nil, diag.FromErr(err)
+   diags = append(diags, diag.Diagnostic{
+     Severity: diag.Error,
+     Summary:  "Unable to create HashiCups client",
+     Detail:   "Unable to auth user for unauthenticated HashiCups client",
+   })
+   return nil, diags
  }
  
  return c, diags
}

Notice how the Diagnostic severity is set to diag.Error. There are two levels of severitydiag.Error and diag.Warning. You will create and view a warning message in the next section.

»Test error message

Now that you’ve added a custom error message, verify that the provider returns your error message.

First, navigate to the terraform-provider-hashicups root directory.

Then, build the binary and move it into your user Terraform plugins directory. This allows you to sideload and test your custom providers.

$ make install
go build -o terraform-provider-hashicups
mv terraform-provider-hashicups ~/.terraform.d/plugins/hashicorp.com/edu/hashicups

Navigate to the examples directory. This contains a sample Terraform configuration for the Terraform HashiCups provider.

$ cd examples

Update your HashiCups provider configuration so it uses the wrong credentials.

provider "hashicups" {
  username = "education"
-  password = "test123"
+  password = "test1234"
}

Next, initialize your workspace to refresh your HashiCups provider, then apply. This should return the error message you defined above.

$ terraform init && terraform apply --auto-approve
## Output truncated...
module.psl.data.hashicups_coffees.all: Refreshing state...

Error: Unable to create HashiCups client

Unable to authenticate user for authenticated HashiCups client

Because your provider errors, your Terraform state doesn't update when you ran the terraform apply.

»Add warning message

Add the following snippet after diags is declared in providerConfigure function.

diags = append(diags, diag.Diagnostic{
  Severity: diag.Warning,
  Summary:  "Warning Message Summary",
  Detail:   "This is the detailed warning message from providerConfigure",
})

Your providerConfigure function should look like the following.

func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
  ...
  
  // Warning or errors can be collected in a slice type
  var diags diag.Diagnostics
  
+ diags = append(diags, diag.Diagnostic{
+   Severity: diag.Warning,
+   Summary:  "Warning Message Summary",
+   Detail:   "This is the detailed warning message from providerConfigure",
+ })

  ...
}

Notice that you have set the Diagnostic's severity to diag.Warning. When you use your provider, both warning and messages appear because diags contains both. A warning message will only appear when the provider also errors.

The Diagnostic's detail contains additional information about the warning message, including which function it was triggered in. This could provide handy while debugging.

»Test warning message

Now that you’ve added a warning message and updated your Terraform log level, verify that the provider returns both your warning and error message.

First, navigate to the terraform-provider-hashicups root directory.

Then, build the binary and move it into your user Terraform plugins directory. This allows you to sideload and test your custom providers.

$ make install
go build -o terraform-provider-hashicups
mv terraform-provider-hashicups ~/.terraform.d/plugins/hashicorp.com/edu/hashicups

Navigate to the examples directory. This contains a sample Terraform configuration for the Terraform HashiCups provider.

$ cd examples

Next, initialize your workspace to refresh your HashiCups provider, then apply. This should return both a warning and error message.

$ terraform init && terraform apply --auto-approve
## Output truncated...
module.psl.data.hashicups_coffees.all: Refreshing state...

Warning: Warning Message Summary

This is the detailed warning message from providerConfigure


Error: Unable to create HashiCups client

Unable to authenticate user for authenticated HashiCups client

Because your provider errors, your Terraform state doesn't update when you ran the terraform apply.

»Fix provider

Comment out or remove the warning message, then recompile your Terraform provider.

func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
  ...
  
  // Warning or errors can be collected in a slice type
  var diags diag.Diagnostics
  
- diags = append(diags, diag.Diagnostic{
-   Severity: diag.Warning,
-   Summary:  "Warning Message Summary",
-   Detail:   "This is the detailed warning message from providerConfigure",
- })

  ...
}

Fix your Terraform configuration to use the correct credentials.

provider "hashicups" {
  username = "education"
-  password = "test1234"
+  password = "test123"
}

Finally, re-initializing your workspace and applying the configuration.

$ terraform init && terraform apply --auto-approve
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

There should be no errors or warning messages.

»Next steps

Congratulations! You have added error and warning messages to your HashiCups provider and implemented a nested read function.

If you were stuck during this tutorial, checkout the debug-tf-providers branch to see the changes implemented in this tutorial.