Context: I am implementing (my first) Terraform plugin/provider as a wrapper towards existing public API.
One of the create operations in the API specifies an integer field which takes positive values or -1
serving as a default. If you specify -1
in the create API call, the value gets replaced by some default on the server side (say field = 1000
) and is stored as 1000
from now on.
If I present this to my Terraform plugin (terraform apply
):
resource "something" "mysomething" {
name = "someName"
field = -1
}
the call is not idempotent. Terraform continues to see a drift and subsequently offers:
# something.mysomething will be updated in-place
~ resource "something" "mysomething" {
id = "165-1567498530352"
name = "someName"
~ field = 1000 -> -1
}
Plan: 0 to add, 1 to change, 0 to destroy.
How to deal with such API?
You can use the DiffSuppressFunc
flag on schema attributes to conditionally suppress a diff so that Terraform doesn't choose to do anything with the diff.
Something like this should work for you:
package something
import (
"github.com/hashicorp/terraform/helper/schema"
)
func somethingSomething() *schema.Resource {
return &schema.Resource{
// ...
Schema: map[string]*schema.Schema{
// ...
"field": {
Type: schema.TypeInt,
Optional: true,
Default: -1,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
if new == "-1" {
return true
}
return false
},
},
},
}
}
Martin's answer provides probably a better alternative by using the Computed
flag and making the attribute Optional
. For this to work nicely you'd want to prevent people from specifying -1
as a value for this which you can do with a ValidateFunc
, using the IntAtLeast
validator from the list of predefined validations in the core SDK:
package something
import (
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
)
func somethingSomething() *schema.Resource {
return &schema.Resource{
// ...
Schema: map[string]*schema.Schema{
// ...
"field": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
ValidateFunc: validation.IntAtLeast(1),
},
},
}
}
The Terraform SDK includes a special schema flag Computed
, which means "if there is no value given in the configuration then a default value will be selected at apply time".
That seems to match with your use-case here. If you unset Default
and set Computed: true
instead -- retaining Optional: true
to indicate that the user can optionally set it -- then you can activate that behavior.
If you're able to predict the final "computed" value during the plan step, before creating or updating anything, then you should implement CustomizeDiff
for the resource and use d.Set
to provide the value, and then Terraform can take it into account to produce a more complete plan.
If not, then you can leave it unset during planning (in Terraform terms, its value will be "unknown") and then call d.Set
in the Create
and Update
functions instead, with the value appearing as (known after apply)
in the plan.
When using this mechanism, it's important to be self-consistent: if you provide a known value during planning using CustomizeDiff
then that must exactly match a final value selected during Create
or Update
. If you aren't consistent then any references to this attribute in other expressions will lead to errors during apply when Terraform Core verifies that the final changes are consistent with what was planned.
There is a currently caveat with this approach: due to API design limitations of the Terraform SDK today, provider code can't tell when a value that was previously set in configuration is now no longer set. Or, to put it another way, the SDK can't tell whether the value already stored was selected by explicit configuration or was populated by the provider as a default during apply.
For this reason, the last value set in the configuration will be "sticky" if the user unsets it, and the provider will not be able to adjust that value back to the server-provided default automatically.
That caveat is often not a big deal in practice, but is worth noting in case it does matter in your specific situation. A subsequent version of the SDK is likely to provide a mechanism to ask if a particular value is set in configuration, separately from what is stored in the state.