diff --git a/lib/vault/api/logical.rb b/lib/vault/api/logical.rb index 49754c52..9f422215 100644 --- a/lib/vault/api/logical.rb +++ b/lib/vault/api/logical.rb @@ -73,6 +73,32 @@ def write(path, data = {}, options = {}) end end + # Update the secret at the given path with the given data. Note that the + # data must be a {Hash}! Data will be merged with existing values. + # + # Note: This will raise an error if used on KV Secrets Engine Version 1. + # Note: The path must include `data` to work properly for Version 2. + # + # @example + # Vault.logical.update("secret/data/multiple", password: "secret") #=> # + # + # @param [String] path + # the path to write + # @param [Hash] data + # the data to write + # + # @return [Secret] + def update(path, data = {}, options = {}) + headers = extract_headers!(options) + headers["Content-Type"] = "application/merge-patch+json" + json = client.patch("/v1/#{encode_path(path)}", JSON.fast_generate(data), headers) + if json.nil? + return true + else + return Secret.decode(json) + end + end + # Delete the secret at the given path. If the secret does not exist, vault # will still return true. # diff --git a/spec/integration/api/logical_spec.rb b/spec/integration/api/logical_spec.rb index edb5bf15..d57a707b 100644 --- a/spec/integration/api/logical_spec.rb +++ b/spec/integration/api/logical_spec.rb @@ -91,6 +91,57 @@ module Vault end end + describe "#update" do + context "v1 KV" do + before do + @original_mount = vault_test_client.sys.mounts[:secret] + vault_test_client.sys.unmount("secret") + vault_test_client.sys.mount( + "secret", "kv", "v1 KV", options: {version: "1"} + ) + end + + after do + vault_test_client.sys.unmount("secret") + vault_test_client.sys.mount( + "secret", @original_mount.type, @original_mount.description, options: @original_mount.options + ) + end + + it "raises an error" do + subject.write("secret/test-update-v1", zip: "zap") + expect { + subject.update("secret/test-update-v1", bacon: true) + }.to raise_error(Vault::HTTPClientError, /405/) + end + end + + context "v2 KV" do + before do + @original_mount = vault_test_client.sys.mounts[:secret] + vault_test_client.sys.unmount("secret") + vault_test_client.sys.mount( + "secret", "kv", "v2 KV", options: {version: "2"} + ) + end + + after do + vault_test_client.sys.unmount("secret") + vault_test_client.sys.mount( + "secret", @original_mount.type, @original_mount.description, options: @original_mount.options + ) + end + + it "updates existing secrets" do + subject.write("secret/data/test-update-v2", data: {zip: "zap"}) + subject.update("secret/data/test-update-v2", data: {bacon: true}) + result = subject.read("secret/data/test-update-v2") + expect(result).to be + expect(result.data[:data]).to eq(zip: "zap", bacon: true) + end + end + end + describe "#delete" do it "deletes the secret" do subject.write("secret/delete", foo: "bar")