Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Breaking Changes

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

* Added `api` field to dual account/workspace resources (`databricks_user`, `databricks_service_principal`, `databricks_group`, `databricks_group_role`, `databricks_group_member`, `databricks_user_role`, `databricks_service_principal_role`, `databricks_user_instance_profile`, `databricks_group_instance_profile`, `databricks_metastore`, `databricks_metastore_assignment`, `databricks_metastore_data_access`, `databricks_storage_credential`, `databricks_service_principal_secret`, `databricks_access_control_rule_set`) to explicitly control whether account-level or workspace-level APIs are used. This enables support for unified hosts like `api.databricks.com` where the API level cannot be inferred from the host ([#5483](https://github.com/databricks/terraform-provider-databricks/pull/5483)).

Expand Down
3 changes: 3 additions & 0 deletions common/resource.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion docs/resources/secret.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ With this resource you can insert a secret under the provided scope with the giv

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

-> **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).

## Example Usage

```hcl
Expand All @@ -33,7 +35,9 @@ resource "databricks_cluster" "this" {

The following arguments are required:

* `string_value` - (Required) (String) super secret sensitive value.
* `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.
* `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.
* `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.
* `scope` - (Required) (String) name of databricks secret scope. Must consist of alphanumeric characters, dashes, underscores, and periods, and may not exceed 128 characters.
* `key` - (Required) (String) key within secret scope. Must consist of alphanumeric characters, dashes, underscores, and periods, and may not exceed 128 characters.
* `provider_config` - (Optional) Configure the provider for management through account provider. This block consists of the following fields:
Expand Down
49 changes: 49 additions & 0 deletions qa/cty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package qa

import (
"github.com/hashicorp/go-cty/cty"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

// https://github.com/hashicorp/terraform-plugin-sdk/blob/866d0b19a878fe2241fa8e008bee8c6cb8b2c32b/internal/configs/hcl2shim/values.go#L130-L194
func hcl2ValueFromConfigValue(v interface{}) cty.Value {
if v == nil {
return cty.NullVal(cty.DynamicPseudoType)
}

switch tv := v.(type) {
case bool:
return cty.BoolVal(tv)
case string:
return cty.StringVal(tv)
case int:
return cty.NumberIntVal(int64(tv))
case float64:
return cty.NumberFloatVal(tv)
case []interface{}:
vals := make([]cty.Value, len(tv))
for i, ev := range tv {
vals[i] = hcl2ValueFromConfigValue(ev)
}
return cty.TupleVal(vals)
case map[string]interface{}:
vals := map[string]cty.Value{}
for k, ev := range tv {
vals[k] = hcl2ValueFromConfigValue(ev)
}
return cty.ObjectVal(vals)
default:
return cty.NullVal(cty.DynamicPseudoType)
}
}

func makeResourceRawConfig(config *terraform.ResourceConfig, resource *schema.Resource) cty.Value {
original := hcl2ValueFromConfigValue(config.Raw)
coerced, err := resource.CoreConfigSchema().CoerceValue(original)
if err != nil {
return cty.NullVal(cty.DynamicPseudoType)
}
return coerced
}
3 changes: 3 additions & 0 deletions qa/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@ func (f ResourceFixture) Apply(t *testing.T) (*schema.ResourceData, error) {
if err != nil {
return nil, err
}
if diff != nil {
diff.RawConfig = makeResourceRawConfig(resourceConfig, resource)
}
if f.Update {
err = f.requiresNew(diff)
if err != nil {
Expand Down
36 changes: 34 additions & 2 deletions secrets/resource_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/databricks/databricks-sdk-go/apierr"
"github.com/databricks/databricks-sdk-go/service/workspace"
"github.com/databricks/terraform-provider-databricks/common"

"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
Expand All @@ -35,16 +35,43 @@ func readSecret(ctx context.Context, w *databricks.WorkspaceClient, scope string
}
}

func getStringValue(d *schema.ResourceData) (string, error) {
woValue, diags := d.GetRawConfigAt(cty.GetAttrPath("string_value_wo"))
if !diags.HasError() && woValue.Type().Equals(cty.String) && !woValue.IsNull() {
return woValue.AsString(), nil
}
if v, ok := d.GetOk("string_value"); ok {
return v.(string), nil
}
return "", fmt.Errorf("failed to get one of attributes `string_value_wo` or `string_value`")
}

// ResourceSecret manages secrets
func ResourceSecret() common.Resource {
p := common.NewPairSeparatedID("scope", "key", "|||")
s := map[string]*schema.Schema{
"string_value": {
Type: schema.TypeString,
ValidateFunc: validation.StringIsNotEmpty,
Required: true,
Optional: true,
ForceNew: true,
Sensitive: true,
ExactlyOneOf: []string{"string_value", "string_value_wo"},
},
"string_value_wo": {
Type: schema.TypeString,
ValidateFunc: validation.StringIsNotEmpty,
Optional: true,
WriteOnly: true,
Sensitive: true,
RequiredWith: []string{"string_value_wo_version"},
ExactlyOneOf: []string{"string_value", "string_value_wo"},
},
"string_value_wo_version": {
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
RequiredWith: []string{"string_value_wo"},
},
"scope": {
Type: schema.TypeString,
Expand Down Expand Up @@ -81,6 +108,11 @@ func ResourceSecret() common.Resource {
}
var putSecretReq workspace.PutSecret
common.DataToStructPointer(d, s, &putSecretReq)
stringValue, err := getStringValue(d)
if err != nil {
return err
}
putSecretReq.StringValue = stringValue
err = w.Secrets.PutSecret(ctx, putSecretReq)
if err != nil {
return err
Expand Down
90 changes: 90 additions & 0 deletions secrets/resource_secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/databricks/databricks-sdk-go/apierr"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"

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

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

func TestResourceSecretCreate_WriteOnlyValue(t *testing.T) {
d, err := qa.ResourceFixture{
Fixtures: []qa.HTTPFixture{
{
Method: "POST",
Resource: "/api/2.0/secrets/put",
ExpectedRequest: workspace.PutSecret{
StringValue: "SparkIsTh3Be$t",
Scope: "foo",
Key: "bar",
},
},
{
Method: "GET",
Resource: "/api/2.0/secrets/list?scope=foo",
Response: workspace.ListSecretsResponse{
Secrets: []workspace.SecretMetadata{
{
Key: "bar",
LastUpdatedTimestamp: 12345678,
},
},
},
},
},
Resource: ResourceSecret(),
State: map[string]any{
"scope": "foo",
"key": "bar",
"string_value_wo": "SparkIsTh3Be$t",
"string_value_wo_version": 1,
},
Create: true,
}.Apply(t)
assert.NoError(t, err)
assert.Equal(t, "foo|||bar", d.Id())
assert.Equal(t, "", d.Get("string_value"))
}

func TestResourceSecretUpdate_WriteOnlyValueVersionChange(t *testing.T) {
qa.ResourceFixture{
Fixtures: []qa.HTTPFixture{
{
Method: "POST",
Resource: "/api/2.0/secrets/put",
ExpectedRequest: workspace.PutSecret{
StringValue: "SparkIsTh3Be$t-v2",
Scope: "foo",
Key: "bar",
},
},
{
Method: "GET",
Resource: "/api/2.0/secrets/list?scope=foo",
Response: workspace.ListSecretsResponse{
Secrets: []workspace.SecretMetadata{
{
Key: "bar",
LastUpdatedTimestamp: 12345679,
},
},
},
},
},
Resource: ResourceSecret(),
InstanceState: map[string]string{
"scope": "foo",
"key": "bar",
"string_value_wo_version": "1",
},
State: map[string]any{
"scope": "foo",
"key": "bar",
"string_value_wo": "SparkIsTh3Be$t-v2",
"string_value_wo_version": 2,
},
ExpectedDiff: map[string]*terraform.ResourceAttrDiff{
"config_reference": {Old: "", New: "", NewComputed: true, NewRemoved: false, RequiresNew: false, Sensitive: false},
"key": {Old: "bar", New: "bar", NewComputed: false, NewRemoved: false, RequiresNew: false, Sensitive: false},
"last_updated_timestamp": {Old: "", New: "", NewComputed: true, NewRemoved: false, RequiresNew: false, Sensitive: false},
"scope": {Old: "foo", New: "foo", NewComputed: false, NewRemoved: false, RequiresNew: false, Sensitive: false},
"string_value_wo": {Old: "", New: "SparkIsTh3Be$t-v2", NewComputed: false, NewRemoved: false, RequiresNew: false, Sensitive: true},
"string_value_wo_version": {Old: "1", New: "2", NewComputed: false, NewRemoved: false, RequiresNew: true, Sensitive: false},
},
RequiresNew: true,
ID: "foo|||bar",
}.Apply(t)
}

func TestResourceSecretCreate_Error(t *testing.T) {
d, err := qa.ResourceFixture{
Fixtures: []qa.HTTPFixture{
Expand Down
Loading