diff --git a/Gemfile.lock b/Gemfile.lock index 07ae230..090688d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -28,6 +28,8 @@ GEM i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) aws-partitions (1.71.0) aws-sdk-core (3.17.1) aws-partitions (~> 1.0) @@ -39,9 +41,12 @@ GEM aws-sigv4 (1.0.2) builder (3.2.3) concurrent-ruby (1.0.5) + crack (0.4.3) + safe_yaml (~> 1.0.0) crass (1.0.3) diff-lcs (1.3) erubi (1.7.1) + hashdiff (0.3.7) i18n (0.9.5) concurrent-ruby (~> 1.0) jmespath (1.3.1) @@ -53,6 +58,7 @@ GEM minitest (5.11.3) nokogiri (1.8.2) mini_portile2 (~> 2.3.0) + public_suffix (3.0.2) rack (2.0.4) rack-test (0.8.3) rack (>= 1.0, < 3) @@ -81,10 +87,15 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.7.0) rspec-support (3.7.1) + safe_yaml (1.0.4) thor (0.20.0) thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) + webmock (3.3.0) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff PLATFORMS ruby @@ -95,6 +106,7 @@ DEPENDENCIES psenv-rails! rake (~> 10.0) rspec (~> 3.0) + webmock (~> 3.3) BUNDLED WITH 1.16.1 diff --git a/lib/psenv-rails.rb b/lib/psenv-rails.rb index 1841158..2976423 100644 --- a/lib/psenv-rails.rb +++ b/lib/psenv-rails.rb @@ -6,15 +6,15 @@ if defined?(Rake.application) end module Psenv - class Railtie < Rails::Railtie - def load - Psenv.load - end + class Railtie < Rails::Railtie + def load + Psenv.load + end - def self.load - instance.load - end + def self.load + instance.load + end config.before_configuration { load } - end + end end diff --git a/lib/psenv.rb b/lib/psenv.rb index 6355792..5f639f8 100644 --- a/lib/psenv.rb +++ b/lib/psenv.rb @@ -1,3 +1,5 @@ +require "psenv/environment" +require "psenv/retriever" require "psenv/version" require "aws-sdk-ssm" @@ -5,19 +7,17 @@ require "aws-sdk-ssm" module Psenv module_function - def load - if ENV["PARAMETER_STORE_PATH"] != nil - ssm = Aws::SSM::Client.new + def load(*paths) + paths.unshift(ENV["PARAMETER_STORE_PATH"]) if ENV["PARAMETER_STORE_PATH"] + Environment.create(*paths.map { |path| retrieve_variables(path) }).apply + end - ssm. - get_parameters_by_path( - path: ENV["PARAMETER_STORE_PATH"], - with_decryption: true, - ). - parameters. - each do |param| - ENV.store(param.name.split("/").last, param.value) - end - end + def overload(*paths) + paths.unshift(ENV["PARAMETER_STORE_PATH"]) if ENV["PARAMETER_STORE_PATH"] + Environment.create(*paths.map { |path| retrieve_variables(path) }).apply! + end + + def retrieve_variables(path) + Retriever.new(path).call end end diff --git a/lib/psenv/environment.rb b/lib/psenv/environment.rb new file mode 100644 index 0000000..33a5be7 --- /dev/null +++ b/lib/psenv/environment.rb @@ -0,0 +1,15 @@ +module Psenv + class Environment < Hash + def apply + each { |k, v| ENV.store(k, v) unless ENV.has_key?(k) } + end + + def apply! + each { |k, v| ENV.store(k, v) } + end + + def self.create(*variables) + Environment[variables.reverse.reduce({}, :merge)] + end + end +end diff --git a/lib/psenv/retriever.rb b/lib/psenv/retriever.rb new file mode 100644 index 0000000..731c60c --- /dev/null +++ b/lib/psenv/retriever.rb @@ -0,0 +1,48 @@ +require "aws-sdk-ssm" + +module Psenv + class RetrieveError < StandardError; end + + class Parameter + attr_reader :name, :value + + def initialize(parameter) + @name = parameter[:name].split("/").last + @value = parameter[:value] + @type = parameter[:type] + @version = parameter[:version] + end + end + + class Retriever + def initialize(path) + @path = path + end + + def call + Hash[ + parameters. + map { |parameter| Parameter.new(parameter) }. + map { |parameter| [parameter.name, parameter.value] } + ] + end + + def self.call(path) + new(path).call + end + + private + + def ssm + @ssm ||= Aws::SSM::Client.new + end + + def parameters + ssm. + get_parameters_by_path(path: @path, with_decryption: true). + parameters + rescue StandardError => error + raise RetrieveError, error + end + end +end diff --git a/psenv.gemspec b/psenv.gemspec index e4f442b..04455d4 100644 --- a/psenv.gemspec +++ b/psenv.gemspec @@ -23,6 +23,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency "bundler", "~> 1.16" spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency "rspec", "~> 3.0" + spec.add_development_dependency "webmock", "~> 3.3" spec.add_dependency "aws-sdk-ssm", "~> 1" end diff --git a/spec/psenv_rails_spec.rb b/spec/psenv_rails_spec.rb new file mode 100644 index 0000000..796a30d --- /dev/null +++ b/spec/psenv_rails_spec.rb @@ -0,0 +1,24 @@ +require "spec_helper" +require "rails" +require "psenv-rails" + +RSpec.describe Psenv::Railtie do + before(:each) do + Rails.env = "test" + Rails.application = double(:application) + end + + context "before_configuration" do + it "calls #load" do + expect(Psenv::Railtie.instance).to receive(:load) + ActiveSupport.run_load_hooks(:before_configuration) + end + end + + context ".load" do + it "calls Psenv.load" do + expect(Psenv).to receive(:load) + Psenv::Railtie.load + end + end +end diff --git a/spec/psenv_spec.rb b/spec/psenv_spec.rb index b9a5d3f..5e34705 100644 --- a/spec/psenv_spec.rb +++ b/spec/psenv_spec.rb @@ -1,7 +1,114 @@ +require "spec_helper" + RSpec.describe Psenv do + let(:env_path) { "/env/" } + let(:arg_paths) { ["/arg1/", "/arg2"] } + let(:env_variables) { { TEST: "env", ANOTHER: "env" } } + let(:arg_variables) do + [{ ONE: "arg1", TWO: "arg1" }, { TWO: "arg2", THREE: "arg2" }] + end + let(:retriever1) { double(:retriever) } + let(:retriever2) { double(:retriever) } + let(:retriever3) { double(:retriever) } + let(:environment) { double(:environment) } + + before(:each) do + allow(Psenv::Retriever).to receive(:new).with(env_path) { retriever1 } + allow(Psenv::Retriever).to receive(:new).with(arg_paths[0]) { retriever2 } + allow(Psenv::Retriever).to receive(:new).with(arg_paths[1]) { retriever3 } + allow(retriever1).to receive(:call) { env_variables } + allow(retriever2).to receive(:call) { arg_variables[0] } + allow(retriever3).to receive(:call) { arg_variables[1] } + allow(Psenv::Environment).to receive(:create) { environment } + allow(environment).to receive(:apply) + allow(environment).to receive(:apply!) + + ENV.store("PARAMETER_STORE_PATH", nil) + end + it "has a version number" do expect(Psenv::VERSION).not_to be nil end - it "tests something useful" + context ".load" do + context "when PARAMETER_STORE_PATH is set" do + before(:each) do + ENV.store("PARAMETER_STORE_PATH", env_path) + Psenv.load + end + + it "retrieves the correct path" do + expect(Psenv::Retriever).to have_received(:new).with(env_path) + end + + it "creates the environment with the correct variables" do + expect(Psenv::Environment). + to have_received(:create).with(env_variables) + end + + it "applies the environment" do + expect(environment).to have_received(:apply) + end + end + + context "when paths are passed in" do + before(:each) { Psenv.load(*arg_paths) } + + it "retrieves the correct paths" do + arg_paths.each do |path| + expect(Psenv::Retriever).to have_received(:new).with(path) + end + end + + it "creates the environment with the correct variables" do + expect(Psenv::Environment). + to have_received(:create).with(*arg_variables) + end + + it "apples the environment" do + expect(environment).to have_received(:apply) + end + end + end + + context ".overload" do + context "when PARAMETER_STORE_PATH is set" do + before(:each) do + ENV.store("PARAMETER_STORE_PATH", env_path) + Psenv.overload + end + + it "retrieves the correct path" do + expect(Psenv::Retriever).to have_received(:new).with(env_path) + end + + it "creates the environment with the correct variables" do + expect(Psenv::Environment). + to have_received(:create).with(env_variables) + end + + it "applies the environment" do + expect(environment).to have_received(:apply!) + end + end + + context "when paths are passed in" do + before(:each) { Psenv.overload(*arg_paths) } + + it "retrieves the correct paths" do + arg_paths.each do |path| + expect(Psenv::Retriever).to have_received(:new).with(path) + end + end + + it "creates the environment with the correct variables" do + expect(Psenv::Environment). + to have_received(:create).with(*arg_variables) + end + + it "apples the environment" do + expect(environment).to have_received(:apply!) + end + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9f2da4e..056b0a9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,9 @@ require "bundler/setup" +require 'webmock/rspec' require "psenv" +WebMock.disable_net_connect!(allow_localhost: true) + RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status"