Skip to content

Commit 29beca4

Browse files
authored
Merge pull request #150 from hashicorp/frameworkMigration
Null Provider Framework Migration
2 parents 5e8e8e5 + ee12a03 commit 29beca4

File tree

16 files changed

+1127
-112
lines changed

16 files changed

+1127
-112
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ jobs:
7171
- '1.2.*'
7272
- '1.3.*'
7373

74+
7475
steps:
7576

7677
- uses: actions/checkout@v3

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 3.2.0 (October 25, 2022)
2+
3+
NOTES:
4+
5+
* Provider: Rewritten to use the new [`terraform-plugin-framework`](https://www.terraform.io/plugin/framework) ([#150](https://github.com/hashicorp/terraform-provider-null/pull/150))
6+
17
## 3.1.1 (March 16, 2022)
28

39
NOTES:

docs/data-sources/data_source.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@ same can now be achieved using [locals](https://www.terraform.io/docs/language/v
2222
```terraform
2323
data "null_data_source" "values" {
2424
inputs = {
25-
all_server_ids = concat(aws_instance.green.*.id, aws_instance.blue.*.id)
26-
all_server_ips = concat(aws_instance.green.*.private_ip, aws_instance.blue.*.private_ip)
25+
all_server_ids = concat(
26+
aws_instance.green.*.id,
27+
aws_instance.blue.*.id,
28+
)
29+
all_server_ips = concat(
30+
aws_instance.green.*.private_ip,
31+
aws_instance.blue.*.private_ip,
32+
)
2733
}
2834
}
2935

docs/resources/resource.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ resource "aws_instance" "cluster" {
2222
# ...
2323
}
2424
25-
# The primary use-case for the null resource is as a do-nothing container for
26-
# arbitrary actions taken by a provisioner.
25+
# The primary use-case for the null resource is as a do-nothing container
26+
# for arbitrary actions taken by a provisioner.
2727
#
28-
# In this example, three EC2 instances are created and then a null_resource instance
29-
# is used to gather data about all three and execute a single action that affects
30-
# them all. Due to the triggers map, the null_resource will be replaced each time
31-
# the instance ids change, and thus the remote-exec provisioner will be re-run.
28+
# In this example, three EC2 instances are created and then a
29+
# null_resource instance is used to gather data about all three
30+
# and execute a single action that affects them all. Due to the triggers
31+
# map, the null_resource will be replaced each time the instance ids
32+
# change, and thus the remote-exec provisioner will be re-run.
3233
resource "null_resource" "cluster" {
3334
# Changes to any instance of the cluster requires re-provisioning
3435
triggers = {
@@ -42,9 +43,10 @@ resource "null_resource" "cluster" {
4243
}
4344
4445
provisioner "remote-exec" {
45-
# Bootstrap script called with private_ip of each node in the clutser
46+
# Bootstrap script called with private_ip of each node in the cluster
4647
inline = [
47-
"bootstrap-cluster.sh ${join(" ", aws_instance.cluster.*.private_ip)}",
48+
"bootstrap-cluster.sh ${join(" ",
49+
aws_instance.cluster.*.private_ip)}",
4850
]
4951
}
5052
}

examples/data-sources/null_data_source/data-source.tf

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
data "null_data_source" "values" {
22
inputs = {
3-
all_server_ids = concat(aws_instance.green.*.id, aws_instance.blue.*.id)
4-
all_server_ips = concat(aws_instance.green.*.private_ip, aws_instance.blue.*.private_ip)
3+
all_server_ids = concat(
4+
aws_instance.green.*.id,
5+
aws_instance.blue.*.id,
6+
)
7+
all_server_ips = concat(
8+
aws_instance.green.*.private_ip,
9+
aws_instance.blue.*.private_ip,
10+
)
511
}
612
}
713

examples/resources/null_resource/resource.tf

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ resource "aws_instance" "cluster" {
44
# ...
55
}
66

7-
# The primary use-case for the null resource is as a do-nothing container for
8-
# arbitrary actions taken by a provisioner.
7+
# The primary use-case for the null resource is as a do-nothing container
8+
# for arbitrary actions taken by a provisioner.
99
#
10-
# In this example, three EC2 instances are created and then a null_resource instance
11-
# is used to gather data about all three and execute a single action that affects
12-
# them all. Due to the triggers map, the null_resource will be replaced each time
13-
# the instance ids change, and thus the remote-exec provisioner will be re-run.
10+
# In this example, three EC2 instances are created and then a
11+
# null_resource instance is used to gather data about all three
12+
# and execute a single action that affects them all. Due to the triggers
13+
# map, the null_resource will be replaced each time the instance ids
14+
# change, and thus the remote-exec provisioner will be re-run.
1415
resource "null_resource" "cluster" {
1516
# Changes to any instance of the cluster requires re-provisioning
1617
triggers = {
@@ -24,9 +25,10 @@ resource "null_resource" "cluster" {
2425
}
2526

2627
provisioner "remote-exec" {
27-
# Bootstrap script called with private_ip of each node in the clutser
28+
# Bootstrap script called with private_ip of each node in the cluster
2829
inline = [
29-
"bootstrap-cluster.sh ${join(" ", aws_instance.cluster.*.private_ip)}",
30+
"bootstrap-cluster.sh ${join(" ",
31+
aws_instance.cluster.*.private_ip)}",
3032
]
3133
}
3234
}

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ go 1.18
44

55
require (
66
github.com/hashicorp/terraform-plugin-docs v0.13.0
7+
github.com/hashicorp/terraform-plugin-framework v0.14.0
8+
github.com/hashicorp/terraform-plugin-go v0.14.0
79
github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.0
810
)
911

@@ -34,7 +36,6 @@ require (
3436
github.com/hashicorp/logutils v1.0.0 // indirect
3537
github.com/hashicorp/terraform-exec v0.17.3 // indirect
3638
github.com/hashicorp/terraform-json v0.14.0 // indirect
37-
github.com/hashicorp/terraform-plugin-go v0.14.0 // indirect
3839
github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect
3940
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c // indirect
4041
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e
139139
github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM=
140140
github.com/hashicorp/terraform-plugin-docs v0.13.0 h1:6e+VIWsVGb6jYJewfzq2ok2smPzZrt1Wlm9koLeKazY=
141141
github.com/hashicorp/terraform-plugin-docs v0.13.0/go.mod h1:W0oCmHAjIlTHBbvtppWHe8fLfZ2BznQbuv8+UD8OucQ=
142+
github.com/hashicorp/terraform-plugin-framework v0.14.0 h1:Mwj55u+Jc/QGM6fLBPCe1P+ZF3cuYs6wbCdB15lx/Dg=
143+
github.com/hashicorp/terraform-plugin-framework v0.14.0/go.mod h1:wcZdk4+Uef6Ng+BiBJjGAcIPlIs5bhlEV/TA1k6Xkq8=
142144
github.com/hashicorp/terraform-plugin-go v0.14.0 h1:ttnSlS8bz3ZPYbMb84DpcPhY4F5DsQtcAS7cHo8uvP4=
143145
github.com/hashicorp/terraform-plugin-go v0.14.0/go.mod h1:2nNCBeRLaenyQEi78xrGrs9hMbulveqG/zDMQSvVJTE=
144146
github.com/hashicorp/terraform-plugin-log v0.7.0 h1:SDxJUyT8TwN4l5b5/VkiTIaQgY6R+Y2BQ0sRZftGKQs=
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package planmodifiers
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
7+
"github.com/hashicorp/terraform-plugin-framework/types"
8+
)
9+
10+
func RequiresReplaceIfValuesNotNull() tfsdk.AttributePlanModifier {
11+
return requiresReplaceIfValuesNotNullModifier{}
12+
}
13+
14+
type requiresReplaceIfValuesNotNullModifier struct{}
15+
16+
func (r requiresReplaceIfValuesNotNullModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) {
17+
if req.AttributeConfig == nil || req.AttributePlan == nil || req.AttributeState == nil {
18+
// shouldn't happen, but let's not panic if it does
19+
return
20+
}
21+
22+
if req.State.Raw.IsNull() {
23+
// if we're creating the resource, no need to delete and
24+
// recreate it
25+
return
26+
}
27+
28+
if req.Plan.Raw.IsNull() {
29+
// if we're deleting the resource, no need to delete and
30+
// recreate it
31+
return
32+
}
33+
34+
// If there are no differences, do not mark the resource for replacement
35+
// and ensure the plan matches the configuration.
36+
if req.AttributeConfig.Equal(req.AttributeState) {
37+
return
38+
}
39+
40+
if req.AttributeState.IsNull() {
41+
// terraform-plugin-sdk would store maps as null if all keys had null
42+
// values. To prevent unintentional replacement plans when migrating
43+
// to terraform-plugin-framework, only trigger replacement when the
44+
// prior state (map) is null and when there are not null map values.
45+
allNullValues := true
46+
47+
configMap, ok := req.AttributeConfig.(types.Map)
48+
49+
if !ok {
50+
return
51+
}
52+
53+
for _, configValue := range configMap.Elems {
54+
if !configValue.IsNull() {
55+
allNullValues = false
56+
}
57+
}
58+
59+
if allNullValues {
60+
return
61+
}
62+
} else {
63+
// terraform-plugin-sdk would completely omit storing map keys with
64+
// null values, so this also must prevent unintentional replacement
65+
// in that case as well.
66+
allNewNullValues := true
67+
68+
configMap, ok := req.AttributeConfig.(types.Map)
69+
70+
if !ok {
71+
return
72+
}
73+
74+
stateMap, ok := req.AttributeState.(types.Map)
75+
76+
if !ok {
77+
return
78+
}
79+
80+
for configKey, configValue := range configMap.Elems {
81+
stateValue, ok := stateMap.Elems[configKey]
82+
83+
// If the key doesn't exist in state and the config value is
84+
// null, do not trigger replacement.
85+
if !ok && configValue.IsNull() {
86+
continue
87+
}
88+
89+
// If the state value exists and it is equal to the config value,
90+
// do not trigger replacement.
91+
if configValue.Equal(stateValue) {
92+
continue
93+
}
94+
95+
allNewNullValues = false
96+
break
97+
}
98+
99+
for stateKey := range stateMap.Elems {
100+
_, ok := configMap.Elems[stateKey]
101+
102+
// If the key doesn't exist in the config, but there is a state
103+
// value, trigger replacement.
104+
if !ok {
105+
allNewNullValues = false
106+
break
107+
}
108+
}
109+
110+
if allNewNullValues {
111+
return
112+
}
113+
}
114+
115+
resp.RequiresReplace = true
116+
}
117+
118+
// Description returns a human-readable description of the plan modifier.
119+
func (r requiresReplaceIfValuesNotNullModifier) Description(ctx context.Context) string {
120+
return "If the value of this attribute changes, Terraform will destroy and recreate the resource."
121+
}
122+
123+
// MarkdownDescription returns a markdown description of the plan modifier.
124+
func (r requiresReplaceIfValuesNotNullModifier) MarkdownDescription(ctx context.Context) string {
125+
return "If the value of this attribute changes, Terraform will destroy and recreate the resource."
126+
}

0 commit comments

Comments
 (0)