-
Notifications
You must be signed in to change notification settings - Fork 223
Allow disabling automatic "data" insertion into v2 path #155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Is there a workaround for this ? |
I'm not aware of any. If you look at the code it is quite hard-coded. As for a higher-level workaround, my use case is the vault features of jenkins configuration-as-code, and for the time being I wrap my jenkins in a script that pulls secrets from vault and sticks them in env vars. This is not ideal because the secrets are floating around in the env without special treatment. |
ended up using an older version of client 3.1.0 |
Any updates on this? I have:
And:
However, I cannot read with that code the secret - it returns:
|
I'm seeing the same problem, after our vault server was updated to 1.0. I've tried using version 4.0.0 and also tried using a |
Not sure this will help you, but I was able to fix my problem by doing two things:
|
First time posting on Github, so please forgive any lack of proper etiquette. I recently started trying to use this driver and I ran into the same issue. Here are the code changes that I made to get KV mounts with an extra prefixs. Apologizes for such verbosity. Update to VaultConfig class: ...
private String secretsPrefix;
...
...
/**
* <p>Sets the secrets Engine paths used by Vault.</p>
*
* @param prefix prefix of secrets mount.
* prefix: prefix path, value: prefix path.
* Example string: "my/custom/prefix"
* @return This object, with secrets prefix populated, ready for additional builder-pattern method calls or else finalization with the build() method
*/
public VaultConfig secretsPrefix(final String prefix) {
this.secretsPrefix = prefix;
return this;
}
...
...
public String getSecretsPrefix() { return secretsPrefix; }
... Updates to Logical class: ...
public LogicalResponse read(final String path) throws VaultException {
if (config.getSecretsPrefix() != null)
return read(config.getSecretsPrefix(), path);
if (this.engineVersionForSecretPath(path).equals(2)) {
return read(path, true, logicalOperations.readV2);
} else return read(path, true, logicalOperations.readV1);
}
/**
* <p>Basic read operation to retrieve a secret. A single secret key can map to multiple name-value pairs,
* which can be retrieved from the response object. E.g.:</p>
*
* <blockquote>
* <pre>{@code
* final LogicalResponse response = vault.logical().read("secret/hello");
*
* final String value = response.getData().get("value");
* final String otherValue = response.getData().get("other_value");
* }</pre>
* </blockquote>
*
* @param prefix The prefix of the secrets mount (e.g. <code>this/is/my/prefix</code>)
* @param path The Vault key value from which to read (e.g. <code>secret/hello</code>)
* @return The response information returned from Vault
* @throws VaultException If any errors occurs with the REST request (e.g. non-200 status code, invalid JSON payload,
* etc), and the maximum number of retries is exceeded.
*/
public LogicalResponse read(final String prefix, final String path) throws VaultException {
if (this.engineVersionForSecretPath(path).equals(2)) {
return read(prefix, path, true, logicalOperations.readV2);
} else return read(prefix, path, true, logicalOperations.readV1);
}
...
...
private LogicalResponse read(final String prefix, final String path, Boolean shouldRetry, final logicalOperations operation)
throws VaultException {
int retryCount = 0;
while (true) {
try {
// Make an HTTP request to Vault
final RestResponse restResponse = new Rest()//NOPMD
.url(config.getAddress() + "/v1/" + prefix + "/" + adjustPathForReadOrWrite(path, operation))
.header("X-Vault-Token", config.getToken())
.optionalHeader("X-Vault-Namespace", this.nameSpace)
.connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
.sslVerification(config.getSslConfig().isVerify())
.sslContext(config.getSslConfig().getSslContext())
.get();
// Validate response
if (restResponse.getStatus() != 200) {
throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus()
+ "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8),
restResponse.getStatus());
}
return new LogicalResponse(restResponse, retryCount, operation);
} catch (RuntimeException | VaultException | RestException e) {
if (!shouldRetry)
throw new VaultException(e);
// If there are retries to perform, then pause for the configured interval and then execute the loop again...
if (retryCount < config.getMaxRetries()) {
retryCount++;
try {
final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
Thread.sleep(retryIntervalMilliseconds);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
} else if (e instanceof VaultException) {
// ... otherwise, give up.
throw (VaultException) e;
} else {
throw new VaultException(e);
}
}
}
}
...
...
/**
* <p>Basic read operation to retrieve a specified secret version for KV engine version 2. A single secret key version
* can map to multiple name-value pairs, which can be retrieved from the response object. E.g.:</p>
*
* <blockquote>
* <pre>{@code
* final LogicalResponse response = vault.logical().read("secret/hello", true, 1);
*
* final String value = response.getData().get("value");
* final String otherValue = response.getData().get("other_value");
* }</pre>
* </blockquote>
*
* @param prefix The prefix of the secrets mount (e.g. <code>this/is/my/prefix</code>)
* @param path The Vault key value from which to read (e.g. <code>secret/hello</code>
* @param shouldRetry Whether to try more than once
* @param version The Integer version number of the secret to read, e.g. "1"
* @return The response information returned from Vault
* @throws VaultException If any errors occurs with the REST request (e.g. non-200 status code, invalid JSON payload,
* etc), and the maximum number of retries is exceeded.
*/
public LogicalResponse read(final String prefix, final String path, Boolean shouldRetry, final Integer version) throws VaultException {
if (this.engineVersionForSecretPath(path) != 2) {
throw new VaultException("Version reads are only supported in KV Engine version 2.");
}
int retryCount = 0;
while (true) {
try {
// Make an HTTP request to Vault
final RestResponse restResponse = new Rest()//NOPMD
.url(config.getAddress() + "/v1/" + prefix + "/" + adjustPathForReadOrWrite(path, logicalOperations.readV2))
.header("X-Vault-Token", config.getToken())
.optionalHeader("X-Vault-Namespace", this.nameSpace)
.parameter("version", version.toString())
.connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
.sslVerification(config.getSslConfig().isVerify())
.sslContext(config.getSslConfig().getSslContext())
.get();
// Validate response
if (restResponse.getStatus() != 200) {
throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus()
+ "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8),
restResponse.getStatus());
}
return new LogicalResponse(restResponse, retryCount, logicalOperations.readV2);
} catch (RuntimeException | VaultException | RestException e) {
if (!shouldRetry)
throw new VaultException(e);
// If there are retries to perform, then pause for the configured interval and then execute the loop again...
if (retryCount < config.getMaxRetries()) {
retryCount++;
try {
final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
Thread.sleep(retryIntervalMilliseconds);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
} else if (e instanceof VaultException) {
// ... otherwise, give up.
throw (VaultException) e;
} else {
throw new VaultException(e);
}
}
}
}
...
...
public LogicalResponse write(final String path, final Map<String, Object> nameValuePairs) throws VaultException {
if (config.getSecretsPrefix() != null)
return write(config.getSecretsPrefix(), path, nameValuePairs);
if (engineVersionForSecretPath(path).equals(2)) {
return write(path, nameValuePairs, logicalOperations.writeV2);
} else return write(path, nameValuePairs, logicalOperations.writeV1);
}
/**
* <p>Basic operation to store secrets. Multiple name value pairs can be stored under the same secret key.
* E.g.:</p>
*
* <blockquote>
* <pre>{@code
* final Map<String, String> nameValuePairs = new HashMap<String, Object>();
* nameValuePairs.put("value", "foo");
* nameValuePairs.put("other_value", "bar");
*
* final LogicalResponse response = vault.logical().write("secret/hello", nameValuePairs);
* }</pre>
* </blockquote>
*
* <p>The values in these name-value pairs may be booleans, numerics, strings, or nested JSON objects. However,
* be aware that this method does not recursively parse any nested structures. If you wish to write arbitrary
* JSON objects to Vault... then you should parse them to JSON outside of this method, and pass them here as JSON
* strings.</p>
*
* @param prefix The prefix of the secrets mount (e.g. <code>this/is/my/prefix</code>)
* @param path The Vault key value to which to write (e.g. <code>secret/hello</code>)
* @param nameValuePairs Secret name and value pairs to store under this Vault key (can be <code>null</code> for
* writing to keys that do not need or expect any fields to be specified)
* @return The response information received from Vault
* @throws VaultException If any errors occurs with the REST request, and the maximum number of retries is exceeded.
*/
public LogicalResponse write(final String prefix, final String path, final Map<String, Object> nameValuePairs) throws VaultException {
if (engineVersionForSecretPath(path).equals(2)) {
return write(prefix, path, nameValuePairs, logicalOperations.writeV2);
} else return write(prefix, path, nameValuePairs, logicalOperations.writeV1);
}
...
...
private LogicalResponse write(final String prefix, final String path, final Map<String, Object> nameValuePairs,
final logicalOperations operation) throws VaultException {
int retryCount = 0;
while (true) {
try {
JsonObject requestJson = Json.object();
if (nameValuePairs != null) {
for (final Map.Entry<String, Object> pair : nameValuePairs.entrySet()) {
final Object value = pair.getValue();
if (value == null) {
requestJson = requestJson.add(pair.getKey(), (String) null);
} else if (value instanceof Boolean) {
requestJson = requestJson.add(pair.getKey(), (Boolean) pair.getValue());
} else if (value instanceof Integer) {
requestJson = requestJson.add(pair.getKey(), (Integer) pair.getValue());
} else if (value instanceof Long) {
requestJson = requestJson.add(pair.getKey(), (Long) pair.getValue());
} else if (value instanceof Float) {
requestJson = requestJson.add(pair.getKey(), (Float) pair.getValue());
} else if (value instanceof Double) {
requestJson = requestJson.add(pair.getKey(), (Double) pair.getValue());
} else {
requestJson = requestJson.add(pair.getKey(), pair.getValue().toString());
}
}
}
// Make an HTTP request to Vault
final RestResponse restResponse = new Rest()//NOPMD
.url(config.getAddress() + "/v1/" + prefix + "/" + adjustPathForReadOrWrite(path, operation))
.body(jsonObjectToWriteFromEngineVersion(operation, requestJson).toString().getBytes(StandardCharsets.UTF_8))
.header("X-Vault-Token", config.getToken())
.optionalHeader("X-Vault-Namespace", this.nameSpace)
.connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
.sslVerification(config.getSslConfig().isVerify())
.sslContext(config.getSslConfig().getSslContext())
.post();
// HTTP Status should be either 200 (with content - e.g. PKI write) or 204 (no content)
final int restStatus = restResponse.getStatus();
if (restStatus == 200 || restStatus == 204) {
return new LogicalResponse(restResponse, retryCount, operation);
} else {
throw new VaultException("Expecting HTTP status 204 or 200, but instead receiving " + restStatus
+ "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8), restStatus);
}
} catch (Exception e) {
// If there are retries to perform, then pause for the configured interval and then execute the loop again...
if (retryCount < config.getMaxRetries()) {
retryCount++;
try {
final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
Thread.sleep(retryIntervalMilliseconds);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
} else if (e instanceof VaultException) {
// ... otherwise, give up.
throw (VaultException) e;
} else {
throw new VaultException(e);
}
}
}
}
...
...
public List<String> list(final String path) throws VaultException {
if (config.getSecretsPrefix() != null)
return list(config.getSecretsPrefix(), path);
if (engineVersionForSecretPath(path).equals(2)) {
return list(path, logicalOperations.listV2);
} else return list(path, logicalOperations.listV1);
}
/**
* <p>Retrieve a list of keys corresponding to key/value pairs at a given Vault path.</p>
*
* <p>Key values ending with a trailing-slash characters are sub-paths. Running a subsequent <code>list()</code>
* call, using the original path appended with this key, will retrieve all secret keys stored at that sub-path.</p>
*
* <p>This method returns only the secret keys, not values. To retrieve the actual stored value for a key,
* use <code>read()</code> with the key appended onto the original base path.</p>
*
* @param prefix The prefix of the secrets mount (e.g. <code>this/is/my/prefix</code>)
* @param path The Vault key value at which to look for secrets (e.g. <code>secret</code>)
* @return A list of keys corresponding to key/value pairs at a given Vault path, or an empty list if there are none
* @throws VaultException If any errors occur, or unexpected response received from Vault
*/
public List<String> list(final String prefix, final String path) throws VaultException {
if (engineVersionForSecretPath(path).equals(2)) {
return list(prefix, path, logicalOperations.listV2);
} else return list(prefix, path, logicalOperations.listV1);
}
...
...
private List<String> list(final String prefix, final String path, final logicalOperations operation) throws VaultException {
LogicalResponse response = null;
try {
response = read(prefix, adjustPathForList(path, operation), true, operation);
} catch (final VaultException e) {
if (e.getHttpStatusCode() != 404) {
throw e;
}
}
final List<String> returnValues = new ArrayList<>();
if (
response != null
&& response.getRestResponse().getStatus() != 404
&& response.getData() != null
&& response.getData().get("keys") != null
) {
final JsonArray keys = Json.parse(response.getData().get("keys")).asArray();
for (int index = 0; index < keys.size(); index++) {
returnValues.add(keys.get(index).asString());
}
}
return returnValues;
}
...
...
public LogicalResponse delete(final String path) throws VaultException {
if (config.getSecretsPrefix() != null)
return delete(config.getSecretsPrefix(), path);
if (engineVersionForSecretPath(path).equals(2)) {
return delete(path, logicalOperations.deleteV2);
} else return delete(path, logicalOperations.deleteV1);
}
/**
* <p>Deletes the key/value pair located at the provided path.</p>
*
* <p>If the path represents a sub-path, then all of its contents must be deleted prior to deleting the empty
* sub-path itself.</p>
*
* @param prefix The prefix of the secrets mount (e.g. <code>this/is/my/prefix</code>)
* @param path The Vault key value to delete (e.g. <code>secret/hello</code>).
* @return The response information received from Vault
* @throws VaultException If any error occurs, or unexpected response received from Vault
*/
public LogicalResponse delete(final String prefix, final String path) throws VaultException {
if (engineVersionForSecretPath(path).equals(2)) {
return delete(prefix, path, logicalOperations.deleteV2);
} else return delete(prefix, path, logicalOperations.deleteV1);
}
...
...
private LogicalResponse delete(final String prefix, final String path, final Logical.logicalOperations operation) throws VaultException {
int retryCount = 0;
while (true) {
try {
// Make an HTTP request to Vault
final RestResponse restResponse = new Rest()//NOPMD
.url(config.getAddress() + "/v1/" + prefix + "/" + adjustPathForDelete(path, operation))
.header("X-Vault-Token", config.getToken())
.optionalHeader("X-Vault-Namespace", this.nameSpace)
.connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
.sslVerification(config.getSslConfig().isVerify())
.sslContext(config.getSslConfig().getSslContext())
.delete();
// Validate response
if (restResponse.getStatus() != 204) {
throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus()
+ "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8),
restResponse.getStatus());
}
return new LogicalResponse(restResponse, retryCount, operation);
} catch (RuntimeException | VaultException | RestException e) {
// If there are retries to perform, then pause for the configured interval and then execute the loop again...
if (retryCount < config.getMaxRetries()) {
retryCount++;
try {
final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
Thread.sleep(retryIntervalMilliseconds);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
} else if (e instanceof VaultException) {
// ... otherwise, give up.
throw (VaultException) e;
} else {
throw new VaultException(e);
}
}
}
}
...
...
/**
* <p>Soft deletes the specified version of the key/value pair located at the provided path.</p>
* <p>
* Only supported for KV Engine version 2. If the data is desired, it can be recovered with a matching unDelete operation.
*
* <p>If the path represents a sub-path, then all of its contents must be deleted prior to deleting the empty
* sub-path itself.</p>
*
* @param prefix The prefix of the secrets mount (e.g. <code>this/is/my/prefix</code>)
* @param path The Vault key value to delete (e.g. <code>secret/hello</code>).
* @param versions An array of Integers corresponding to the versions you wish to delete, e.g. [1, 2] etc.
* @return The response information received from Vault
* @throws VaultException If any error occurs, or unexpected response received from Vault
*/
public LogicalResponse delete(final String prefix, final String path, final int[] versions) throws VaultException {
if (this.engineVersionForSecretPath(path) != 2) {
throw new VaultException("Version deletes are only supported for KV Engine 2.");
}
intArrayCheck(versions);
int retryCount = 0;
while (true) {
try {
// Make an HTTP request to Vault
JsonObject versionsToDelete = new JsonObject().add("versions", versions);
final RestResponse restResponse = new Rest()//NOPMD
.url(config.getAddress() + "/v1/" + prefix + "/" + adjustPathForVersionDelete(path))
.header("X-Vault-Token", config.getToken())
.optionalHeader("X-Vault-Namespace", this.nameSpace)
.connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
.sslVerification(config.getSslConfig().isVerify())
.sslContext(config.getSslConfig().getSslContext())
.body(versionsToDelete.toString().getBytes(StandardCharsets.UTF_8))
.post();
// Validate response
return getLogicalResponse(retryCount, restResponse);
} catch (RuntimeException | VaultException | RestException e) {
// If there are retries to perform, then pause for the configured interval and then execute the loop again...
if (retryCount < config.getMaxRetries()) {
retryCount++;
try {
final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
Thread.sleep(retryIntervalMilliseconds);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
} else if (e instanceof VaultException) {
// ... otherwise, give up.
throw (VaultException) e;
} else {
throw new VaultException(e);
}
}
}
}
...
...
public LogicalResponse unDelete(final String path, final int[] versions) throws VaultException {
if (config.getSecretsPrefix() != null)
return unDelete(config.getSecretsPrefix(), path, versions);
...
...
/**
* <p>Recovers a soft delete of the specified version of the key/value pair located at the provided path.</p>
* <p>
* Only supported for KV Engine version 2.
*
* @param prefix The prefix of the secrets mount (e.g. <code>this/is/my/prefix</code>)
* @param path The Vault key value to undelete (e.g. <code>secret/hello</code>).
* @param versions An array of Integers corresponding to the versions you wish to undelete, e.g. [1, 2] etc.
* @return The response information received from Vault
* @throws VaultException If any error occurs, or unexpected response received from Vault
*/
public LogicalResponse unDelete(final String prefix, final String path, final int[] versions) throws VaultException {
if (this.engineVersionForSecretPath(path) != 2) {
throw new VaultException("Version undeletes are only supported for KV Engine 2.");
}
intArrayCheck(versions);
int retryCount = 0;
while (true) {
try {
// Make an HTTP request to Vault
JsonObject versionsToUnDelete = new JsonObject().add("versions", versions);
final RestResponse restResponse = new Rest()//NOPMD
.url(config.getAddress() + "/v1/" + prefix + "/" + adjustPathForVersionUnDelete(path))
.header("X-Vault-Token", config.getToken())
.optionalHeader("X-Vault-Namespace", this.nameSpace)
.connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
.sslVerification(config.getSslConfig().isVerify())
.sslContext(config.getSslConfig().getSslContext())
.body(versionsToUnDelete.toString().getBytes(StandardCharsets.UTF_8))
.post();
// Validate response
if (restResponse.getStatus() != 204) {
throw new VaultException("Vault responded with HTTP status code: " + restResponse.getStatus()
+ "\nResponse body: " + new String(restResponse.getBody(), StandardCharsets.UTF_8),
restResponse.getStatus());
}
return new LogicalResponse(restResponse, retryCount, logicalOperations.unDelete);
} catch (RuntimeException | VaultException | RestException e) {
// If there are retries to perform, then pause for the configured interval and then execute the loop again...
if (retryCount < config.getMaxRetries()) {
retryCount++;
try {
final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
Thread.sleep(retryIntervalMilliseconds);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
} else if (e instanceof VaultException) {
// ... otherwise, give up.
throw (VaultException) e;
} else {
throw new VaultException(e);
}
}
}
}
...
...
public LogicalResponse destroy(final String path, final int[] versions) throws VaultException {
if (config.getSecretsPrefix() != null)
return destroy(config.getSecretsPrefix(), path, versions);
...
...
/**
* <p>Performs a hard delete of the specified version of the key/value pair located at the provided path.</p>
* <p>
* Only supported for KV Engine version 2. There are no recovery options for the specified version of the data deleted
* in this method.
*
* @param prefix The prefix of the secrets mount (e.g. <code>this/is/my/prefix</code>)
* @param path The Vault key value to destroy (e.g. <code>secret/hello</code>).
* @param versions An array of Integers corresponding to the versions you wish to destroy, e.g. [1, 2] etc.
* @return The response information received from Vault
* @throws VaultException If any error occurs, or unexpected response received from Vault
*/
public LogicalResponse destroy(final String prefix, final String path, final int[] versions) throws VaultException {
if (this.engineVersionForSecretPath(path) != 2) {
throw new VaultException("Secret destroys are only supported for KV Engine 2.");
}
intArrayCheck(versions);
int retryCount = 0;
while (true) {
try {
// Make an HTTP request to Vault
JsonObject versionsToDestroy = new JsonObject().add("versions", versions);
final RestResponse restResponse = new Rest()//NOPMD
.url(config.getAddress() + "/v1/" + adjustPathForVersionDestroy(path))
.header("X-Vault-Token", config.getToken())
.optionalHeader("X-Vault-Namespace", this.nameSpace)
.connectTimeoutSeconds(config.getOpenTimeout())
.readTimeoutSeconds(config.getReadTimeout())
.sslVerification(config.getSslConfig().isVerify())
.sslContext(config.getSslConfig().getSslContext())
.body(versionsToDestroy.toString().getBytes(StandardCharsets.UTF_8))
.post();
// Validate response
return getLogicalResponse(retryCount, restResponse);
} catch (RuntimeException | VaultException | RestException e) {
// If there are retries to perform, then pause for the configured interval and then execute the loop again...
if (retryCount < config.getMaxRetries()) {
retryCount++;
try {
final int retryIntervalMilliseconds = config.getRetryIntervalMilliseconds();
Thread.sleep(retryIntervalMilliseconds);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
} else if (e instanceof VaultException) {
// ... otherwise, give up.
throw (VaultException) e;
} else {
throw new VaultException(e);
}
}
}
}
... Usage example: try {
final SslConfig sslConfig = new SslConfig().verify(false);
final VaultConfig config = new VaultConfig().engineVersion(2).address("https://127.0.0.1:8200").sslConfig(sslConfig).token("s.abcde12345").secretsPrefix("extra/prefix/stuff").build();
final Vault vault = new Vault(config);
final String value = vault.logical().read("secret/foo").getData().get("bar");
System.out.println(value);
} catch (VaultException e) {
e.printStackTrace();
} |
@crash-bandi could you push your changes to a branch in your own fork, and then submit a PR? Even if it's still a work in progress, that would make it easier for people to review the diff. |
Similar fix for this problem is now available on pull request #189. Fix is backward-compatible and requires no change for current library users. |
Appreciate the work so far to add KV v2 support 👍
We currently have KV v2 engines mounted at mount points containing slashes, which break the assumption that "data" should be second-from-the-root. I don't think there will be a foolproof way to infer the correct place to insert the "data" segment, unless you enumerate readable mounts or something like that (probably not a great idea). Could we add an option to disable the automatic insertion, and leave it to the user to pass the path in ready-to-go?
The text was updated successfully, but these errors were encountered: