diff --git a/README.md b/README.md index 1e14523d..94d53da1 100644 --- a/README.md +++ b/README.md @@ -2743,6 +2743,16 @@ The name of the bucket to managed. A JSON parsable string of the policy to apply to the bucket. +##### `lifecycle_configuration` + +A JSON parsable string of the lifecycle configuration to apply to the bucket. +More https://docs.amazonaws.cn/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_bucket_lifecycle_configuration-instance_method + +##### `encryption_configuration` + +A JSON parsable string of the encryption configuration to apply to the bucket. +More https://docs.amazonaws.cn/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_bucket_encryption-instance_method + #### Type: sqs_queue ##### `name` diff --git a/lib/puppet/provider/s3_bucket/v2.rb b/lib/puppet/provider/s3_bucket/v2.rb index cd45952c..3136bd5d 100644 --- a/lib/puppet/provider/s3_bucket/v2.rb +++ b/lib/puppet/provider/s3_bucket/v2.rb @@ -24,6 +24,20 @@ def self.instances Puppet.debug("An error occurred reading the policy on S3 bucket #{s3_bucket.name}: " + e.message) end + begin + results = s3_client.get_bucket_lifecycle_configuration({bucket: s3_bucket.name}) + data[:lifecycle_configuration] = JSON.pretty_generate(camelize_stringify_keys(results.to_h)) + rescue Exception => e + Puppet.debug("An error occurred reading the lifecycle configuration on S3 bucket #{s3_bucket.name}: " + e.message) + end + + begin + results = s3_client.get_bucket_encryption({bucket: s3_bucket.name}) + data[:encryption_configuration] = JSON.pretty_generate(camelize_stringify_keys(results.server_side_encryption_configuration.to_h)) + rescue Exception => e + Puppet.debug("An error occurred reading the encryption configuration on S3 bucket #{s3_bucket.name}: " + e.message) + end + new(data) end bucket_list.reject {|b| b.nil? } @@ -61,4 +75,60 @@ def policy=(value) }) end + def lifecycle_configuration=(value) + Puppet.debug('Replacing bucket lifecycle configuration') + s3_client.put_bucket_lifecycle_configuration({ + bucket: @property_hash[:name], + lifecycle_configuration: underscore_symbolarize_keys(JSON.parse(value)) + }) + end + + def encryption_configuration=(value) + Puppet.debug('Replacing bucket encryption configuration') + s3_client.put_bucket_encryption({ + bucket: @property_hash[:name], + server_side_encryption_configuration: underscore_symbolarize_keys(JSON.parse(value)) + }) + end + end + +private + + def underscore_symbolarize_keys(obj) + return obj.reduce({}) do |acc, (k, v)| + acc.tap { |m| m[underscore(k).to_sym] = underscore_symbolarize_keys(v) } + end if obj.is_a? Hash + + return obj.reduce([]) do |acc, v| + acc << underscore_symbolarize_keys(v); acc + end if obj.is_a? Array + + obj + end + + def camelize_stringify_keys(obj) + return obj.reduce({}) do |acc, (k, v)| + acc.tap { |m| m[camelize(k.to_s)] = camelize_stringify_keys(v) } + end if obj.is_a? Hash + + return obj.reduce([]) do |acc, v| + acc << camelize_stringify_keys(v); acc + end if obj.is_a? Array + + obj + end + + def underscore(str) + str.gsub(/::/, '/'). + gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). + gsub(/([a-z\d])([A-Z])/,'\1_\2'). + tr("-", "_"). + downcase + end + + def camelize(str) + return "ID" if str == "id" + return "SSEAlgorithm" if str == "sse_algorithm" + str.split(/_/).map(&:capitalize).join + end diff --git a/lib/puppet/type/s3_bucket.rb b/lib/puppet/type/s3_bucket.rb index 8b70eaf3..915275a1 100644 --- a/lib/puppet/type/s3_bucket.rb +++ b/lib/puppet/type/s3_bucket.rb @@ -17,18 +17,55 @@ newproperty(:policy) do desc 'The policy document JSON string to apply' validate do |value| - fail Puppet::Error, 'Policy documents must be JSON strings' unless value.is_a? String + fail Puppet::Error, 'Policy documents must be JSON strings' unless is_valid_json?(value) end munge do |value| begin - data = JSON.parse(value) - JSON.pretty_generate(data) + JSON.pretty_generate(JSON.parse(value)) rescue fail('Policy string is not valid JSON') end end end + newproperty(:lifecycle_configuration) do + desc 'The lifecycle configuration document JSON string to apply' + validate do |value| + fail Puppet::Error, 'Lifecycle configuration documents must be JSON strings' unless is_valid_json?(value) + end + + munge do |value| + begin + JSON.pretty_generate(JSON.parse(value)) + rescue + fail('Lifecycle configuration string is not valid JSON') + end + end + end + + newproperty(:encryption_configuration) do + desc 'The bucket encryption document JSON string to apply' + validate do |value| + fail Puppet::Error, 'Bucket encryption documents must be JSON strings' unless is_valid_json?(value) + end + + munge do |value| + begin + JSON.pretty_generate(JSON.parse(value)) + rescue + fail('Bucket encryption string is not valid JSON') + end + end + end + end +private + + def is_valid_json?(string) + !!JSON.parse(string) + rescue JSON::ParserError => _e + false + end + diff --git a/spec/unit/type/s3_bucket_spec.rb b/spec/unit/type/s3_bucket_spec.rb index 18367555..d53f7172 100644 --- a/spec/unit/type/s3_bucket_spec.rb +++ b/spec/unit/type/s3_bucket_spec.rb @@ -15,9 +15,19 @@ :ensure, :creation_date, :policy, + :lifecycle_configuration, ] end + let :valid_attributes do + { + name: 'name', + policy: '{}', + encryption_configuration: '{}', + lifecycle_configuration: '{}' + } + end + it 'should have expected properties' do properties.each do |property| expect(type_class.properties.map(&:name)).to be_include(property) @@ -42,9 +52,32 @@ }.to raise_error(Puppet::Error, /Empty bucket names are not allowed/) end - context 'with a valid name' do + context 'with a valid parameters' do it 'should create a valid instance' do - type_class.new({ name: 'name' }) + type_class.new(valid_attributes) + end + + [:policy, :encryption_configuration, :lifecycle_configuration].each do |param| + it "should create a valid instance without optional :#{param}" do + type_class.new(valid_attributes.reject! { |k, _v| k == param }) + end + + it "should require non-blank #{param}" do + expect { + type_class.new(valid_attributes.merge({ param => '' })) + }.to raise_error(Puppet::Error) + end + + it "should fail if string is not a valid JSON #{param}" do + expect { + type_class.new(valid_attributes.merge({ param => 'Hi' })) + }.to raise_error(Puppet::Error) + end + + it "should accept any valid JSON #{param}" do + type_class.new(valid_attributes.merge({ param => '{ "hello": "world!" }' })) + end + end end