diff --git a/.rubocop.yml b/.rubocop.yml index f6ecbcf..8a10653 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,68 @@ +Metrics/ClassLength: + Enabled: false +Metrics/MethodLength: + Enabled: false +Metrics/BlockLength: + Enabled: false +Metrics/AbcSize: + Enabled: false +Metrics/CyclomaticComplexity: + Enabled: false +Metrics/PerceivedComplexity: + Enabled: false +Metrics/BlockNesting: + Enabled: false +Metrics/ParameterLists: + Enabled: false + +Naming/AccessorMethodName: + Enabled: false +Style/NestedTernaryOperator: + Enabled: false +Naming/PredicateName: + Enabled: false + +Layout/MultilineMethodCallIndentation: + EnforcedStyle: indented +Layout/ParameterAlignment: + EnforcedStyle: with_fixed_indentation +Layout/ArgumentAlignment: + EnforcedStyle: with_fixed_indentation +Naming/MethodParameterName: + Enabled: false +Layout/MultilineOperationIndentation: + Enabled: false +Style/GlobalVars: + Enabled: false +Style/ConditionalAssignment: + Enabled: false +Style/ClassVars: + Enabled: false +Style/HashSyntax: + Enabled: false + +Layout/FirstArrayElementIndentation: + Enabled: true +Layout/FirstArrayElementLineBreak: + Enabled: true +Layout/ArrayAlignment: + EnforcedStyle: with_fixed_indentation +Layout/MultilineArrayBraceLayout: + EnforcedStyle: new_line + +Layout/FirstHashElementIndentation: + Enabled: true + EnforcedStyle: consistent +Layout/MultilineHashKeyLineBreaks: + Enabled: true +Layout/MultilineHashBraceLayout: + EnforcedStyle: new_line + +Style/OpenStructUse: + Enabled: false +Naming/VariableName: + Enabled: false + AllCops: - # We're using Ruby 2.4 right now. So force that version or higher. - # The version listed here should match whatever is specified in the gemspec - TargetRubyVersion: 2.4 + NewCops: enable + diff --git a/Gemfile b/Gemfile index 40d1ee9..cdea070 100644 --- a/Gemfile +++ b/Gemfile @@ -2,5 +2,9 @@ source 'https://rubygems.org' -# Specify your gem's dependencies in the gemspec -gemspec +gem 'aws-sdk-ssm' +gem 'byebug' +gem 'logger' +gem 'rspec' +gem 'rubocop' +gem 'yaml' diff --git a/Gemfile.lock b/Gemfile.lock index faff5e5..565602b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,11 +1,3 @@ -PATH - remote: . - specs: - uc3-ssm (0.3.10) - aws-sdk-ssm (~> 1.84) - logger (~> 1.4) - yaml (~> 0.2.1) - GEM remote: https://rubygems.org/ specs: @@ -25,6 +17,8 @@ GEM byebug (11.1.3) diff-lcs (1.5.1) jmespath (1.6.2) + json (2.7.2) + language_server-protocol (3.17.0.3) logger (1.6.0) parallel (1.26.3) parser (3.3.4.2) @@ -35,44 +29,49 @@ GEM regexp_parser (2.9.2) rexml (3.3.6) strscan - rspec (3.9.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-core (3.9.3) - rspec-support (~> 3.9.3) - rspec-expectations (3.9.4) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.0) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.2) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.1) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-support (3.9.4) - rubocop (0.88.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.1) + rubocop (1.65.1) + json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 2.7.1.1) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.7) - rexml - rubocop-ast (>= 0.1.0, < 1.0) + regexp_parser (>= 2.4, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (0.8.0) - parser (>= 2.7.1.5) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.32.1) + parser (>= 3.3.1.0) ruby-progressbar (1.13.0) strscan (3.1.0) - unicode-display_width (1.8.0) - yaml (0.2.1) + unicode-display_width (2.5.0) + yaml (0.3.0) PLATFORMS arm64-darwin-22 + arm64-darwin-23 x86_64-linux DEPENDENCIES - byebug (= 11.1.3) - rspec (= 3.9.0) - rubocop (= 0.88.0) - uc3-ssm! + aws-sdk-ssm + byebug + logger + rspec + rubocop + yaml BUNDLED WITH - 2.3.25 + 2.4.19 diff --git a/lib/uc3-ssm.rb b/lib/uc3-ssm.rb index f8b89b0..79303b8 100644 --- a/lib/uc3-ssm.rb +++ b/lib/uc3-ssm.rb @@ -14,7 +14,6 @@ def initialize(msg) end # This code is designed to mimic https://github.com/terrywbrady/yaml/blob/master/config.yml - # rubocop:disable Metrics/ClassLength class ConfigResolver # def_value - value to return if no default is configured. # This prevents an exception from being thrown. @@ -26,7 +25,6 @@ class ConfigResolver # we search for keys under each path sequentially, returning the value # of the first matching key found. Example: # ssm_root_path: '/prog/srvc/subsrvc/env:/prod/srvc/subsrvc/default' - # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def initialize(**options) # see issue #9 - @regex should not be a user definable option dflt_regex = '^(.*)\\{!(ENV|SSM):\\s*([^\\}!]*)(!DEFAULT:\\s([^\\}]*))?\\}(.*)$' @@ -43,7 +41,7 @@ def initialize(**options) @region = options.fetch(:region, dflt_region) @ssm_root_path = sanitize_root_path(options.fetch(:ssm_root_path, dflt_ssm_root_path)) @def_value = options.fetch(:def_value, '') - @logger = options.fetch(:logger, Logger.new(STDOUT)) + @logger = options.fetch(:logger, Logger.new($stdout)) @client = Aws::SSM::Client.new(region: @region) unless @ssm_skip_resolution rescue Aws::Errors::MissingRegionError raise ConfigResolverError, 'No AWS region defined. Either set ENV["AWS_REGION"] or pass in `region: [region]`' @@ -57,7 +55,7 @@ def resolve_file_values(file:, resolve_key: nil, return_key: nil) raise ConfigResolverError, "Config file #{file} not found!" unless File.exist?(file) raise ConfigResolverError, "Config file #{file} is empty!" unless File.size(file).positive? - config = YAML.safe_load(File.read(file), aliases: true) + config = YAML.safe_load_file(file, aliases: true) resolve_hash_values(hash: config, resolve_key: resolve_key, return_key: return_key) end @@ -77,27 +75,25 @@ def resolve_hash_values(hash:, resolve_key: nil, return_key: nil) # Retrieve all key+values for a path (using the ssm_root_path if none is specified) # See https://docs.aws.amazon.com/sdk-for-ruby/v2/api/Aws/SSM/Client.html#get_parameters_by_path-instance_method # details on available `options` - # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity + # rubocop:disable Metrics/MethodLength def parameters_for_path(**options) return [] if @ssm_skip_resolution param_list = [] path_list = options[:path].nil? ? @ssm_root_path : sanitize_parameter_key(options[:path]) path_list.each do |root_path| - begin - options[:path] = root_path - param_list += fetch_param_list(**options) - rescue Aws::SSM::Errors::ParameterNotFound - @logger.debug "ParameterNotFound for path '#{root_path}' in parameters_by_path" - next - end + options[:path] = root_path + param_list += fetch_param_list(**options) + rescue Aws::SSM::Errors::ParameterNotFound + @logger.debug "ParameterNotFound for path '#{root_path}' in parameters_by_path" + next end param_list rescue Aws::Errors::MissingCredentialsError raise ConfigResolverError, 'No AWS credentials available. Make sure the server has access to the aws-sdk' end - # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity + # rubocop:enable Metrics/MethodLength # Retrieve a value for a single key def parameter_for_key(key) @@ -121,7 +117,7 @@ def sanitize_root_path(root_path) root_path.split(':').each do |path| raise ConfigResolverError, 'ssm_root_path must start with forward slash' unless path.start_with?('/') - root_path_list.push(path.end_with?('/') ? path : path + '/') + root_path_list.push(path.end_with?('/') ? path : "#{path}/") end root_path_list end @@ -154,11 +150,11 @@ def resolve_array(obj) def resolve_hash(obj) return {} if obj.nil? - obj.map { |k, v| [k, resolve_value(v)] }.to_h + obj.transform_values { |v| resolve_value(v) } end # Retrieve value for the string - # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/MethodLength + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def resolve_string(obj) matched = obj.match(@regex) return obj unless matched @@ -217,17 +213,15 @@ def sanitize_parameter_key(key) key_not_qualified_msg = 'SSM parameter name is not fully qualified and no ssm_root_path defined.' raise ConfigResolverError, key_not_qualified_msg.to_s if @ssm_root_path.empty? - keylist = [] - @ssm_root_path.each do |root_path| - keylist.push("#{root_path}#{key}") + @ssm_root_path.map do |root_path| + "#{root_path}#{key}" end - - keylist end # Attempt to retrieve the value from AWS SSM def retrieve_ssm_value(key) return key if @ssm_skip_resolution + @client.get_parameter(name: key, with_decryption: true)[:parameter][:value] rescue Aws::SSM::Errors::ParameterNotFound @logger.debug "ParameterNotFound for key '#{key}' in retrieve_ssm_value" @@ -249,7 +243,6 @@ def fetch_param_list(**options) param_list += fetch_param_list(**options) if options[:next_token].present? param_list end - end # rubocop:enable Metrics/ClassLength end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d431cc5..2a6cd59 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + require 'byebug' require 'uc3-ssm' diff --git a/spec/test/initialize_resolver_object_spec.rb b/spec/test/initialize_resolver_object_spec.rb index e660928..96e25b6 100644 --- a/spec/test/initialize_resolver_object_spec.rb +++ b/spec/test/initialize_resolver_object_spec.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true -require 'spec_helper.rb' +require 'spec_helper' require 'aws-sdk-ssm' -# rubocop:disable Metrics/BlockLength RSpec.describe 'Test resolver object initialization. ', type: :feature do - context 'ConfigResolver.new' do describe 'with no user provided options' do myResolver = Uc3Ssm::ConfigResolver.new @@ -44,14 +42,14 @@ expect(myResolver.instance_variable_get(:@def_value)).to eq('NOT_APPLICABLE') end # see issue #10 - @ssm_skip_resolution only settable as ENV var - #it 'sets @ssm_skip_resolution to true.' do + # it 'sets @ssm_skip_resolution to true.' do # expect(myResolver.instance_variable_get(:@ssm_skip_resolution)).to be true - #end + # end end describe 'where ssm_root_path is list of colon separated paths' do myResolver = Uc3Ssm::ConfigResolver.new( - ssm_root_path: '/root/path/:/no/trailing/slash', + ssm_root_path: '/root/path/:/no/trailing/slash' ) it '@ssm_root_path is array with 2 paths.' do expect(myResolver.instance_variable_get(:@ssm_root_path).length).to eq(2) @@ -63,9 +61,9 @@ describe 'when ssm_root_path does not start with forward slash.' do it 'raises exception.' do - expect { + expect do Uc3Ssm::ConfigResolver.new(ssm_root_path: 'no/starting/slash/') - }.to raise_exception(Uc3Ssm::ConfigResolverError) + end.to raise_exception(Uc3Ssm::ConfigResolverError) end end @@ -88,4 +86,5 @@ end end end -end \ No newline at end of file +end +# rubocop:enable Metrics/BlockLength diff --git a/spec/test/resolver_spec.rb b/spec/test/resolver_spec.rb index 215349f..929dd79 100644 --- a/spec/test/resolver_spec.rb +++ b/spec/test/resolver_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true -require 'spec_helper.rb' +require 'spec_helper' -# rubocop:disable Metrics/BlockLength RSpec.describe 'basic_resolver_tests', type: :feature do def basic_hash { @@ -34,32 +33,31 @@ def basic_hash @no_ssm_error = 'UC3 SSM Error: No AWS credentials available. Make sure the server has access to the aws-sdk' end - # rubocop:disable Metrics/MethodLength def mock_ssm(name, value) param_json = { - "parameter": { - "name": name, - "lastModifiedDate": 1_593_459_594.587, - "value": value, - "version": 1, - "type": 'String', - "ARN": "arn:aws:ssm:us-west-2:1111111111:parameter#{name}" + parameter: { + name: name, + lastModifiedDate: 1_593_459_594.587, + value: value, + version: 1, + type: 'String', + ARN: "arn:aws:ssm:us-west-2:1111111111:parameter#{name}" } } allow_any_instance_of(Aws::SSM::Client).to receive(:get_parameter).with({ name: name, with_decryption: true }) - .and_return(param_json) + .and_return(param_json) end # rubocop:enable Metrics/MethodLength def mock_ssm_failure(name, err) allow_any_instance_of(Aws::SSM::Client).to receive(:get_parameter).with({ name: name, with_decryption: true }) - .and_raise(err) + .and_raise(err) end def mock_ssm_not_found(name) allow_any_instance_of(Aws::SSM::Client).to receive(:get_parameter) - .with({ name: name, with_decryption: true }) - .and_raise(Aws::SSM::Errors::ParameterNotFound.new({}, name)) + .with({ name: name, with_decryption: true }) + .and_raise(Aws::SSM::Errors::ParameterNotFound.new({}, name)) end after(:each) do @@ -81,11 +79,11 @@ def mock_ssm_not_found(name) end skip it 'searches over ssm_root_path when options[path] not specified' do allow_any_instance_of(Aws::SSM::Client).to receive(:get_parameters_by_path).with(path: '/root/path/') - @resolver_prefix.parameters_for_path() + @resolver_prefix.parameters_for_path end skip it 'prepends the ssm_root_path to options["subpath"]' do allow_any_instance_of(Aws::SSM::Client).to receive(:get_parameters_by_path).with(path: '/root/path/subpath') - @resolver_prefix.parameters_for_path({path: 'subpath'}) + @resolver_prefix.parameters_for_path({ path: 'subpath' }) end it 'throws an AWS Credentials error if SSM could not be accessed' do err = Aws::Errors::MissingCredentialsError.new @@ -107,28 +105,28 @@ def mock_ssm_not_found(name) resp1 = OpenStruct.new(parameters: %w[a b]) resp2 = OpenStruct.new(parameters: %w[c d]) allow_any_instance_of(Aws::SSM::Client).to receive(:get_parameters_by_path) - .with(path: '/root/path/foo').and_return(resp1) + .with(path: '/root/path/foo').and_return(resp1) allow_any_instance_of(Aws::SSM::Client).to receive(:get_parameters_by_path) - .with(path: '/other/path/foo').and_return(resp2) + .with(path: '/other/path/foo').and_return(resp2) expect(@resolver_prefix_list.parameters_for_path(path: 'foo')).to eql(%w[a b c d]) end skip it 'only returns params for second root_paths when first root_path does not contain key' do - resp1 = OpenStruct.new(parameters: %w[a b]) + OpenStruct.new(parameters: %w[a b]) resp2 = OpenStruct.new(parameters: %w[c d]) allow_any_instance_of(Aws::SSM::Client).to receive(:get_parameters_by_path) - .with(path: '/root/path/foo') - .and_raise(Aws::SSM::Errors::ParameterNotFound.new({}, '/root/path/foo')) + .with(path: '/root/path/foo') + .and_raise(Aws::SSM::Errors::ParameterNotFound.new({}, '/root/path/foo')) allow_any_instance_of(Aws::SSM::Client).to receive(:get_parameters_by_path) - .with(path: '/other/path/foo').and_return(resp2) + .with(path: '/other/path/foo').and_return(resp2) expect(@resolver_prefix_list.parameters_for_path(path: 'foo')).to eql(%w[c d]) end skip it 'returns more than 10 params' do - resp1 = OpenStruct.new(parameters: %w[0 1 2 3 4 5 6 7 8 9], next_token: "foo") + resp1 = OpenStruct.new(parameters: %w[0 1 2 3 4 5 6 7 8 9], next_token: 'foo') resp2 = OpenStruct.new(parameters: %w[a]) allow_any_instance_of(Aws::SSM::Client).to receive(:fetch_param_list) - .with(path: '/root/path/foo').and_return(resp1) + .with(path: '/root/path/foo').and_return(resp1) allow_any_instance_of(Aws::SSM::Client).to receive(:get_parameters_by_path) - .with(path: '/other/path/foo', next_token: "foo").and_return(resp2) + .with(path: '/other/path/foo', next_token: 'foo').and_return(resp2) expect(@resolver_prefix_list.parameters_for_path(path: 'foo')).to eql(%w[0 1 2 3 4 5 6 7 8 9 a]) end end @@ -276,10 +274,10 @@ def mock_ssm_not_found(name) describe '#return_hash(hash, return_key)' do it 'returns the hash as-is if the return_key is not specified' do - expect(@resolver.send(:return_hash, { 'one': 1 }, nil)).to eql({ 'one': 1 }) + expect(@resolver.send(:return_hash, { one: 1 }, nil)).to eql({ one: 1 }) end it 'returns the hash as-is if the hash does not contain the return_key' do - expect(@resolver.send(:return_hash, { 'one': 1 }, 'two')).to eql({ 'one': 1 }) + expect(@resolver.send(:return_hash, { one: 1 }, 'two')).to eql({ one: 1 }) end it 'returns the value of the return_key' do expect(@resolver.send(:return_hash, { one: 1 }, :one)).to eql(1) @@ -301,7 +299,6 @@ def mock_ssm_not_found(name) end end - it 'Test Basic static values' do config_in = basic_hash config = @resolver.resolve_hash_values(hash: config_in) diff --git a/uc3-ssm.gemspec b/uc3-ssm.gemspec deleted file mode 100644 index c2d42f4..0000000 --- a/uc3-ssm.gemspec +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -$LOAD_PATH.push File.expand_path('lib', __dir__) -require 'uc3-ssm/version' - -Gem::Specification.new do |spec| - spec.name = 'uc3-ssm' - spec.version = Uc3Ssm::VERSION - spec.platform = Gem::Platform::RUBY - spec.authors = ['Terry Brady'] - spec.email = ['terry.brady@ucop.edu'] - - spec.summary = 'UC3 - Credential store for AWS SSM' - spec.description = 'Provides access to the AWS SSM credential store for Ruby' - spec.homepage = 'https://github.com/CDLUC3/uc3-ssm' - spec.license = 'MIT' - - spec.files = Dir['lib/**/*'] + %w[README.md] - spec.require_paths = ['lib'] - spec.required_ruby_version = '>= 3.0' - - spec.add_runtime_dependency('aws-sdk-ssm', '~> 1.84') - spec.add_runtime_dependency('logger', '~> 1.4') - spec.add_runtime_dependency('yaml', '~> 0.2.1') - - # Requirements for running RSpec - spec.add_development_dependency('byebug', '11.1.3') - spec.add_development_dependency('rspec', '3.9.0') - spec.add_development_dependency('rubocop', '0.88.0') -end