Skip to content

Commit c5a087a

Browse files
commjoenmp911de
authored andcommitted
Add support for custom kv2 metadata.
Original pull request: gh-808 Closes gh-789
1 parent 5cc5254 commit c5a087a

File tree

6 files changed

+129
-13
lines changed

6 files changed

+129
-13
lines changed

spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueMetadataTemplate.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ private static VaultMetadataResponse fromMap(Map<String, Object> metadataRespons
9898
.oldestVersion(Integer.parseInt(String.valueOf(metadataResponse.get("oldest_version"))))
9999
.updatedTime(toInstant((String) metadataResponse.get("updated_time")))
100100
.versions(buildVersions((Map) metadataResponse.get("versions")))
101+
.customMetadata((Map) metadataResponse.get("custom_metadata"))
101102
.build();
102103
}
103104

@@ -115,13 +116,18 @@ private static Versioned.Metadata buildVersion(String version, Map<String, Objec
115116
Instant deletionTime = toInstant((String) versionData.get("deletion_time"));
116117
boolean destroyed = (Boolean) versionData.get("destroyed");
117118
Versioned.Version kvVersion = Versioned.Version.from(Integer.parseInt(version));
118-
119-
return Versioned.Metadata.builder()
119+
Versioned.Metadata.MetadataBuilder builder = Versioned.Metadata.builder()
120120
.createdAt(createdTime)
121121
.deletedAt(deletionTime)
122122
.destroyed(destroyed)
123-
.version(kvVersion)
124-
.build();
123+
.version(kvVersion);
124+
125+
if (versionData.get("custom_metadata") != null) {
126+
Map<String, String> customMetadata = (Map<String, String>) versionData.get("custom_metadata");
127+
builder.customMetadata(customMetadata);
128+
}
129+
130+
return builder.build();
125131
}
126132

127133
@Nullable

spring-vault-core/src/main/java/org/springframework/vault/core/VaultVersionedKeyValueTemplate.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ private static Metadata getMetadata(Map<String, Object> responseMetadata) {
178178
Integer version = (Integer) responseMetadata.get("version");
179179
builder.version(Version.from(version));
180180

181+
if (responseMetadata.get("custom_metadata") != null) {
182+
Map<String, String> customMetadata = (Map<String, String>) responseMetadata.get("custom_metadata");
183+
builder.customMetadata(customMetadata);
184+
}
185+
181186
return builder.build();
182187
}
183188

spring-vault-core/src/main/java/org/springframework/vault/support/VaultMetadataRequest.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.vault.support;
1717

1818
import java.time.Duration;
19+
import java.util.Map;
1920

2021
import com.fasterxml.jackson.annotation.JsonProperty;
2122

@@ -41,11 +42,16 @@ public class VaultMetadataRequest {
4142
@JsonProperty("delete_version_after")
4243
private final String deleteVersionAfter;
4344

44-
private VaultMetadataRequest(int maxVersions, boolean casRequired, @Nullable Duration deleteVersionAfter) {
45+
@JsonProperty("custom_metadata")
46+
private final Map<String, String> customMetadata;
47+
48+
private VaultMetadataRequest(int maxVersions, boolean casRequired, @Nullable Duration deleteVersionAfter,
49+
@Nullable Map<String, String> customMetadata) {
4550
this.maxVersions = maxVersions;
4651
this.casRequired = casRequired;
4752
this.deleteVersionAfter = DurationParser
4853
.formatDuration(deleteVersionAfter != null ? deleteVersionAfter : Duration.ZERO);
54+
this.customMetadata = customMetadata;
4955
}
5056

5157
public static VaultMetadataRequestBuilder builder() {
@@ -75,6 +81,11 @@ public String getDeleteVersionAfter() {
7581
return this.deleteVersionAfter;
7682
}
7783

84+
@Nullable
85+
public Map<String, String> getCustomMetadata() {
86+
return this.customMetadata;
87+
}
88+
7889
public static class VaultMetadataRequestBuilder {
7990

8091
private int maxVersions;
@@ -84,6 +95,9 @@ public static class VaultMetadataRequestBuilder {
8495
@Nullable
8596
private Duration deleteVersionAfter;
8697

98+
@Nullable
99+
private Map<String, String> customMetadata;
100+
87101
/**
88102
* Set the number of versions to keep per key.
89103
* @param maxVersions
@@ -115,11 +129,22 @@ public VaultMetadataRequestBuilder deleteVersionAfter(Duration deleteVersionAfte
115129
return this;
116130
}
117131

132+
/**
133+
* Sets the custom Metadata for the metadatarequest
134+
* @param customMetadata
135+
* @return {@link VaultMetadataRequest}
136+
*/
137+
public VaultMetadataRequestBuilder customMetadata(Map<String, String> customMetadata) {
138+
this.customMetadata = customMetadata;
139+
return this;
140+
}
141+
118142
/**
119143
* @return a new {@link VaultMetadataRequest}
120144
*/
121145
public VaultMetadataRequest build() {
122-
return new VaultMetadataRequest(this.maxVersions, this.casRequired, this.deleteVersionAfter);
146+
return new VaultMetadataRequest(this.maxVersions, this.casRequired, this.deleteVersionAfter,
147+
this.customMetadata);
123148
}
124149

125150
}

spring-vault-core/src/main/java/org/springframework/vault/support/VaultMetadataResponse.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.time.Instant;
2020
import java.time.Period;
2121
import java.util.List;
22+
import java.util.Map;
2223

2324
import org.springframework.lang.Nullable;
2425

@@ -46,9 +47,11 @@ public class VaultMetadataResponse {
4647

4748
private final List<Versioned.Metadata> versions;
4849

50+
private final Map<String, String> customMetadata;
51+
4952
private VaultMetadataResponse(boolean casRequired, Instant createdTime, int currentVersion,
5053
Duration deleteVersionAfter, int maxVersions, int oldestVersion, Instant updatedTime,
51-
List<Versioned.Metadata> versions) {
54+
List<Versioned.Metadata> versions, Map<String, String> customMetadata) {
5255
this.casRequired = casRequired;
5356
this.createdTime = createdTime;
5457
this.currentVersion = currentVersion;
@@ -57,6 +60,7 @@ private VaultMetadataResponse(boolean casRequired, Instant createdTime, int curr
5760
this.oldestVersion = oldestVersion;
5861
this.updatedTime = updatedTime;
5962
this.versions = versions;
63+
this.customMetadata = customMetadata;
6064
}
6165

6266
public static VaultMetadataResponseBuilder builder() {
@@ -93,6 +97,14 @@ public Duration getDeleteVersionAfter() {
9397
return this.deleteVersionAfter;
9498
}
9599

100+
/**
101+
* @return KV of customMetadata. Entries can be any arbitrary key-value pairs
102+
*/
103+
@Nullable
104+
public Map<String, String> getCustomMetadata() {
105+
return this.customMetadata;
106+
}
107+
96108
/**
97109
* @return max secret versions accepted by this key
98110
*/
@@ -145,6 +157,8 @@ public static class VaultMetadataResponseBuilder {
145157

146158
private List<Versioned.Metadata> versions;
147159

160+
private Map<String, String> customMetadata;
161+
148162
public VaultMetadataResponseBuilder casRequired(boolean casRequired) {
149163
this.casRequired = casRequired;
150164
return this;
@@ -185,9 +199,15 @@ public VaultMetadataResponseBuilder versions(List<Versioned.Metadata> versions)
185199
return this;
186200
}
187201

202+
public VaultMetadataResponseBuilder customMetadata(Map<String, String> customMetadata) {
203+
this.customMetadata = customMetadata;
204+
return this;
205+
}
206+
188207
public VaultMetadataResponse build() {
189208
return new VaultMetadataResponse(this.casRequired, this.createdTime, this.currentVersion,
190-
this.deleteVersionAfter, this.maxVersions, this.oldestVersion, this.updatedTime, this.versions);
209+
this.deleteVersionAfter, this.maxVersions, this.oldestVersion, this.updatedTime, this.versions,
210+
this.customMetadata);
191211
}
192212

193213
}

spring-vault-core/src/main/java/org/springframework/vault/support/Versioned.java

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.vault.support;
1717

1818
import java.time.Instant;
19+
import java.util.Map;
1920
import java.util.Objects;
2021
import java.util.Optional;
2122

@@ -32,7 +33,7 @@
3233
* <li>Versioned secrets with {@link Metadata} attached
3334
* {@link Versioned#create(Object, Metadata)}</li>
3435
* </ul>
35-
*
36+
* <p>
3637
* Versioned secrets follow a lifecycle that spans from creation to destruction:
3738
*
3839
* <ol>
@@ -44,9 +45,9 @@
4445
* </ol>
4546
*
4647
* @author Mark Paluch
47-
* @since 2.1
4848
* @see Version
4949
* @see Metadata
50+
* @since 2.1
5051
*/
5152
public class Versioned<T> {
5253

@@ -223,11 +224,15 @@ public static class Metadata {
223224

224225
private final Version version;
225226

226-
private Metadata(Instant createdAt, @Nullable Instant deletedAt, boolean destroyed, Version version) {
227+
private final @Nullable Map<String, String> customMetadata;
228+
229+
private Metadata(Instant createdAt, @Nullable Instant deletedAt, boolean destroyed, Version version,
230+
@Nullable Map<String, String> customMetadata) {
227231
this.createdAt = createdAt;
228232
this.deletedAt = deletedAt;
229233
this.destroyed = destroyed;
230234
this.version = version;
235+
this.customMetadata = customMetadata;
231236
}
232237

233238
/**
@@ -275,11 +280,29 @@ public boolean isDestroyed() {
275280
return this.destroyed;
276281
}
277282

283+
/**
284+
* @return Metadata .
285+
*/
286+
@Nullable
287+
public Map<String, String> getCustomMetadata() {
288+
return customMetadata;
289+
}
290+
278291
@Override
279292
public String toString() {
280293

294+
String customMetadataString = "";
295+
if (customMetadata != null && customMetadata.keySet().size() > 0) {
296+
StringBuilder metadataPrintBuilder = new StringBuilder(", customMetadata=Map[");
297+
for (String key : customMetadata.keySet()) {
298+
metadataPrintBuilder.append(key).append(":").append(customMetadata.get(key)).append(" ");
299+
}
300+
metadataPrintBuilder.append("]");
301+
customMetadataString = metadataPrintBuilder.toString();
302+
}
303+
281304
return getClass().getSimpleName() + " [createdAt=" + this.createdAt + ", deletedAt=" + this.deletedAt
282-
+ ", destroyed=" + this.destroyed + ", version=" + this.version + ']';
305+
+ ", destroyed=" + this.destroyed + ", version=" + this.version + customMetadataString + ']';
283306
}
284307

285308
/**
@@ -295,6 +318,8 @@ public static class MetadataBuilder {
295318

296319
private @Nullable Version version;
297320

321+
private @Nullable Map<String, String> customMetadata;
322+
298323
private MetadataBuilder() {
299324
}
300325

@@ -354,6 +379,15 @@ public MetadataBuilder version(Version version) {
354379
return this;
355380
}
356381

382+
public MetadataBuilder customMetadata(Map<String, String> customMetadata) {
383+
384+
Assert.notNull(customMetadata, "customMetadata should not be null");
385+
Assert.notEmpty(customMetadata.keySet(), "customMetadata should have at least one key");
386+
Assert.notEmpty(customMetadata.values(), "customMetadata should have at least one value");
387+
this.customMetadata = customMetadata;
388+
return this;
389+
}
390+
357391
/**
358392
* Build the {@link Versioned.Metadata} object. Requires
359393
* {@link #createdAt(Instant)} and {@link #version(Versioned.Version)} to be
@@ -365,7 +399,7 @@ public Metadata build() {
365399
Assert.notNull(this.createdAt, "CreatedAt must not be null");
366400
Assert.notNull(this.version, "Version must not be null");
367401

368-
return new Metadata(this.createdAt, this.deletedAt, this.destroyed, this.version);
402+
return new Metadata(this.createdAt, this.deletedAt, this.destroyed, this.version, this.customMetadata);
369403
}
370404

371405
}

spring-vault-core/src/test/java/org/springframework/vault/core/VaultVersionedKeyValueTemplateIntegrationTests.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.time.Instant;
1919
import java.util.Collections;
20+
import java.util.HashMap;
2021
import java.util.Map;
2122
import java.util.UUID;
2223

@@ -29,6 +30,7 @@
2930
import org.springframework.test.context.junit.jupiter.SpringExtension;
3031
import org.springframework.vault.VaultException;
3132
import org.springframework.vault.domain.Person;
33+
import org.springframework.vault.support.VaultMetadataRequest;
3234
import org.springframework.vault.support.Versioned;
3335
import org.springframework.vault.support.Versioned.Metadata;
3436
import org.springframework.vault.support.Versioned.Version;
@@ -103,6 +105,30 @@ void shouldCreateVersionedWithCAS() {
103105
.hasMessageContaining("check-and-set parameter did not match the current version");
104106
}
105107

108+
@Test
109+
void shouldWriteSecretWithCustomMetadata() {
110+
Person person = new Person();
111+
person.setFirstname("Walter");
112+
person.setLastname("White");
113+
114+
String key = UUID.randomUUID().toString();
115+
116+
Map<String, String> customMetadata = new HashMap<>();
117+
customMetadata.put("foo", "bar");
118+
customMetadata.put("uid", "werwer");
119+
120+
this.versionedOperations.put(key, Versioned.create(person));
121+
122+
VaultMetadataRequest request = VaultMetadataRequest.builder().customMetadata(customMetadata).build();
123+
124+
this.versionedOperations.opsForKeyValueMetadata().put(key, request);
125+
126+
Versioned<Person> versioned = this.versionedOperations.get(key, Person.class);
127+
128+
assertThat(versioned.getMetadata().getCustomMetadata().get("foo")).isEqualTo("bar");
129+
130+
}
131+
106132
@Test
107133
void shouldReadAndWriteVersionedSecret() {
108134

0 commit comments

Comments
 (0)