is2-aws-ssm-env 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,58 @@
1
+ require 'aws-ssm-env/naming_strategy'
2
+
3
+ module AwsSsmEnv
4
+ # パラメータ名の階層表現をスネークケースに変換した値を環境変数名とする。
5
+ # 例えば、`removed_prefix`が`/path`で`/path/to/environment_name`というパラメータ名なら
6
+ # ENV['TO_ENVIRONMENT_NAME']にパラメータ値がインジェクションされる。
7
+ #
8
+ # @author Ryohei Sonoda
9
+ # @since 0.1.0
10
+ class SnakeCaseNamingStrategy < NamingStrategy
11
+ # ここの引数は AwsSsmEnv#load の呼び出し時に渡された引数がそのまま渡される。
12
+ #
13
+ # @param [Hash] args AwsSsmEnv#load の呼び出し時に渡された引数。
14
+ # @option args [String] :removed_prefix
15
+ # パラメータ名から除去するプレフィクス。この文字列は導出される環境変数名に含まない。
16
+ # :removed_prefixが指定されておらず、:begins_with または :path が指定されていた場合はそれを利用する。 TODO: AwsSsmEnv#loadとREADMEに反映
17
+ # @option args [String, Regexp] :delimiter
18
+ # アンダースコアに変換する区切り文字。デフォルトはスラッシュ('/')。 TODO: AwsSsmEnv#loadとREADMEに反映
19
+ def initialize(**args)
20
+ @logger = args[:logger]
21
+ @delimiter = detect_delimiter(args)
22
+ removed_prefix = detect_prefix(args).sub(%r{/\z}, '')
23
+ @removed_prefix = /\A#{Regexp.escape(removed_prefix)}/
24
+ @logger.debug("#{self.class.name} removed_prefix is #{@removed_prefix}") if @logger
25
+ end
26
+
27
+ # @see AwsSsmEnv::NamingStrategy#parse_name
28
+ #
29
+ # パラメータ名からプレフィクスを除去してパス区切りをアンダースコアに変換後、大文字にして返す。
30
+ # @return [String] 環境変数名
31
+ def parse_name(parameter)
32
+ name_without_prefix = parameter.name.gsub(@removed_prefix, '')
33
+ name_without_prefix.gsub(@delimiter, '_').upcase
34
+ end
35
+
36
+ private
37
+
38
+ def detect_delimiter(**args)
39
+ if args[:delimiter].nil?
40
+ '/'
41
+ else
42
+ args[:delimiter]
43
+ end
44
+ end
45
+
46
+ def detect_prefix(**args)
47
+ if args[:removed_prefix]
48
+ args[:removed_prefix]
49
+ elsif args[:begins_with]
50
+ args[:begins_with]
51
+ elsif args[:path]
52
+ args[:path]
53
+ else
54
+ ''
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,23 @@
1
+ module AwsSsmEnv
2
+ # パラメータの値を設定する環境変数名を決定するためのStrategyクラス。
3
+ # 実装クラスを AwsSsmEnv#load の引数で`naming`パラメータとして渡すことにより
4
+ # インジェクションされる環境変数名の命名ルールを切り替えられるようにする。
5
+ #
6
+ # @abstract
7
+ # @author Ryohei Sonoda
8
+ # @since 0.1.0
9
+ class NamingStrategy
10
+ # ここの引数は AwsSsmEnv#load の呼び出し時に渡された引数がそのまま渡される。
11
+ # サブクラスでは必要に応じて使う引数をインスタンス変数に保持しておく。
12
+ #
13
+ # @param [Hash] ** AwsSsmEnv#load の呼び出し時に渡された引数。
14
+ def initialize(**); end
15
+
16
+ # パラメータから環境変数名を導出するメソッド。
17
+ # @abstract
18
+ # @return [String] 環境変数名
19
+ def parse_name(_parameter)
20
+ raise NotImplementedError, 'parse_name'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module AwsSsmEnv
2
+ VERSION = '0.1.1'.freeze
3
+ end
@@ -0,0 +1,158 @@
1
+ require 'spec_helper'
2
+
3
+ describe AwsSsmEnv::Fetcher do
4
+ let(:fetcher) { described_class.new(args) }
5
+ let(:args) { {} }
6
+ let(:client) { fetcher.send(:client) }
7
+ let(:ssm_client_args) {
8
+ {
9
+ access_key_id: 'access_key_id',
10
+ secret_access_key: 'secret_access_key'
11
+ }
12
+ }
13
+
14
+ describe '#initialize' do
15
+ context 'when decryption was not set' do
16
+ it 'with_decryption is true' do
17
+ expect(fetcher.send(:with_decryption)).to be_truthy
18
+ end
19
+ end
20
+
21
+ context 'when decryption is nil' do
22
+ let(:args) { { decryption: nil } }
23
+
24
+ it 'with_decryption is true' do
25
+ expect(fetcher.send(:with_decryption)).to be_truthy
26
+ end
27
+ end
28
+
29
+ context 'when decryption is truthy string' do
30
+ let(:args) { { decryption: 'TrUe' } }
31
+
32
+ it 'with_decryption is true' do
33
+ expect(fetcher.send(:with_decryption)).to be_truthy
34
+ end
35
+ end
36
+
37
+ context 'when decryption is not truthy string' do
38
+ let(:args) { { decryption: 'foo' } }
39
+
40
+ it 'with_decryption is false' do
41
+ expect(fetcher.send(:with_decryption)).to be_falsey
42
+ end
43
+ end
44
+
45
+ context 'when client was not set' do
46
+ context 'when ssm_client_args was set' do
47
+ let(:args) { { ssm_client_args: ssm_client_args } }
48
+
49
+ it 'client is initialized by ssm_client_args' do
50
+ expect(client.config[:access_key_id]).to eq('access_key_id')
51
+ expect(client.config[:secret_access_key]).to eq('secret_access_key')
52
+ end
53
+ end
54
+
55
+ context 'when ssm_client_args was not set' do
56
+ it 'client is default construction' do
57
+ expect(client.config[:access_key_id]).to be_nil
58
+ end
59
+ end
60
+ end
61
+
62
+ context 'when client was set' do
63
+ context 'when client is instance of Aws::SSM::Client' do
64
+ let(:ssm_client) { Aws::SSM::Client.new }
65
+ let(:args) { { client: ssm_client } }
66
+
67
+ it 'client is equals to args[:client]' do
68
+ expect(client).to eq(ssm_client)
69
+ end
70
+ end
71
+
72
+ context 'when client is not instance of Aws::SSM::Client' do
73
+ let(:ssm_client) { 'foo' }
74
+ let(:args) { { client: ssm_client, ssm_client_args: ssm_client_args } }
75
+
76
+ it 'client is not equals to args[:client] and client is initialized by ssm_client_args' do
77
+ expect(client).not_to eq(ssm_client)
78
+ expect(client.config[:access_key_id]).to eq('access_key_id')
79
+ expect(client.config[:secret_access_key]).to eq('secret_access_key')
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ describe '#each' do
86
+ let(:parameters) { [ Parameter.new('foo', 'foo'), Parameter.new('bar', 'bar') ] }
87
+ let(:fetcher) {
88
+ mock_class = Class.new(described_class) do
89
+ def initialize(response); @response = response; end
90
+ protected def fetch(_); @response; end
91
+ end
92
+ mock_class.new(dummy_response)
93
+ }
94
+
95
+ context 'when fetch returns empty parameters at first' do
96
+ let(:dummy_response) { AwsSsmEnv::FetchResult::EMPTY }
97
+
98
+ it 'consumer is not called' do
99
+ called = false
100
+ fetcher.each do |_|
101
+ called = true
102
+ end
103
+ expect(called).to be_falsey
104
+ end
105
+ end
106
+
107
+ context 'when fetch returns two parameters at first and empty next_token' do
108
+ let(:dummy_response) { AwsSsmEnv::FetchResult.new(parameters, nil) }
109
+
110
+ it 'consumer is called twice' do
111
+ called = 0
112
+ fetcher.each do |_|
113
+ called += 1
114
+ end
115
+ expect(called).to eq(2)
116
+ end
117
+ end
118
+
119
+ context 'when fetch returns two parameters and next_token at first, fetch returns two parameters and empty next_token at second' do
120
+ let(:fetcher) {
121
+ mock_class = Class.new(described_class) do
122
+ def initialize(parameters); @parameters = parameters; @count = 0; end
123
+ protected def fetch(_)
124
+ if @count == 0
125
+ @count = 1
126
+ AwsSsmEnv::FetchResult.new(@parameters, 'next_token')
127
+ else
128
+ AwsSsmEnv::FetchResult.new(@parameters, nil)
129
+ end
130
+ end
131
+ end
132
+ mock_class.new(parameters)
133
+ }
134
+
135
+ it 'consumer is called four times' do
136
+ called = 0
137
+ fetcher.each do |_|
138
+ called += 1
139
+ end
140
+ expect(called).to eq(4)
141
+ end
142
+ end
143
+ end
144
+
145
+ describe '#fetch' do
146
+ it 'raise error' do
147
+ expect { fetcher.send(:fetch, 'next_token') }.to raise_error(NotImplementedError)
148
+ end
149
+ end
150
+ end
151
+
152
+ describe AwsSsmEnv::FetchResult do
153
+ describe '#initialize' do
154
+ subject { described_class.new([], 'next_token') }
155
+
156
+ it { is_expected.to have_attributes(parameters: [], next_token: 'next_token') }
157
+ end
158
+ end
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+ require 'aws-ssm-env/fetchers/begins_with'
3
+
4
+ describe AwsSsmEnv::BeginsWithFetcher do
5
+ let(:fetcher) { described_class.new(args) }
6
+ let(:args) { { begins_with: [ '/foo', '/bar' ] } }
7
+ let(:base_params) { fetcher.instance_variable_get(:'@base_params') }
8
+ let(:parameter_filter) { base_params[:parameter_filters][0] }
9
+
10
+ describe '#initialize' do
11
+ context 'when :begins_with was not set' do
12
+ it 'raise error' do
13
+ expect { described_class.new(begins_with: nil) }.to raise_error(ArgumentError)
14
+ end
15
+ end
16
+
17
+ context 'when :begins_with is Array' do
18
+ it 'parameter_filter[:values] is begins_with value' do
19
+ expect(parameter_filter[:values]).to eq(args[:begins_with])
20
+ end
21
+ end
22
+
23
+ context 'when :begins_with is not Array' do
24
+ let(:args) { { begins_with: '/foo' } }
25
+
26
+ it 'parameter_filter[:values] is [ begins_with value ]' do
27
+ expect(parameter_filter[:values]).to eq([ args[:begins_with] ])
28
+ end
29
+ end
30
+
31
+ context 'when :fetch_size was set and less than 50' do
32
+ let(:args) { { begins_with: '/foo', fetch_size: 49 } }
33
+
34
+ it '@base_params[:max_results] is fetch_size value' do
35
+ expect(base_params[:max_results]).to eq(49)
36
+ end
37
+ end
38
+
39
+ context 'when :fetch_size was not set' do
40
+ let(:args) { { begins_with: '/foo', fetch_size: nil } }
41
+
42
+ it '@base_params[:max_results] is 50' do
43
+ expect(base_params[:max_results]).to eq(50)
44
+ end
45
+ end
46
+
47
+ context 'when :fetch_size > 50' do
48
+ let(:args) { { begins_with: '/foo', fetch_size: 51 } }
49
+
50
+ it '@base_params[:max_results] is 50' do
51
+ expect(base_params[:max_results]).to eq(50)
52
+ end
53
+ end
54
+ end
55
+
56
+ describe '#fetch' do
57
+ let(:client) { fetcher.send(:client) }
58
+
59
+ context 'when describe_parameters return empty parameters' do
60
+ before do
61
+ allow_any_instance_of(Aws::SSM::Client).to \
62
+ receive(:describe_parameters).and_return(AwsSsmEnv::FetchResult::EMPTY)
63
+ end
64
+
65
+ it 'return AwsSsmEnv::FetchResult::EMPTY' do
66
+ expect(fetcher.send(:fetch, nil)).to eq(AwsSsmEnv::FetchResult::EMPTY)
67
+ end
68
+
69
+ context 'when next_token is nil' do
70
+ it 'called describe_parameters without next_token' do
71
+ expect(fetcher.send(:fetch, nil)).to eq(AwsSsmEnv::FetchResult::EMPTY)
72
+ expect(client).to \
73
+ have_received(:describe_parameters).with(base_params).once
74
+ end
75
+ end
76
+
77
+ context 'when next_token is not nil' do
78
+ it 'called get_parameters_by_path with next_token' do
79
+ expect(fetcher.send(:fetch, 'next_token')).to eq(AwsSsmEnv::FetchResult::EMPTY)
80
+ expect(client).to have_received(:describe_parameters)
81
+ .with(base_params.merge(next_token: 'next_token')).once
82
+ end
83
+ end
84
+ end
85
+
86
+ context 'when describe_parameters return not empty parameters' do
87
+ let!(:dummy_parameters) { [ Parameter.new('foo'), Parameter.new('bar') ] }
88
+ let!(:dummy_response) { AwsSsmEnv::FetchResult.new(dummy_parameters, 'next_token') }
89
+
90
+ before do
91
+ allow_any_instance_of(Aws::SSM::Client).to \
92
+ receive(:describe_parameters).and_return(dummy_response)
93
+ allow_any_instance_of(Aws::SSM::Client).to \
94
+ receive(:get_parameters).and_return(dummy_response)
95
+ end
96
+
97
+ it 'return parameters' do
98
+ response = fetcher.send(:fetch, 'next_token')
99
+ expect(response.parameters).to eq(dummy_response.parameters)
100
+ expect(response.next_token).to eq(dummy_response.next_token)
101
+ expect(client).to have_received(:get_parameters)
102
+ .with(names: dummy_parameters.map(&:name), with_decryption: true).once
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe AwsSsmEnv::FetcherFactory do
4
+ describe '#create_fetcher' do
5
+ let(:fetcher) { described_class.create_fetcher(args) }
6
+
7
+ context 'when fetch was not set' do
8
+ context 'when begins_with was not set' do
9
+ let(:args) { { path: '/path' } }
10
+
11
+ it 'return AwsSsmEnv::PathFetcher' do
12
+ expect(fetcher).to be_a(AwsSsmEnv::PathFetcher)
13
+ end
14
+ end
15
+
16
+ context 'when begins_with was set' do
17
+ let(:args) { { begins_with: '/path' } }
18
+
19
+ it 'return AwsSsmEnv::BeginsWithFetcher' do
20
+ expect(fetcher).to be_a(AwsSsmEnv::BeginsWithFetcher)
21
+ end
22
+ end
23
+ end
24
+
25
+ context 'when fetch is :path' do
26
+ let(:args) { { fetch: :path, path: '/path' } }
27
+
28
+ it 'return AwsSsmEnv::PathFetcher' do
29
+ expect(fetcher).to be_a(AwsSsmEnv::PathFetcher)
30
+ end
31
+ end
32
+
33
+ context 'when fetch is :begins_with' do
34
+ let(:args) { { fetch: :begins_with, begins_with: '/path' } }
35
+
36
+ it 'return AwsSsmEnv::BeginsWithFetcher' do
37
+ expect(fetcher).to be_a(AwsSsmEnv::BeginsWithFetcher)
38
+ end
39
+ end
40
+
41
+ context 'when fetch is AwsSsmEnv::Fetcher implementation instance' do
42
+ let(:fetcher_class) { Class.new(AwsSsmEnv::Fetcher) { def fetch(_); end } }
43
+ let(:fetcher_instance) { fetcher_class.new }
44
+ let(:args) { { fetch: fetcher_instance } }
45
+
46
+ it 'return it as is' do
47
+ expect(fetcher).to eq(fetcher_instance)
48
+ end
49
+ end
50
+
51
+ context 'when fetch has each method' do
52
+ let(:args) { { fetch: [] } }
53
+
54
+ it 'return it as is' do
55
+ expect(fetcher).to eq([])
56
+ end
57
+ end
58
+
59
+ context 'in other cases' do
60
+ it 'raise error' do
61
+ expect { described_class.create_fetcher(fetch: 'foo') }.to raise_error(ArgumentError)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+ require 'aws-ssm-env/fetchers/path'
3
+
4
+ describe AwsSsmEnv::PathFetcher do
5
+ let(:fetcher) { described_class.new(args) }
6
+ let(:args) { { path: '/path' } }
7
+ let(:base_params) { fetcher.instance_variable_get(:'@base_params') }
8
+
9
+ describe '#initialize' do
10
+ context 'when path was not set' do
11
+ it 'raise error' do
12
+ expect { described_class.new(path: nil) }.to raise_error(ArgumentError)
13
+ end
14
+ end
15
+
16
+ context 'when :path was set' do
17
+ it '@base_params[:path] is argument value' do
18
+ expect(base_params[:path]).to eq('/path')
19
+ end
20
+ end
21
+
22
+ context 'when recursive was not set' do
23
+ it '@base_params[:recursive] is false' do
24
+ expect(base_params[:recursive]).to be_falsey
25
+ end
26
+ end
27
+
28
+ context 'when recursive is truthy string' do
29
+ let(:args) { { path: '/path', recursive: 'TruE' } }
30
+
31
+ it '@base_params[:recursive] is true' do
32
+ expect(base_params[:recursive]).to be_truthy
33
+ end
34
+ end
35
+
36
+ context 'when recursive is not truthy string' do
37
+ let(:args) { { path: '/path', recursive: 'foo' } }
38
+
39
+ it '@base_params[:recursive] is false' do
40
+ expect(base_params[:recursive]).to be_falsey
41
+ end
42
+ end
43
+
44
+ context 'when :fetch_size was set and less than 10' do
45
+ let(:args) { { path: '/path', fetch_size: 5 } }
46
+
47
+ it '@base_params[:max_results] is fetch_size value' do
48
+ expect(base_params[:max_results]).to eq(5)
49
+ end
50
+ end
51
+
52
+ context 'when :fetch_size was not set' do
53
+ it '@base_params[:max_results] is 10' do
54
+ expect(base_params[:max_results]).to eq(10)
55
+ end
56
+ end
57
+
58
+ context 'when :fetch_size is nil' do
59
+ let(:args) { { path: '/path', fetch_size: nil } }
60
+
61
+ it '@base_params[:max_results] is 10' do
62
+ expect(base_params[:max_results]).to eq(10)
63
+ end
64
+ end
65
+
66
+ context 'when :fetch_size > 10' do
67
+ let(:args) { { path: '/path', fetch_size: 11 } }
68
+
69
+ it '@base_params[:max_results] is 10' do
70
+ expect(base_params[:max_results]).to eq(10)
71
+ end
72
+ end
73
+ end
74
+
75
+ describe '#fetch' do
76
+ before do
77
+ allow_any_instance_of(Aws::SSM::Client).to \
78
+ receive(:get_parameters_by_path).and_return(nil)
79
+ end
80
+
81
+ let(:client) { fetcher.send(:client) }
82
+
83
+ context 'when next_token is nil' do
84
+ it 'called get_parameters_by_path without next_token' do
85
+ expect(fetcher.send(:fetch, nil)).to be_nil
86
+ expect(client).to \
87
+ have_received(:get_parameters_by_path).with(base_params).once
88
+ end
89
+ end
90
+
91
+ context 'when next_token is not nil' do
92
+ it 'called get_parameters_by_path with next_token' do
93
+ expect(fetcher.send(:fetch, 'next_token')).to be_nil
94
+ expect(client).to \
95
+ have_received(:get_parameters_by_path)
96
+ .with(base_params.merge(next_token: 'next_token')).once
97
+ end
98
+ end
99
+ end
100
+ end