Skip to content

Commit

Permalink
Add #update for patch support to KV and Logical
Browse files Browse the repository at this point in the history
- Add KV#update for patch support
- Add Logical#update for patch support
- Limit #update specs to vault >= 1.9.0
- Add vault 1.9.2 and ruby 2.7.4 to test matrix
- Update spec to match on custom_metadata for vault >= 1.9.0
  • Loading branch information
phallstrom committed Dec 23, 2021
1 parent 714a305 commit f3c94f2
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 3 deletions.
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ workflows:
only: /^v[0-9]+\.[0-9]+\.[0-9]+.*/
matrix:
parameters:
ruby-version: ["2.7.1", "2.6", "2.5"]
vault-version: ["1.5.0", "1.4.2", "1.4.1", "1.4.0", "1.3.6"]
ruby-version: ["2.7.4", "2.7.1", "2.6", "2.5"]
vault-version: ["1.9.2", "1.5.0", "1.4.2", "1.4.1", "1.4.0", "1.3.6"]
name: test-ruby-<< matrix.ruby-version >>-vault-<< matrix.vault-version >>
- build-release:
requires:
Expand Down
26 changes: 26 additions & 0 deletions lib/vault/api/kv.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,32 @@ def write_metadata(path, metadata = {})
true
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.
#
# @example
# Vault.kv.write("secret/multiple", password: "secret") #=> #<Vault::Secret lease_id="">
#
# @param [String] path
# the path to update
# @param [Hash] data
# the data to merge
#
# @return [Secret]
def update(path, data = {}, options = {})
headers = extract_headers!(options)
headers["Content-Type"] = "application/merge-patch+json"
json = client.patch("/v1/#{mount}/data/#{encode_path(path)}", JSON.fast_generate(:data => 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.
#
Expand Down
26 changes: 26 additions & 0 deletions lib/vault/api/logical.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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") #=> #<Vault::Secret lease_id="">
#
# @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.
#
Expand Down
38 changes: 37 additions & 1 deletion spec/integration/api/kv_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ module Vault
subject.write("b:@c%n-read", foo: "bar")
secret = subject.read("b:@c%n-read")
expect(secret).to be
expect(secret.metadata.keys).to match_array([:created_time, :deletion_time, :version, :destroyed])
if vault_meets_requirements?(">= 1.9.0")
expect(secret.metadata.keys).to match_array([:created_time, :deletion_time, :version, :destroyed, :custom_metadata])
else
expect(secret.metadata.keys).to match_array([:created_time, :deletion_time, :version, :destroyed])
end
end
end

Expand Down Expand Up @@ -118,6 +122,38 @@ module Vault
end
end

describe "#update", vault: ">= 1.9.0" do
it "merges data and returns the secret" do
subject.write("test-update", zip: "zap")
subject.update("test-update", zig: "zag")
result = subject.read("test-update")
expect(result).to be
expect(result.data).to eq(zip: "zap", zig: "zag")
end

it "raises an error if the path does not exist" do
expect {
subject.update("test-update-non-existent", zig: "zag")
}.to raise_error(Vault::HTTPClientError)
end

it "raises an error if the path has been deleted" do
expect {
subject.write("test-update-deleted", zip: "zap")
subject.delete("test-update-deleted")
subject.update("test-update-deleted", zig: "zag")
}.to raise_error(Vault::HTTPClientError)
end

it "raises an error if the path has been destroyed" do
expect {
subject.write("test-update-destroyed", zip: "zap")
subject.delete("test-update-destroyed")
subject.update("test-update-destroyed", zig: "zag")
}.to raise_error(Vault::HTTPClientError)
end
end

describe "#delete" do
it "deletes the secret" do
subject.write("delete", foo: "bar")
Expand Down
51 changes: 51 additions & 0 deletions spec/integration/api/logical_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,57 @@ module Vault
end
end

describe "#update", vault: ">= 1.9.0" 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)
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")
Expand Down

0 comments on commit f3c94f2

Please sign in to comment.