Skip to content

Commit b6e51cc

Browse files
committed
[Feature] Add databricks_secret write-only attributes
1 parent a7931a4 commit b6e51cc

File tree

7 files changed

+185
-3
lines changed

7 files changed

+185
-3
lines changed

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Breaking Changes
66

77
### New Features and Improvements
8+
* Added `string_value_wo` and `string_value_wo_version` attributes to `databricks_secret` resource ([#5480](https://github.com/databricks/terraform-provider-databricks/pull/5480))
89

910
### Bug Fixes
1011

common/resource.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/resources/secret.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ With this resource you can insert a secret under the provided scope with the giv
77

88
-> This resource can only be used with a workspace-level provider!
99

10+
-> **Note** Write-Only argument string_value_wo is available to use in place of string_value. Write-Only argumentss are supported in HashiCorp Terraform 1.11.0 and later. [Learn more](https://developer.hashicorp.com/terraform/language/manage-sensitive-data/ephemeral#write-only-arguments).
11+
1012
## Example Usage
1113

1214
```hcl
@@ -33,7 +35,9 @@ resource "databricks_cluster" "this" {
3335

3436
The following arguments are required:
3537

36-
* `string_value` - (Required) (String) super secret sensitive value.
38+
* `string_value` - (Optional) (String) Specifies text data that you want to encrypt and store in this version of the secret. This is required if `string_value_wo` is not set.
39+
* `string_value_wo` (Optional) (String) Specifies text data that you want to encrypt and store in this version of the secret. This is required if string_value is not set.
40+
* `string_value_wo_version` (Optional) (Integer) Use together with string_value_wo to trigger an update. Increment this value when an update to string_value_wo is required.
3741
* `scope` - (Required) (String) name of databricks secret scope. Must consist of alphanumeric characters, dashes, underscores, and periods, and may not exceed 128 characters.
3842
* `key` - (Required) (String) key within secret scope. Must consist of alphanumeric characters, dashes, underscores, and periods, and may not exceed 128 characters.
3943
* `provider_config` - (Optional) Configure the provider for management through account provider. This block consists of the following fields:

qa/cty.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package qa
2+
3+
import (
4+
"github.com/hashicorp/go-cty/cty"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
8+
)
9+
10+
// https://github.com/hashicorp/terraform-plugin-sdk/blob/866d0b19a878fe2241fa8e008bee8c6cb8b2c32b/internal/configs/hcl2shim/values.go#L130-L194
11+
func hcl2ValueFromConfigValue(v interface{}) cty.Value {
12+
if v == nil {
13+
return cty.NullVal(cty.DynamicPseudoType)
14+
}
15+
16+
switch tv := v.(type) {
17+
case bool:
18+
return cty.BoolVal(tv)
19+
case string:
20+
return cty.StringVal(tv)
21+
case int:
22+
return cty.NumberIntVal(int64(tv))
23+
case float64:
24+
return cty.NumberFloatVal(tv)
25+
case []interface{}:
26+
vals := make([]cty.Value, len(tv))
27+
for i, ev := range tv {
28+
vals[i] = hcl2ValueFromConfigValue(ev)
29+
}
30+
return cty.TupleVal(vals)
31+
case map[string]interface{}:
32+
vals := map[string]cty.Value{}
33+
for k, ev := range tv {
34+
vals[k] = hcl2ValueFromConfigValue(ev)
35+
}
36+
return cty.ObjectVal(vals)
37+
default:
38+
return cty.NullVal(cty.DynamicPseudoType)
39+
}
40+
}
41+
42+
func makeResourceRawConfig(config *terraform.ResourceConfig, resource *schema.Resource) cty.Value {
43+
original := hcl2ValueFromConfigValue(config.Raw)
44+
coerced, err := resource.CoreConfigSchema().CoerceValue(original)
45+
if err != nil {
46+
return cty.NullVal(cty.DynamicPseudoType)
47+
}
48+
return coerced
49+
}

qa/testing.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,9 @@ func (f ResourceFixture) Apply(t *testing.T) (*schema.ResourceData, error) {
324324
if err != nil {
325325
return nil, err
326326
}
327+
if diff != nil {
328+
diff.RawConfig = makeResourceRawConfig(resourceConfig, resource)
329+
}
327330
if f.Update {
328331
err = f.requiresNew(diff)
329332
if err != nil {

secrets/resource_secret.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"github.com/databricks/databricks-sdk-go/apierr"
99
"github.com/databricks/databricks-sdk-go/service/workspace"
1010
"github.com/databricks/terraform-provider-databricks/common"
11-
11+
"github.com/hashicorp/go-cty/cty"
1212
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1313
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1414
)
@@ -35,16 +35,43 @@ func readSecret(ctx context.Context, w *databricks.WorkspaceClient, scope string
3535
}
3636
}
3737

38+
func getStringValue(d *schema.ResourceData) (string, error) {
39+
woValue, diags := d.GetRawConfigAt(cty.GetAttrPath("string_value_wo"))
40+
if !diags.HasError() && woValue.Type().Equals(cty.String) && !woValue.IsNull() {
41+
return woValue.AsString(), nil
42+
}
43+
if v, ok := d.GetOk("string_value"); ok {
44+
return v.(string), nil
45+
}
46+
return "", fmt.Errorf("failed to get one of attributes `string_value_wo` or `string_value`")
47+
}
48+
3849
// ResourceSecret manages secrets
3950
func ResourceSecret() common.Resource {
4051
p := common.NewPairSeparatedID("scope", "key", "|||")
4152
s := map[string]*schema.Schema{
4253
"string_value": {
4354
Type: schema.TypeString,
4455
ValidateFunc: validation.StringIsNotEmpty,
45-
Required: true,
56+
Optional: true,
4657
ForceNew: true,
4758
Sensitive: true,
59+
ExactlyOneOf: []string{"string_value", "string_value_wo"},
60+
},
61+
"string_value_wo": {
62+
Type: schema.TypeString,
63+
ValidateFunc: validation.StringIsNotEmpty,
64+
Optional: true,
65+
WriteOnly: true,
66+
Sensitive: true,
67+
RequiredWith: []string{"string_value_wo_version"},
68+
ExactlyOneOf: []string{"string_value", "string_value_wo"},
69+
},
70+
"string_value_wo_version": {
71+
Type: schema.TypeInt,
72+
Optional: true,
73+
ForceNew: true,
74+
RequiredWith: []string{"string_value_wo"},
4875
},
4976
"scope": {
5077
Type: schema.TypeString,
@@ -81,6 +108,11 @@ func ResourceSecret() common.Resource {
81108
}
82109
var putSecretReq workspace.PutSecret
83110
common.DataToStructPointer(d, s, &putSecretReq)
111+
stringValue, err := getStringValue(d)
112+
if err != nil {
113+
return err
114+
}
115+
putSecretReq.StringValue = stringValue
84116
err = w.Secrets.PutSecret(ctx, putSecretReq)
85117
if err != nil {
86118
return err

secrets/resource_secret_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"testing"
55

66
"github.com/databricks/databricks-sdk-go/apierr"
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
78

89
"github.com/databricks/databricks-sdk-go/service/workspace"
910

@@ -121,6 +122,95 @@ func TestResourceSecretCreate(t *testing.T) {
121122
assert.Equal(t, "foo|||bar", d.Id())
122123
}
123124

125+
func TestResourceSecretCreate_WriteOnlyValue(t *testing.T) {
126+
d, err := qa.ResourceFixture{
127+
Fixtures: []qa.HTTPFixture{
128+
{
129+
Method: "POST",
130+
Resource: "/api/2.0/secrets/put",
131+
ExpectedRequest: workspace.PutSecret{
132+
StringValue: "SparkIsTh3Be$t",
133+
Scope: "foo",
134+
Key: "bar",
135+
},
136+
},
137+
{
138+
Method: "GET",
139+
Resource: "/api/2.0/secrets/list?scope=foo",
140+
Response: workspace.ListSecretsResponse{
141+
Secrets: []workspace.SecretMetadata{
142+
{
143+
Key: "bar",
144+
LastUpdatedTimestamp: 12345678,
145+
},
146+
},
147+
},
148+
},
149+
},
150+
Resource: ResourceSecret(),
151+
State: map[string]any{
152+
"scope": "foo",
153+
"key": "bar",
154+
"string_value_wo": "SparkIsTh3Be$t",
155+
"string_value_wo_version": 1,
156+
},
157+
Create: true,
158+
}.Apply(t)
159+
assert.NoError(t, err)
160+
assert.Equal(t, "foo|||bar", d.Id())
161+
assert.Equal(t, "", d.Get("string_value"))
162+
}
163+
164+
func TestResourceSecretUpdate_WriteOnlyValueVersionChange(t *testing.T) {
165+
qa.ResourceFixture{
166+
Fixtures: []qa.HTTPFixture{
167+
{
168+
Method: "POST",
169+
Resource: "/api/2.0/secrets/put",
170+
ExpectedRequest: workspace.PutSecret{
171+
StringValue: "SparkIsTh3Be$t-v2",
172+
Scope: "foo",
173+
Key: "bar",
174+
},
175+
},
176+
{
177+
Method: "GET",
178+
Resource: "/api/2.0/secrets/list?scope=foo",
179+
Response: workspace.ListSecretsResponse{
180+
Secrets: []workspace.SecretMetadata{
181+
{
182+
Key: "bar",
183+
LastUpdatedTimestamp: 12345679,
184+
},
185+
},
186+
},
187+
},
188+
},
189+
Resource: ResourceSecret(),
190+
InstanceState: map[string]string{
191+
"scope": "foo",
192+
"key": "bar",
193+
"string_value_wo_version": "1",
194+
},
195+
State: map[string]any{
196+
"scope": "foo",
197+
"key": "bar",
198+
"string_value_wo": "SparkIsTh3Be$t-v2",
199+
"string_value_wo_version": 2,
200+
},
201+
ExpectedDiff: map[string]*terraform.ResourceAttrDiff{
202+
"config_reference": {Old: "", New: "", NewComputed: true, NewRemoved: false, RequiresNew: false, Sensitive: false},
203+
"key": {Old: "bar", New: "bar", NewComputed: false, NewRemoved: false, RequiresNew: false, Sensitive: false},
204+
"last_updated_timestamp": {Old: "", New: "", NewComputed: true, NewRemoved: false, RequiresNew: false, Sensitive: false},
205+
"scope": {Old: "foo", New: "foo", NewComputed: false, NewRemoved: false, RequiresNew: false, Sensitive: false},
206+
"string_value_wo": {Old: "", New: "SparkIsTh3Be$t-v2", NewComputed: false, NewRemoved: false, RequiresNew: false, Sensitive: true},
207+
"string_value_wo_version": {Old: "1", New: "2", NewComputed: false, NewRemoved: false, RequiresNew: true, Sensitive: false},
208+
},
209+
RequiresNew: true,
210+
ID: "foo|||bar",
211+
}.Apply(t)
212+
}
213+
124214
func TestResourceSecretCreate_Error(t *testing.T) {
125215
d, err := qa.ResourceFixture{
126216
Fixtures: []qa.HTTPFixture{

0 commit comments

Comments
 (0)