Best practices #
The following is a list of best practices that contributions are expected to follow in order to ensure a consistent UX for the Terraform provider for Google Cloud internally and also compared to other Terraform providers.
ForceNew #
ForceNew
in a Terraform resource schema attribute that indicates that a field is immutable – that is, that a change to the field requires the resource to be destroyed and recreated.
This is necessary and required for cases where a field can’t be updated in-place, so that Terraform’s core workflow of aligning real infrastructure with configuration can be achieved. If a field or resource can never be updated in-place and is not marked with ForceNew
, that is considered a bug in the provider.
Some fields or resources may be possible to update in place, but only under specific conditions. In these cases, you can treat the field as updatable - that is, do not mark it as ForceNew; instead, implement standard update functionality. Then, call diff.ForceNew
inside a CustomizeDiff
if the appropriate conditions to allow update in place are not met. Any CustomizeDiff
function like this must be thoroughly unit tested. Making a field conditionally updatable like this is considered a good and useful enhancement in cases where recreation is costly and conditional updates do not introduce undue complexity.
In complex cases, it is better to mark the field ForceNew
to ensure that users can apply their configurations successfully.
Mitigating data loss risk via deletion_protection #
Some resources, such as databases, have a significant risk of unrecoverable data loss if the resource is accidentally deleted due to a change to a ForceNew field. For these resources, the best practice is to add a deletion_protection
field that defaults to true
, which prevents the resource from being deleted if enabled. Although it is a small breaking change, for users, the benefits of deletion_protection
defaulting to true
outweigh the cost.
APIs also sometimes add deletion_protection
fields, which will generally default to false
for backwards-compatibility reasons. Any deletion_protection
API field added to an existing Terraform resource must match the API default initially. The default may be set to true
in the next major release. For new Terraform resources, any deletion_protection
field should default to true
in Terraform regardless of the API default. When creating the corresponding Terraform field, the name
should match the API field name (i.e. it need not literally be named deletion_protection
if the API uses something different) and should be the same field type (example: if the API field is an enum, so should the Terraform field).
A resource can have up to two deletion_protection
fields (with different names): one that represents a field in the API, and one that is only in Terraform. This could happen because the API added its field after deletion_protection
already existed in Terraform; it could also happen because a separate field was added in Terraform to make sure that deletion_protection
is enabled by default. In either case, they should be reconciled into a single field (that defaults to enabled and whose name matches the API field) in the next major release.
Resources that do not have a significant risk of unrecoverable data loss or similar critical concern will not be given deletion_protection
fields.
See Client-side fields for information about adding deletion_protection
fields.
Note: The previous best practice was a field calledforce_delete
that defaulted tofalse
. This is still present on some resources for backwards-compatibility reasons, butdeletion_protection
is preferred going forward.
Deletion policy #
Some resources need to let users control the actions taken add deletion time. For these resources, the best practice is to add a deletion_policy
enum field that defaults to an empty string and allows special values that control the deletion behavior.
One common example is ABANDON
, which is useful if the resource is safe to delete from Terraform but could cause problems if deleted from the API - for example, google_bigtable_gc_policy
deletion can fail in replicated instances. ABANDON
indicates that attempts to delete the resource should remove it from state without actually deleting it.
See Client-side fields for information about adding deletion_policy
fields.
Add labels and annotations support #
The new labels model and the new annotations model are introduced in Terraform provider for Google Cloud 5.0.0.
There are now three label-related fields with the new labels model:
- The
labels
field is now non-authoritative and only manages the label keys defined in your configuration for the resource. - The
terraform_labels
cannot be specified directly by the user. It merges the labels defined in the resource’s configuration and the default labels configured in the provider block. If the same label key exists on both the resource level and provider level, the value on the resource will override the provider-level default. - The output-only
effective_labels
will list all the labels present on the resource in GCP, including the labels configured through Terraform, the system, and other clients.
There are now two annotation-related fields with the new annotations model:
- The
annotations
field is now non-authoritative and only manages the annotation keys defined in your configuration for the resource. - The output-only
effective_annotations
will list all the annotations present on the resource in GCP, including the annotations configured through Terraform, the system, and other clients.
This document describes how to add labels
and annotations
field to resources to support the new models.
Labels support #
When adding a new labels
field, please make the changes below to support the new labels model. Otherwise, it has to wait for the next major release to make the changes.
MMv1 resources #
- Use the type
KeyValueLabels
for the standard resourcelabels
field. The standard resourcelabels
field could be the top levellabels
field or the nestedlabels
field inside the top levelmetadata
field. Don’t adddefault_from_api: true
to this field or don’t use this type for otherlabels
fields in the resource.KeyValueLabels
will add all of changes required for the new model automatically.
- name: 'labels'
type: KeyValueLabels
description: |
The labels associated with this dataset. You can use these to
organize and group your datasets.
- In the handwritten acceptance tests, add
labels
andterraform_labels
toImportStateVerifyIgnore
iflabels
field is in the configuration.
ImportStateVerifyIgnore: []string{"labels", "terraform_labels"},
- In the corresponding data source, after the resource read method, call the function
tpgresource.SetDataSourceLabels(d)
to makelabels
andterraform_labels
have all of the labels on the resource.
err = resourceArtifactRegistryRepositoryRead(d, meta)
if err != nil {
return err
}
if err := tpgresource.SetDataSourceLabels(d); err != nil {
return err
}
Handwritten resources #
- Add
tpgresource.SetLabelsDiff
toCustomizeDiff
of the resource.
CustomizeDiff: customdiff.All(
tpgresource.SetLabelsDiff,
),
- Add
labels
field and add more attributes (such asForceNew: true,
,Set: schema.HashString,
) to this field if necessary.
"labels": {
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Description: `A set of key/value label pairs to assign to the project.
**Note**: This field is non-authoritative, and will only manage the labels present in your configuration.
Please refer to the field 'effective_labels' for all of the labels present on the resource.`,
},
- Add output only field
terraform_labels
and add more attributes (such asSet: schema.HashString,
) to this field if necessary. Don’t addForceNew:true,
to this field.
"terraform_labels": {
Type: schema.TypeMap,
Computed: true,
Description: `The combination of labels configured directly on the resource and default labels configured on the provider.`,
Elem: &schema.Schema{Type: schema.TypeString},
},
- Add output only field
effective_labels
and add more attributes (such asForceNew: true,
,Set: schema.HashString,
) to this field if necessary.
"effective_labels": {
Type: schema.TypeMap,
Computed: true,
Description: `All of labels (key/value pairs) present on the resource in GCP, including the labels configured through Terraform, other clients and services.`,
Elem: &schema.Schema{Type: schema.TypeString},
},
- In the create method, use the value of
effective_labels
in API request. - In the update method, use the value of
effective_labels
in API request. - In the read mehtod, set
labels
,terraform_labels
andeffective_labels
to state.
if err := tpgresource.SetLabels(res.Labels, d, "labels"); err != nil {
return fmt.Errorf("Error setting labels: %s", err)
}
if err := tpgresource.SetLabels(res.Labels, d, "terraform_labels"); err != nil {
return fmt.Errorf("Error setting terraform_labels: %s", err)
}
if err := d.Set("effective_labels", res.Labels); err != nil {
return fmt.Errorf("Error setting effective_labels: %s", err)
}
- In the handwritten acceptance tests, add
labels
andterraform_labels
toImportStateVerifyIgnore
. - In the corresponding data source, after the resource read method, call the function
tpgresource.SetDataSourceLabels(d)
to makelabels
andterraform_labels
have all of the labels on the resource. - Add the documentation for these label-related fields.
Annotations support #
When adding a new annotations
field, please make the changes below below to support the new annotations model. Otherwise, it has to wait for the next major release to make the breaking changes.
MMv1 resources #
- Use the type
KeyValueAnnotations
for the standard resourceannotations
field. The standard resourceannotations
field could be the top levelannotations
field or the nestedannotations
field inside the top levelmetadata
field. Don’t adddefault_from_api: true
to this field or don’t use this type for otherannotations
fields in the resource.KeyValueAnnotations
will add all of changes required for the new model automatically.
- name: 'annotations'
type: KeyValueAnnotations
description: |
Client-specified annotations. This is distinct from labels.
- In the handwritten acceptance tests, add
annotations
toImportStateVerifyIgnore
ifannotations
field is in the configuration.
ImportStateVerifyIgnore: []string{"annotations"},
- In the corresponding data source, after the resource read method, call the function
tpgresource.SetDataSourceAnnotations(d)
to makeannotations
have all of the annotations on the resource.
err = resourceSecretManagerSecretRead(d, meta)
if err != nil {
return err
}
if err := tpgresource.SetDataSourceLabels(d); err != nil {
return err
}
if err := tpgresource.SetDataSourceAnnotations(d); err != nil {
return err
}
Handwritten resources #
- Add
tpgresource.SetAnnotationsDiff
toCustomizeDiff
of the resource. - Add
annotations
field and add more attributes (such asForceNew: true,
,Set: schema.HashString,
) to this field if necessary. - Add output only field
effective_annotations
and add more attributes (such asForceNew: true,
,Set: schema.HashString,
) to this field if necessary. - In the create method, use the value of
effective_annotations
in API request. - In the update method, use the value of
effective_annotations
in API request. - In the read mehtod, set
annotations
, andeffective_annotations
to state. - In the handwritten acceptance tests, add
annotations
toImportStateVerifyIgnore
. - In the corresponding data source, after the resource read method, call the function
tpgresource.SetDataSourceAnnotations(d)
to makeannotations
have all of the labels on the resource. - Add the documentation for these annotation-related fields.