Skip to content

Commit 7c95019

Browse files
authored
Merge pull request #17 from terraform-providers/paddy_import_generic_secret
Make generic secrets importable.
2 parents 196c63d + 25ae9e9 commit 7c95019

6 files changed

Lines changed: 225 additions & 65 deletions
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package vault
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform/helper/acctest"
7+
"github.com/hashicorp/terraform/helper/resource"
8+
)
9+
10+
func TestAccGenericSecret_importBasic(t *testing.T) {
11+
path := acctest.RandomWithPrefix("secret/test-")
12+
resource.Test(t, resource.TestCase{
13+
PreCheck: func() { testAccPreCheck(t) },
14+
Providers: testProviders,
15+
Steps: []resource.TestStep{
16+
{
17+
Config: testResourceGenericSecret_initialConfig(path),
18+
Check: testResourceGenericSecret_initialCheck(path),
19+
},
20+
{
21+
ResourceName: "vault_generic_secret.test",
22+
ImportState: true,
23+
ImportStateVerify: true,
24+
},
25+
},
26+
})
27+
}

vault/resource_generic_secret.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,16 @@ import (
1212

1313
func genericSecretResource() *schema.Resource {
1414
return &schema.Resource{
15+
SchemaVersion: 1,
16+
1517
Create: genericSecretResourceWrite,
1618
Update: genericSecretResourceWrite,
1719
Delete: genericSecretResourceDelete,
1820
Read: genericSecretResourceRead,
21+
Importer: &schema.ResourceImporter{
22+
State: schema.ImportStatePassthrough,
23+
},
24+
MigrateState: resourceGenericSecretMigrateState,
1925

2026
Schema: map[string]*schema.Schema{
2127
"path": &schema.Schema{
@@ -34,16 +40,23 @@ func genericSecretResource() *schema.Resource {
3440
// We rebuild the attached JSON string to a simple singleline
3541
// string. This makes terraform not want to change when an extra
3642
// space is included in the JSON string. It is also necesarry
37-
// when allow_read is true for comparing values.
43+
// when disable_read is false for comparing values.
3844
StateFunc: NormalizeDataJSON,
3945
ValidateFunc: ValidateDataJSON,
4046
},
4147

4248
"allow_read": &schema.Schema{
49+
Type: schema.TypeBool,
50+
Optional: true,
51+
Description: "Attempt to read the token from Vault if true; if false, drift won't be detected.",
52+
Deprecated: "Please use disable_read instead.",
53+
},
54+
55+
"disable_read": &schema.Schema{
4356
Type: schema.TypeBool,
4457
Optional: true,
4558
Default: false,
46-
Description: "True if the provided token is allowed to read the secret from vault",
59+
Description: "Don't attempt to read the token from Vault if true; drift won't be detected.",
4760
},
4861
},
4962
}
@@ -99,7 +112,7 @@ func genericSecretResourceWrite(d *schema.ResourceData, meta interface{}) error
99112

100113
d.SetId(path)
101114

102-
return nil
115+
return genericSecretResourceRead(d, meta)
103116
}
104117

105118
func genericSecretResourceDelete(d *schema.ResourceData, meta interface{}) error {
@@ -117,10 +130,16 @@ func genericSecretResourceDelete(d *schema.ResourceData, meta interface{}) error
117130
}
118131

119132
func genericSecretResourceRead(d *schema.ResourceData, meta interface{}) error {
120-
allowed_to_read := d.Get("allow_read").(bool)
121-
path := d.Get("path").(string)
133+
shouldRead := !d.Get("disable_read").(bool)
134+
if !shouldRead {
135+
// if disable_read is set to false or unset (we can't know which)
136+
// and allow_read is set to true, go with allow_read.
137+
shouldRead = d.Get("allow_read").(bool)
138+
}
139+
140+
path := d.Id()
122141

123-
if allowed_to_read {
142+
if shouldRead {
124143
client := meta.(*api.Client)
125144

126145
log.Printf("[DEBUG] Reading %s from Vault", path)
@@ -129,15 +148,17 @@ func genericSecretResourceRead(d *schema.ResourceData, meta interface{}) error {
129148
return fmt.Errorf("error reading from Vault: %s", err)
130149
}
131150

151+
log.Printf("[DEBUG] secret: %#v", secret)
152+
132153
jsonDataBytes, err := json.Marshal(secret.Data)
133154
if err != nil {
134155
return fmt.Errorf("Error marshaling JSON for %q: %s", path, err)
135156
}
136157
d.Set("data_json", string(jsonDataBytes))
158+
d.Set("path", path)
137159
} else {
138-
log.Printf("[WARN] vault_generic_secret does not automatically refresh if allow_read is set to false")
160+
log.Printf("[WARN] vault_generic_secret does not refresh when disable_read is set to true")
139161
}
140-
141-
d.SetId(path)
162+
d.Set("disable_read", !shouldRead)
142163
return nil
143164
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package vault
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"github.com/hashicorp/terraform/terraform"
8+
)
9+
10+
func resourceGenericSecretMigrateState(v int, s *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
11+
if s.Empty() {
12+
log.Println("[DEBUG] Empty InstanceState; nothing to migrate.")
13+
return s, nil
14+
}
15+
16+
switch v {
17+
case 0:
18+
log.Println("[INFO] Found Vault Generic Secret state v0; migrating to v1")
19+
s, err := migrateGenericSecretStateV0toV1(s)
20+
return s, err
21+
default:
22+
return s, fmt.Errorf("Unexpected schema version: %d", v)
23+
}
24+
}
25+
26+
func migrateGenericSecretStateV0toV1(s *terraform.InstanceState) (*terraform.InstanceState, error) {
27+
log.Printf("[DEBUG] Attributes before migration: %#v", s.Attributes)
28+
29+
disabledRead := s.Attributes["allow_read"] != "true"
30+
if disabledRead {
31+
s.Attributes["disable_read"] = "true"
32+
}
33+
34+
log.Printf("[DEBUG] Attributes after migration: %#v:", s.Attributes)
35+
return s, nil
36+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package vault
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform/terraform"
7+
)
8+
9+
func TestGenericSecretMigrateState(t *testing.T) {
10+
cases := map[string]struct {
11+
StateVersion int
12+
Attributes map[string]string
13+
Expected map[string]string
14+
}{
15+
"unset allow_read to disable_read": {
16+
StateVersion: 0,
17+
Attributes: map[string]string{
18+
"data_json": `{"hello": "world"}`,
19+
"path": "secret/test-123",
20+
},
21+
Expected: map[string]string{
22+
"data_json": `{"hello": "world"}`,
23+
"path": "secret/test-123",
24+
},
25+
},
26+
"allow_read false to disable_read": {
27+
StateVersion: 0,
28+
Attributes: map[string]string{
29+
"data_json": `{"hello": "world"}`,
30+
"path": "secret/test-123",
31+
"allow_read": "false",
32+
},
33+
Expected: map[string]string{
34+
"data_json": `{"hello": "world"}`,
35+
"path": "secret/test-123",
36+
"disable_read": "true",
37+
},
38+
},
39+
"allow_read true to disable_read": {
40+
StateVersion: 0,
41+
Attributes: map[string]string{
42+
"data_json": `{"hello": "world"}`,
43+
"path": "secret/test-123",
44+
"allow_read": "true",
45+
},
46+
Expected: map[string]string{
47+
"data_json": `{"hello": "world"}`,
48+
"path": "secret/test-123",
49+
},
50+
},
51+
}
52+
53+
for tn, tc := range cases {
54+
is, err := resourceGenericSecretMigrateState(
55+
tc.StateVersion, &terraform.InstanceState{
56+
ID: tc.Attributes["path"],
57+
Attributes: tc.Attributes,
58+
}, nil)
59+
60+
if err != nil {
61+
t.Fatalf("Unexpected error for migration %q: %+v", tn, err)
62+
}
63+
64+
for k, v := range tc.Expected {
65+
if is.Attributes[k] != v {
66+
t.Fatalf("Expected %q to be %v for %q, got %v", k, v, tn, is.Attributes[k])
67+
}
68+
}
69+
}
70+
}

vault/resource_generic_secret_test.go

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,81 +4,83 @@ import (
44
"fmt"
55
"testing"
66

7-
r "github.com/hashicorp/terraform/helper/resource"
7+
"github.com/hashicorp/terraform/helper/acctest"
8+
"github.com/hashicorp/terraform/helper/resource"
89
"github.com/hashicorp/terraform/terraform"
910

1011
"github.com/hashicorp/vault/api"
1112
)
1213

1314
func TestResourceGenericSecret(t *testing.T) {
14-
r.Test(t, r.TestCase{
15+
path := acctest.RandomWithPrefix("secret/test")
16+
resource.Test(t, resource.TestCase{
1517
Providers: testProviders,
1618
PreCheck: func() { testAccPreCheck(t) },
17-
Steps: []r.TestStep{
18-
r.TestStep{
19-
Config: testResourceGenericSecret_initialConfig,
20-
Check: testResourceGenericSecret_initialCheck,
19+
Steps: []resource.TestStep{
20+
resource.TestStep{
21+
Config: testResourceGenericSecret_initialConfig(path),
22+
Check: testResourceGenericSecret_initialCheck(path),
2123
},
22-
r.TestStep{
24+
resource.TestStep{
2325
Config: testResourceGenericSecret_updateConfig,
2426
Check: testResourceGenericSecret_updateCheck,
2527
},
2628
},
2729
})
2830
}
2931

30-
var testResourceGenericSecret_initialConfig = `
31-
32+
func testResourceGenericSecret_initialConfig(path string) string {
33+
return fmt.Sprintf(`
3234
resource "vault_generic_secret" "test" {
33-
path = "secret/foo"
34-
allow_read = true
35+
path = "%s"
3536
data_json = <<EOT
3637
{
3738
"zip": "zap"
3839
}
3940
EOT
41+
}`, path)
4042
}
4143

42-
`
43-
44-
func testResourceGenericSecret_initialCheck(s *terraform.State) error {
45-
resourceState := s.Modules[0].Resources["vault_generic_secret.test"]
46-
if resourceState == nil {
47-
return fmt.Errorf("resource not found in state")
48-
}
49-
50-
instanceState := resourceState.Primary
51-
if instanceState == nil {
52-
return fmt.Errorf("resource has no primary instance")
53-
}
54-
55-
path := instanceState.ID
56-
57-
if path != instanceState.Attributes["path"] {
58-
return fmt.Errorf("id doesn't match path")
44+
func testResourceGenericSecret_initialCheck(expectedPath string) resource.TestCheckFunc {
45+
return func(s *terraform.State) error {
46+
resourceState := s.Modules[0].Resources["vault_generic_secret.test"]
47+
if resourceState == nil {
48+
return fmt.Errorf("resource not found in state")
49+
}
50+
51+
instanceState := resourceState.Primary
52+
if instanceState == nil {
53+
return fmt.Errorf("resource has no primary instance")
54+
}
55+
56+
path := instanceState.ID
57+
58+
if path != instanceState.Attributes["path"] {
59+
return fmt.Errorf("id doesn't match path")
60+
}
61+
if path != expectedPath {
62+
return fmt.Errorf("unexpected secret path")
63+
}
64+
65+
client := testProvider.Meta().(*api.Client)
66+
secret, err := client.Logical().Read(path)
67+
if err != nil {
68+
return fmt.Errorf("error reading back secret: %s", err)
69+
}
70+
71+
if got, want := secret.Data["zip"], "zap"; got != want {
72+
return fmt.Errorf("'zip' data is %q; want %q", got, want)
73+
}
74+
75+
return nil
5976
}
60-
if path != "secret/foo" {
61-
return fmt.Errorf("unexpected secret path")
62-
}
63-
64-
client := testProvider.Meta().(*api.Client)
65-
secret, err := client.Logical().Read(path)
66-
if err != nil {
67-
return fmt.Errorf("error reading back secret: %s", err)
68-
}
69-
70-
if got, want := secret.Data["zip"], "zap"; got != want {
71-
return fmt.Errorf("'zip' data is %q; want %q", got, want)
72-
}
73-
74-
return nil
7577
}
7678

7779
var testResourceGenericSecret_updateConfig = `
7880
7981
resource "vault_generic_secret" "test" {
8082
path = "secret/foo"
81-
allow_read = true
83+
disable_read = false
8284
data_json = <<EOT
8385
{
8486
"zip": "zoop"

website/docs/r/generic_secret.html.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,22 @@ EOT
4242

4343
The following arguments are supported:
4444

45-
* `path` - (Required) The full logical path at which to write the given
46-
data. To write data into the "generic" secret backend mounted in Vault by
47-
default, this should be prefixed with `secret/`. Writing to other backends
48-
with this resource is possible; consult each backend's documentation to
49-
see which endpoints support the `PUT` and `DELETE` methods.
50-
51-
* `data_json` - (Required) String containing a JSON-encoded object that
52-
will be written as the secret data at the given path.
53-
54-
* `allow_read` - (Optional) True/false. Set this to true if your vault
55-
authentication is able to read the data, this allows the resource to be
56-
compared and updated. Defaults to false.
45+
* `path` - (Required) The full logical path at which to write the given data.
46+
To write data into the "generic" secret backend mounted in Vault by default,
47+
this should be prefixed with `secret/`. Writing to other backends with this
48+
resource is possible; consult each backend's documentation to see which
49+
endpoints support the `PUT` and `DELETE` methods.
50+
51+
* `data_json` - (Required) String containing a JSON-encoded object that will be
52+
written as the secret data at the given path.
53+
54+
* `allow_read` - (Optional, Deprecated) True/false. Set this to true if your
55+
vault authentication is able to read the data, this allows the resource to be
56+
compared and updated. Defaults to false.
57+
58+
* `disable_read` - (Optional) True/false. Set this to true if your vault
59+
authentication is not able to read the data. Setting this to `true` will
60+
break drift detection. Defaults to false.
5761

5862
## Required Vault Capabilities
5963

0 commit comments

Comments
 (0)