@@ -5,14 +5,24 @@ import (
55 "encoding/json"
66 "fmt"
77 "log"
8+ "strings"
9+ "time"
810
911 "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1012 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
13+ "github.com/hashicorp/vault/api"
1114
1215 "github.com/hashicorp/terraform-provider-vault/internal/consts"
1316 "github.com/hashicorp/terraform-provider-vault/internal/provider"
1417)
1518
19+ var kvMetadataFields = map [string ]string {
20+ consts .FieldMaxVersions : consts .FieldMaxVersions ,
21+ consts .FieldCASRequired : consts .FieldCASRequired ,
22+ consts .FieldDeleteVersionAfter : consts .FieldDeleteVersionAfter ,
23+ consts .FieldCustomMetadata : consts .FieldData ,
24+ }
25+
1626func kvSecretV2Resource (name string ) * schema.Resource {
1727 return & schema.Resource {
1828 CreateContext : kvSecretV2Write ,
@@ -100,6 +110,41 @@ func kvSecretV2Resource(name string) *schema.Resource {
100110 Default : false ,
101111 Description : "If set to true, permanently deletes all versions for the specified key." ,
102112 },
113+
114+ consts .FieldCustomMetadata : {
115+ Type : schema .TypeList ,
116+ Optional : true ,
117+ Computed : true ,
118+ Description : "Custom metadata to be set for the secret." ,
119+ Elem : & schema.Resource {
120+ Schema : map [string ]* schema.Schema {
121+ consts .FieldMaxVersions : {
122+ Type : schema .TypeInt ,
123+ Optional : true ,
124+ Description : "The number of versions to keep per key." ,
125+ },
126+ consts .FieldCASRequired : {
127+ Type : schema .TypeBool ,
128+ Optional : true ,
129+ Description : "If true, all keys will require the cas " +
130+ "parameter to be set on all write requests." ,
131+ },
132+ consts .FieldDeleteVersionAfter : {
133+ Type : schema .TypeInt ,
134+ Optional : true ,
135+ Description : "If set, specifies the length of time before " +
136+ "a version is deleted." ,
137+ },
138+ consts .FieldData : {
139+ Type : schema .TypeMap ,
140+ Optional : true ,
141+ Description : "A map of arbitrary string to string valued " +
142+ "user-provided metadata meant to describe the secret." ,
143+ },
144+ },
145+ },
146+ MaxItems : 1 ,
147+ },
103148 },
104149 }
105150}
@@ -108,6 +153,20 @@ func getKVV2Path(mount, name, prefix string) string {
108153 return fmt .Sprintf ("%s/%s/%s" , mount , prefix , name )
109154}
110155
156+ func getCustomMetadata (d * schema.ResourceData ) map [string ]interface {} {
157+ data := map [string ]interface {}{}
158+
159+ fieldPrefix := fmt .Sprintf ("%s.0" , consts .FieldCustomMetadata )
160+ for vaultKey , stateKey := range kvMetadataFields {
161+ fieldKey := fmt .Sprintf ("%s.%s" , fieldPrefix , stateKey )
162+
163+ if val , ok := d .GetOk (fieldKey ); ok {
164+ data [vaultKey ] = val
165+ }
166+ }
167+ return data
168+ }
169+
111170func kvSecretV2Write (ctx context.Context , d * schema.ResourceData , meta interface {}) diag.Diagnostics {
112171 client , e := provider .GetClient (d , meta )
113172 if e != nil {
@@ -140,6 +199,17 @@ func kvSecretV2Write(ctx context.Context, d *schema.ResourceData, meta interface
140199
141200 d .SetId (path )
142201
202+ // Write custom metadata for secret if provided
203+ if _ , ok := d .GetOk (consts .FieldCustomMetadata ); ok {
204+ cm := getCustomMetadata (d )
205+
206+ metadataPath := getKVV2Path (mount , name , consts .FieldMetadata )
207+ log .Printf ("[DEBUG] Writing custom metadata for secret at %s" , path )
208+ if _ , err := client .Logical ().Write (metadataPath , cm ); err != nil {
209+ return diag .Errorf ("error writing custom metadata to %s, err=%s" , metadataPath , err )
210+ }
211+ }
212+
143213 return kvSecretV2Read (ctx , d , meta )
144214}
145215
@@ -184,13 +254,63 @@ func kvSecretV2Read(_ context.Context, d *schema.ResourceData, meta interface{})
184254 if err := d .Set (consts .FieldMetadata , serializeDataMapToString (v )); err != nil {
185255 return diag .FromErr (err )
186256 }
257+
258+ // Read & Set custom metadata
259+ if _ , ok := v [consts .FieldCustomMetadata ]; ok {
260+ cm , err := readKVV2Metadata (d , client )
261+ if err != nil {
262+ return diag .FromErr (err )
263+ }
264+
265+ if err := d .Set (consts .FieldCustomMetadata , []interface {}{cm }); err != nil {
266+ return diag .FromErr (err )
267+ }
268+ }
187269 }
188270 }
271+
189272 }
190273
191274 return nil
192275}
193276
277+ func readKVV2Metadata (d * schema.ResourceData , client * api.Client ) (map [string ]interface {}, error ) {
278+ path := strings .Replace (d .Id (), consts .FieldData , consts .FieldMetadata , 1 )
279+
280+ log .Printf ("[DEBUG] Reading metadata for KVV2 secret at %s" , path )
281+ resp , err := client .Logical ().Read (path )
282+ if err != nil {
283+ return nil , err
284+ }
285+
286+ if resp == nil {
287+ log .Printf ("[DEBUG] no metadata found for secret" )
288+ return nil , nil
289+ }
290+
291+ data := map [string ]interface {}{}
292+
293+ for vaultKey , tfKey := range kvMetadataFields {
294+ if val , ok := resp .Data [vaultKey ]; ok {
295+ // the delete_version_after field is written to
296+ // Vault as an integer but is returned as a string
297+ // of the format "3h12m10s"
298+ if vaultKey == consts .FieldDeleteVersionAfter {
299+ t , err := time .ParseDuration (val .(string ))
300+ if err != nil {
301+ return nil , fmt .Errorf ("error parsing duration, err=%s" , err )
302+ }
303+ val = t .Seconds ()
304+ }
305+
306+ data [tfKey ] = val
307+
308+ }
309+ }
310+
311+ return data , nil
312+ }
313+
194314func kvSecretV2Delete (_ context.Context , d * schema.ResourceData , meta interface {}) diag.Diagnostics {
195315 client , e := provider .GetClient (d , meta )
196316 if e != nil {
0 commit comments