Skip to content

Commit 37e5cbc

Browse files
authored
Merge pull request #1994 from hashicorp/add-key_wo_version-to-tfe_ssh_key
Configs with Write-only `key` value will always converge when used in to tfe_ssh_key
2 parents 15fa5e1 + 2477100 commit 37e5cbc

File tree

4 files changed

+88
-39
lines changed

4 files changed

+88
-39
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ BREAKING CHANGES:
1111
* `r/tfe_organization_run_task`: Added the `hmac_key_wo_version` write-only attribute to allow triggering `hmac_key_wo` updates, by @uk1288 ([#1992](https://github.com/hashicorp/terraform-provider-tfe/pull/1992))
1212
* `r/tfe_policy_set_parameter`: Added the `value_wo_version` write-only attribute to allow triggering `value_wo` updates, by @uk1288 ([#1984](https://github.com/hashicorp/terraform-provider-tfe/pull/1984))
1313
* `r/tfe_saml_settings`: Added the `private_key_wo_version` write-only attribute to allow triggering `private_key_wo` updates, by @uk1288 ([#1993](https://github.com/hashicorp/terraform-provider-tfe/pull/1993))
14+
* `r/tfe_ssh_key`: Added the `key_wo_version` write-only attribute to allow triggering `key_wo` updates, by @uk1288 ([#1994](https://github.com/hashicorp/terraform-provider-tfe/pull/1994))
1415

1516
FEATURES:
1617
* Adds `tfe_query_run` action, allowing users to invoke remote Terraform queries on HCP Terraform and Terraform Enterprise, by @sebasslash [#1982](https://github.com/hashicorp/terraform-provider-tfe/pull/1982)
1718
* Adds `d/tfe_current_user` data source for retrieving information about the user associated with the configured API token, by @ShaunakRembhotkar
1819

20+
1921
## v0.74.1
2022

2123
BUG FIXES:

internal/provider/resource_tfe_ssh_key.go

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ import (
99
"fmt"
1010

1111
tfe "github.com/hashicorp/go-tfe"
12+
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
1213
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
1314
"github.com/hashicorp/terraform-plugin-framework/path"
1415
"github.com/hashicorp/terraform-plugin-framework/resource"
1516
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
17+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
1618
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
1719
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
1820
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
1921
"github.com/hashicorp/terraform-plugin-framework/types"
2022
"github.com/hashicorp/terraform-plugin-log/tflog"
21-
"github.com/hashicorp/terraform-provider-tfe/internal/provider/helpers"
22-
"github.com/hashicorp/terraform-provider-tfe/internal/provider/planmodifiers"
2323
)
2424

2525
var (
@@ -41,17 +41,21 @@ type modelTFESSHKey struct {
4141
Organization types.String `tfsdk:"organization"`
4242
Key types.String `tfsdk:"key"`
4343
KeyWO types.String `tfsdk:"key_wo"`
44+
KeyWOVersion types.Int64 `tfsdk:"key_wo_version"`
4445
}
4546

46-
func modelFromTFESSHKey(organization string, sshKey *tfe.SSHKey, lastValue types.String, isWriteOnly bool) *modelTFESSHKey {
47+
func modelFromTFESSHKey(organization string, sshKey *tfe.SSHKey, lastValue types.String, keyWOVersion types.Int64) *modelTFESSHKey {
4748
m := &modelTFESSHKey{
4849
ID: types.StringValue(sshKey.ID),
4950
Name: types.StringValue(sshKey.Name),
5051
Organization: types.StringValue(organization),
5152
Key: lastValue,
53+
KeyWOVersion: keyWOVersion,
5254
}
5355

54-
if isWriteOnly {
56+
// Don't retrieve values if write-only is being used. Unset the key field before updating the state.
57+
isWriteOnlyValue := !keyWOVersion.IsNull()
58+
if isWriteOnlyValue {
5559
m.Key = types.StringNull()
5660
}
5761

@@ -115,17 +119,29 @@ func (r *resourceTFESSHKey) Schema(_ context.Context, req resource.SchemaRequest
115119
stringvalidator.AtLeastOneOf(path.MatchRoot("key"), path.MatchRoot("key_wo")),
116120
},
117121
},
118-
122+
// since the key_wo write-only values are not saved to state, they will not trigger updates on their own.
123+
// Instead the key_wo_version responsibility is to trigger updates to the key_wo attribute when version number changes.
119124
"key_wo": schema.StringAttribute{
120125
Description: "The text of the SSH private key, guaranteed not to be written to state.",
121126
Optional: true,
122127
WriteOnly: true,
123128
Sensitive: true,
124129
Validators: []validator.String{
125130
stringvalidator.ConflictsWith(path.MatchRoot("key")),
131+
stringvalidator.AlsoRequires(path.MatchRoot("key_wo_version")),
126132
},
127-
PlanModifiers: []planmodifier.String{
128-
planmodifiers.NewReplaceForWriteOnlyStringValue("key_wo"),
133+
},
134+
135+
"key_wo_version": schema.Int64Attribute{
136+
Optional: true,
137+
Description: "Version of the write-only key to trigger updates",
138+
Validators: []validator.Int64{
139+
int64validator.ConflictsWith(path.MatchRoot("key")),
140+
int64validator.AlsoRequires(path.MatchRoot("key_wo")),
141+
},
142+
PlanModifiers: []planmodifier.Int64{
143+
// must be replaced since the go-tfe update api does not have the `key` attribute at time of writing
144+
int64planmodifier.RequiresReplace(),
129145
},
130146
},
131147
},
@@ -153,9 +169,8 @@ func (r *resourceTFESSHKey) Create(ctx context.Context, req resource.CreateReque
153169
Name: plan.Name.ValueStringPointer(),
154170
}
155171

156-
// Set Value from `value_wo` if set, otherwise use the normal value
157-
isWriteOnly := !config.KeyWO.IsNull()
158-
if isWriteOnly {
172+
// Set Value from `key_wo` if set, otherwise use the normal value
173+
if !config.KeyWO.IsNull() {
159174
options.Value = config.KeyWO.ValueStringPointer()
160175
} else {
161176
options.Value = plan.Key.ValueStringPointer()
@@ -169,13 +184,7 @@ func (r *resourceTFESSHKey) Create(ctx context.Context, req resource.CreateReque
169184
}
170185

171186
// Load the response data into the model
172-
result := modelFromTFESSHKey(organization, sshKey, plan.Key, isWriteOnly)
173-
174-
// Write the hashed private key to the state if it was provided
175-
if !config.KeyWO.IsNull() {
176-
store := r.writeOnlyValueStore(resp.Private)
177-
resp.Diagnostics.Append(store.SetPriorValue(ctx, config.KeyWO)...)
178-
}
187+
result := modelFromTFESSHKey(organization, sshKey, plan.Key, config.KeyWOVersion)
179188

180189
if resp.Diagnostics.HasError() {
181190
return
@@ -216,15 +225,8 @@ func (r *resourceTFESSHKey) Read(ctx context.Context, req resource.ReadRequest,
216225
return
217226
}
218227

219-
// Check if the parameter is write-only
220-
isWriteOnly, diags := r.writeOnlyValueStore(resp.Private).PriorValueExists(ctx)
221-
resp.Diagnostics.Append(diags...)
222-
if diags.HasError() {
223-
return
224-
}
225-
226228
// Load the response data into the model
227-
result := modelFromTFESSHKey(organization, sshKey, state.Key, isWriteOnly)
229+
result := modelFromTFESSHKey(organization, sshKey, state.Key, state.KeyWOVersion)
228230

229231
// Update state
230232
resp.Diagnostics.Append(resp.State.Set(ctx, result)...)
@@ -261,7 +263,7 @@ func (r *resourceTFESSHKey) Update(ctx context.Context, req resource.UpdateReque
261263
}
262264

263265
// Load the response data into the model
264-
result := modelFromTFESSHKey(organization, sshKey, plan.Key, !config.KeyWO.IsNull())
266+
result := modelFromTFESSHKey(organization, sshKey, plan.Key, config.KeyWOVersion)
265267

266268
// Update state
267269
resp.Diagnostics.Append(resp.State.Set(ctx, result)...)
@@ -298,7 +300,3 @@ func (r *resourceTFESSHKey) Delete(ctx context.Context, req resource.DeleteReque
298300
return
299301
}
300302
}
301-
302-
func (r *resourceTFESSHKey) writeOnlyValueStore(private helpers.PrivateState) *helpers.WriteOnlyValueStore {
303-
return helpers.NewWriteOnlyValueStore(private, "key_wo")
304-
}

internal/provider/resource_tfe_ssh_key_test.go

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,30 @@ func TestAccTFESSHKey_update(t *testing.T) {
8181
})
8282
}
8383

84+
func TestAccTFESSHKey_WriteOnlyValidation(t *testing.T) {
85+
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()
86+
87+
resource.Test(t, resource.TestCase{
88+
PreCheck: func() { testAccPreCheck(t) },
89+
ProtoV6ProviderFactories: testAccMuxedProviders,
90+
CheckDestroy: testAccCheckTFESSHKeyDestroy,
91+
Steps: []resource.TestStep{
92+
{
93+
Config: testAccTFESSHKey_WriteOnlyMissingVersion(rInt),
94+
ExpectError: regexp.MustCompile(`Attribute "key_wo_version" must be specified when "key_wo" is specified`),
95+
},
96+
{
97+
Config: testAccTFESSHKey_keyAndKeyWO(rInt),
98+
ExpectError: regexp.MustCompile(`Attribute "key_wo" cannot be specified when "key" is specified`),
99+
},
100+
},
101+
})
102+
}
103+
84104
func TestAccTFESSHKey_keyWO(t *testing.T) {
85105
sshKey := &tfe.SSHKey{}
86106
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()
107+
versionOne, versionTwo := 1, 2
87108

88109
// Create the value comparer so we can add state values to it during the test steps
89110
compareValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer())
@@ -94,11 +115,7 @@ func TestAccTFESSHKey_keyWO(t *testing.T) {
94115
CheckDestroy: testAccCheckTFEOrganizationRunTaskDestroy,
95116
Steps: []resource.TestStep{
96117
{
97-
Config: testAccTFESSHKey_keyAndKeyWO(rInt),
98-
ExpectError: regexp.MustCompile(`Attribute "key_wo" cannot be specified when "key" is specified`),
99-
},
100-
{
101-
Config: testAccTFESSHKey_keyWO(rInt, "SSH-KEY-CONTENT"),
118+
Config: testAccTFESSHKey_keyWO(rInt, "SSH-KEY-CONTENT", versionOne),
102119
Check: resource.ComposeTestCheckFunc(
103120
testAccCheckTFESSHKeyExists("tfe_ssh_key.foobar", sshKey),
104121
testAccCheckTFESSHKeyAttributes(sshKey),
@@ -115,7 +132,7 @@ func TestAccTFESSHKey_keyWO(t *testing.T) {
115132
},
116133
},
117134
{
118-
Config: testAccTFESSHKey_keyWO(rInt, "SSH-KEY-CONTENT-UPDATED"),
135+
Config: testAccTFESSHKey_keyWO(rInt, "SSH-KEY-CONTENT-UPDATED", versionTwo),
119136
Check: resource.ComposeTestCheckFunc(
120137
testAccCheckTFESSHKeyExists("tfe_ssh_key.foobar", sshKey),
121138
testAccCheckTFESSHKeyAttributes(sshKey),
@@ -244,7 +261,7 @@ resource "tfe_ssh_key" "foobar" {
244261
}`, rInt)
245262
}
246263

247-
func testAccTFESSHKey_keyWO(rInt int, key string) string {
264+
func testAccTFESSHKey_keyWO(rInt int, key string, versionValue int) string {
248265
return fmt.Sprintf(`
249266
resource "tfe_organization" "foobar" {
250267
name = "tst-terraform-%d"
@@ -255,7 +272,8 @@ resource "tfe_ssh_key" "foobar" {
255272
name = "ssh-key-test"
256273
organization = tfe_organization.foobar.id
257274
key_wo = "%s"
258-
}`, rInt, key)
275+
key_wo_version = %d
276+
}`, rInt, key, versionValue)
259277
}
260278

261279
func testAccTFESSHKey_keyAndKeyWO(rInt int) string {
@@ -272,3 +290,17 @@ resource "tfe_ssh_key" "foobar" {
272290
key_wo = "SSH-KEY-CONTENT"
273291
}`, rInt)
274292
}
293+
294+
func testAccTFESSHKey_WriteOnlyMissingVersion(rInt int) string {
295+
return fmt.Sprintf(`
296+
resource "tfe_organization" "foobar" {
297+
name = "tst-terraform-%d"
298+
email = "admin@company.com"
299+
}
300+
301+
resource "tfe_ssh_key" "foobar" {
302+
name = "ssh-key-test"
303+
organization = tfe_organization.foobar.id
304+
key_wo = "SSH-KEY-CONTENT"
305+
}`, rInt)
306+
}

website/docs/r/ssh_key.html.markdown

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,22 @@ resource "tfe_ssh_key" "test" {
2222
}
2323
```
2424

25+
With write-only key:
26+
27+
```hcl
28+
variable "ssh_key" {
29+
type = string
30+
ephemeral = true
31+
}
32+
33+
resource "tfe_ssh_key" "test" {
34+
name = "my-ssh-key-name"
35+
organization = "my-org-name"
36+
key_wo = var.ssh_key
37+
key_wo_version = 1
38+
}
39+
```
40+
2541
## Argument Reference
2642

2743
The following arguments are supported:
@@ -31,7 +47,8 @@ The following arguments are supported:
3147
* `key` - (Optional) The text of the SSH private key. One of `key` or `key_wo`
3248
must be provided.
3349
* `key_wo` - (Optional, [Write-Only](https://developer.hashicorp.com/terraform/language/v1.11.x/resources/ephemeral#write-only-arguments)) The text of the SSH private key, guaranteed not to be
34-
written to plan or state artifacts. One of `key` or `key_wo` must be provided.
50+
written to plan or state artifacts. One of `key` or `key_wo` must be provided. Must be used with `key_wo_version`.
51+
* `key_wo_version` - (Optional) Version of the write-only key. This field is used to trigger updates when the write-only key changes. Must be used with `key_wo`. When `key_wo_version` changes, the write-only key will be updated.
3552

3653
## Attributes Reference
3754

0 commit comments

Comments
 (0)