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,120 @@
1
+ require 'aws-sdk-ssm'
2
+
3
+ module AwsSsmEnv
4
+ # AWSのParameter Storeからパラメータを取得するための基底抽象クラス。Iteratorパターンを実装。
5
+ # このクラスのサブクラスは`fetch`メソッドを実装する必要がある。
6
+ # 実装クラスを AwsSsmEnv#load の引数に渡すことによりパラメータの取得方法を切り替えられるようにする。
7
+ #
8
+ # @abstract
9
+ # @author Ryohei Sonoda
10
+ # @since 0.1.0
11
+ class Fetcher
12
+ # ここの引数は AwsSsmEnv#load の呼び出し時に渡された引数がそのまま渡される。
13
+ # 実装クラスによって`args`の内容は変化するが、decryption, client, ssm_client_args は全サブクラス共通。
14
+ #
15
+ # @param [Hash] args AwsSsmEnv#load の呼び出し時に渡された引数。
16
+ # @option args [Boolean] :decryption
17
+ # <optional> SecureStringパラメータを復号化するかどうか。デフォルトはtrue(復号化する)。
18
+ # @option args [Aws::SSM::Client] :client
19
+ # <optional> Aws::SSM::Clientのインスタンス。
20
+ # 指定されなかった場合は`ssm_client_args`を引数にして Aws::SSM::Client#new される。
21
+ # @option args [Hash] :ssm_client_args
22
+ # Aws::SSM::Client#new を実行するときの引数。
23
+ #
24
+ # @see http://docs.aws.amazon.com/sdk-for-ruby/v3/api/index.html
25
+ def initialize(**args)
26
+ @client = create_ssm_client(args)
27
+ @with_decryption = with_decryption?(args)
28
+ end
29
+
30
+ # Iteratorパターンを実装したメソッド。AwsSsmEnv#load から呼び出される。
31
+ # 実際のパラメータ取得はサブクラスで実装された fetch メソッドで行う。
32
+ # @yield [consumer] 取得したパラメータを受け取って処理を行うブロック引数。
33
+ # @yieldparam [Aws::SSM::Types::Parameter] parameter パラメータ
34
+ def each
35
+ next_token = nil
36
+ loop do
37
+ response = fetch(next_token)
38
+ next_token = response.next_token
39
+ if response.parameters.empty?
40
+ break
41
+ end
42
+ response.parameters.each do |p|
43
+ yield(p)
44
+ end
45
+ if next_token.nil?
46
+ break
47
+ end
48
+ end
49
+ end
50
+
51
+ protected
52
+
53
+ # Parameter Storeの値を取得するメソッド。サブクラスでOverrideする。
54
+ # 戻り値は AwsSsmEnv::FetchResult と同等の構造でなければいけない。
55
+ #
56
+ # @abstract
57
+ # @param [String] _next_token
58
+ # NextTokenが必要な場合に渡される。
59
+ # 利用するAPIによっては1回で取得可能なものもあるので(GetParameters)、
60
+ # このパラメータを利用するかどうかはサブクラスの実装に任せる。
61
+ # @return [AwsSsmEnv::FetchResult] パラメータの配列とNextToken
62
+ def fetch(_next_token)
63
+ raise NotImplementedError, 'fetch'
64
+ end
65
+
66
+ # @return [Boolean] SecureStringを復号化するかどうかのフラグ。
67
+ # サブクラスからのみアクセスを許可するため`attr_reader`は使わない。
68
+ def with_decryption
69
+ @with_decryption
70
+ end
71
+
72
+ # @return [Aws::SSM::Client] aws-sdkのSSMクライアントインスタンス。
73
+ # サブクラスからのみアクセスを許可するため`attr_reader`は使わない。
74
+ def client
75
+ @client
76
+ end
77
+
78
+ private
79
+
80
+ def with_decryption?(decryption: 'true', **)
81
+ if decryption.nil?
82
+ true
83
+ elsif decryption.to_s.downcase == 'true'
84
+ true
85
+ else
86
+ false
87
+ end
88
+ end
89
+
90
+ def create_ssm_client(client: nil, ssm_client_args: {}, **)
91
+ if client.is_a?(Aws::SSM::Client)
92
+ client
93
+ else
94
+ Aws::SSM::Client.new(ssm_client_args)
95
+ end
96
+ end
97
+ end
98
+
99
+ # parametersとnext_tokenを持った値オブジェクト。
100
+ # Fetcerサブクラスのfetchメソッド戻り値として利用する。
101
+ #
102
+ # @attr_reader [Array] parameters
103
+ # name, value プロパティを持ったパラメータオブジェクトの配列。配列要素の実装クラスは実行されるAPIによって変わる。
104
+ # @attr_reader [String] next_token
105
+ # 1回ですべてのパラメータが取得できないAPIを利用する場合に次の値を取得するためのトークン文字列。
106
+ # だいたいのAWSのAPIには用意されているため、戻り値の型であるこのクラスに持たせる。
107
+ #
108
+ # @author Ryohei Sonoda
109
+ # @since 0.1.0
110
+ class FetchResult
111
+ attr_reader :parameters, :next_token
112
+
113
+ def initialize(parameters, next_token = nil)
114
+ @parameters = parameters
115
+ @next_token = next_token
116
+ end
117
+
118
+ EMPTY = new([])
119
+ end
120
+ end
@@ -0,0 +1,84 @@
1
+ require 'aws-ssm-env/fetcher'
2
+
3
+ module AwsSsmEnv
4
+ # パラメータ名の前方一致で取得するFetcherクラスの実装サブクラス。
5
+ # `begins_with`で指定した文字列で始まるパラメータ名に一致するパラメータを取得する。
6
+ # [`ssm:DescribeParameters`, `ssm:GetParameters`]の認可が必要。
7
+ #
8
+ # @author Ryohei Sonoda
9
+ # @since 0.1.0
10
+ class BeginsWithFetcher < Fetcher
11
+ MAX_FETCH_SIZE = 50
12
+ BASE_FILTER = { key: 'Name', option: 'BeginsWith' }.freeze
13
+
14
+ # @param [Hash] args AwsSsmEnv#load の呼び出し時に渡された引数。
15
+ # @option args [String] :begins_with <required> パラメータ名の開始文字列。この文字列が前方一致するパラメータを取得する。
16
+ # @option args [Integet] :fetch_size <optional> 一度のAPI実行で取得するパラメータ数。最大50。デフォルトは50。
17
+ def initialize(**args)
18
+ super
19
+ @base_params = base_params(args)
20
+ end
21
+
22
+ protected
23
+
24
+ # @see AwsSsmEnv::Fetcher#fetch
25
+ #
26
+ # パラメータ名による検索はGetParametersByPathのような便利なAPIはないため、DescribeParametersでパラメータ名による
27
+ # 前方一致検索をしてからGetParametersでパラメータの値を取得している。
28
+ # @see http://docs.aws.amazon.com/systems-manager/latest/APIReference/API_DescribeParameters.html
29
+ # @see http://docs.aws.amazon.com/systems-manager/latest/APIReference/API_GetParameters.html
30
+ def fetch(next_token)
31
+ params = fetch_params(next_token)
32
+ response = client.describe_parameters(params)
33
+ if response.parameters.empty?
34
+ AwsSsmEnv::FetchResult::EMPTY
35
+ else
36
+ parameters = get_parameters(response.parameters)
37
+ AwsSsmEnv::FetchResult.new(parameters, response.next_token)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def base_params(begins_with: nil, fetch_size: '50', **)
44
+ if begins_with.nil?
45
+ raise ArgumentError, ':begins_with is required.'
46
+ end
47
+ {
48
+ parameter_filters: to_parameter_filters(begins_with),
49
+ max_results: detect_max_results(fetch_size)
50
+ }.freeze
51
+ end
52
+
53
+ def detect_max_results(fetch_size)
54
+ if fetch_size.nil?
55
+ MAX_FETCH_SIZE
56
+ elsif fetch_size.to_i > MAX_FETCH_SIZE
57
+ MAX_FETCH_SIZE
58
+ else
59
+ fetch_size.to_i
60
+ end
61
+ end
62
+
63
+ def to_parameter_filters(begins_with)
64
+ values = Array(begins_with)
65
+ [ BASE_FILTER.merge(values: values) ]
66
+ end
67
+
68
+ def fetch_params(next_token)
69
+ if next_token.nil?
70
+ @base_params
71
+ else
72
+ @base_params.merge(next_token: next_token)
73
+ end
74
+ end
75
+
76
+ def get_parameters(parameters)
77
+ response = client.get_parameters(
78
+ names: parameters.map(&:name),
79
+ with_decryption: with_decryption
80
+ )
81
+ response.parameters
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,67 @@
1
+ require 'aws-ssm-env/fetcher'
2
+
3
+ module AwsSsmEnv
4
+ # Parameter Storeからパラメータを取得するためのクラスを取得もしくは生成するファクトリクラス。
5
+ #
6
+ # @author Ryohei Sonoda
7
+ # @since 0.1.0
8
+ class FetcherFactory
9
+ PATH_FETCHER = :path
10
+ BEGINS_WITH_FETCHER = :begins_with
11
+
12
+ class << self
13
+ # Parameter Storeからパラメータを取得するためのクラスを取得もしくは生成する。
14
+ #
15
+ # @param [Hash] args AwsSsmEnv#load に渡された引数がそのまま渡される。
16
+ # @option args [Symbol, AwsSsmEnv::Fetcher, Object] fetch
17
+ # 引数の詳細は AwsSsmEnv#load の説明を参照。
18
+ def create_fetcher(**args)
19
+ fetch_type = args[:fetch]
20
+ case fetch_type
21
+ when nil
22
+ default_fetcher(args)
23
+ when PATH_FETCHER
24
+ create_path_fetcher(args)
25
+ when BEGINS_WITH_FETCHER
26
+ create_begins_with_fetcher(args)
27
+ else
28
+ unless fetcher_instance?(fetch_type)
29
+ raise ArgumentError, 'Possible values for :fetch are either :path, :begins_with, ' \
30
+ + '"AwsSsmEnv::Fetcher" implementation class, an object with "each" method.'
31
+ end
32
+ fetch_type
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def default_fetcher(**args)
39
+ if args.key?(:begins_with)
40
+ create_begins_with_fetcher(args)
41
+ else
42
+ create_path_fetcher(args)
43
+ end
44
+ end
45
+
46
+ def create_path_fetcher(**args)
47
+ require 'aws-ssm-env/fetchers/path'
48
+ AwsSsmEnv::PathFetcher.new(args)
49
+ end
50
+
51
+ def create_begins_with_fetcher(**args)
52
+ require 'aws-ssm-env/fetchers/begins_with'
53
+ AwsSsmEnv::BeginsWithFetcher.new(args)
54
+ end
55
+
56
+ def fetcher_instance?(object)
57
+ if object.is_a?(AwsSsmEnv::Fetcher)
58
+ true
59
+ elsif object.respond_to?(:each)
60
+ true
61
+ else
62
+ false
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,75 @@
1
+ require 'aws-ssm-env/fetcher'
2
+
3
+ module AwsSsmEnv
4
+ # Parameter Storeのパス階層を利用したFetcherクラスの実装サブクラス。
5
+ # `path`と`recursive`を指定してパス階層のパラメータ値をまとめて取得する。
6
+ # `ssm:GetParametersByPath`の認可が必要。
7
+ #
8
+ # @author Ryohei Sonoda
9
+ # @since 0.1.0
10
+ class PathFetcher < Fetcher
11
+ MAX_FETCH_SIZE = 10
12
+
13
+ # @see AwsSsmEnv::Fetcher#initialize
14
+ #
15
+ # @param [Hash] args AwsSsmEnv#load の呼び出し時に渡された引数。
16
+ # @option args [String] :path <required> 取得するパラメータのパス。
17
+ # @option args [Boolean] :recursive <optional> サブパスのパラメータまで取得するかどうか。デフォルトはfalse(pathの階層のみ)。
18
+ # @option args [Integer] :fetch_size <optional> 一度のAPI実行で取得するパラメータ数。最大10。デフォルトは10。
19
+ def initialize(**args)
20
+ super
21
+ @base_params = base_params(args)
22
+ end
23
+
24
+ protected
25
+
26
+ # @see AwsSsmEnv::Fetcher#fetch
27
+ #
28
+ # 指定したパス階層配下のパラメータを取得する。
29
+ # @see http://docs.aws.amazon.com/systems-manager/latest/APIReference/API_GetParametersByPath.html
30
+ def fetch(next_token)
31
+ params = fetch_params(next_token)
32
+ client.get_parameters_by_path(params)
33
+ end
34
+
35
+ private
36
+
37
+ def base_params(path: nil, recursive: 'false', fetch_size: 10, **)
38
+ if path.nil?
39
+ raise ArgumentError, 'path is required.'
40
+ end
41
+ {
42
+ path: path,
43
+ recursive: recursive?(recursive),
44
+ with_decryption: with_decryption,
45
+ max_results: detect_max_results(fetch_size)
46
+ }.freeze
47
+ end
48
+
49
+ def recursive?(recursive)
50
+ if recursive.to_s.downcase == 'true'
51
+ true
52
+ else
53
+ false
54
+ end
55
+ end
56
+
57
+ def detect_max_results(fetch_size)
58
+ if fetch_size.nil?
59
+ MAX_FETCH_SIZE
60
+ elsif fetch_size.to_i > 10
61
+ MAX_FETCH_SIZE
62
+ else
63
+ fetch_size.to_i
64
+ end
65
+ end
66
+
67
+ def fetch_params(next_token)
68
+ if next_token.nil?
69
+ @base_params
70
+ else
71
+ @base_params.merge(next_token: next_token)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,67 @@
1
+ require 'aws-ssm-env/fetchers/factory'
2
+ require 'aws-ssm-env/naming_strategies/factory'
3
+
4
+ module AwsSsmEnv
5
+ # このgemのエントリポイントとなるクラス。メイン処理を行う。
6
+ # AWS EC2 Parameters Storeからパラメータを取得してENVに書き込む。
7
+ #
8
+ # @author Ryohei Sonoda
9
+ # @since 0.1.0
10
+ class Loader
11
+ # メイン処理。引数の詳細は AwsSsmEnv#load を参照。
12
+ def self.load(**args)
13
+ new(args).load
14
+ end
15
+
16
+ def initialize(**args)
17
+ parse_options(args)
18
+ if @logger
19
+ @logger.debug("#{self.class.name} overwrite: #{@overwrite}")
20
+ @logger.debug("#{self.class.name} fetcher: #{@fetcher}")
21
+ @logger.debug("#{self.class.name} naming_strategy: #{@naming_strategy}")
22
+ end
23
+ end
24
+
25
+ def load
26
+ @fetcher.each do |parameter|
27
+ var_name = @naming_strategy.parse_name(parameter)
28
+ @logger.debug("#{self.class.name} #{parameter.name} parameter value into ENV['#{var_name}']") if @logger
29
+ send(@applier, var_name, parameter.value)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def parse_options(**options)
36
+ @logger = options[:logger]
37
+ @fetcher = AwsSsmEnv::FetcherFactory.create_fetcher(options)
38
+ @naming_strategy = AwsSsmEnv::NamingStrategyFactory.create_naming_strategy(options)
39
+ @overwrite = overwrite?(options)
40
+ if @overwrite
41
+ @applier = :apply!
42
+ else
43
+ @applier = :apply
44
+ end
45
+ end
46
+
47
+ # overwriteフラグが指定されているかどうかを返す。
48
+ def overwrite?(overwrite: nil, **)
49
+ if overwrite.nil?
50
+ false
51
+ else
52
+ overwrite.to_s.downcase == 'true'
53
+ end
54
+ end
55
+
56
+ def apply(name, value)
57
+ if ENV[name]
58
+ return
59
+ end
60
+ apply!(name, value)
61
+ end
62
+
63
+ def apply!(name, value)
64
+ ENV[name] = value
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,19 @@
1
+ require 'aws-ssm-env/naming_strategy'
2
+
3
+ module AwsSsmEnv
4
+ # 環境変数名にパラメータ名の階層表現のbasenameを利用するようにするNamingStrategy実装クラス。
5
+ # AwsSsmEnv#load で`naming`を指定しなかった場合にはこのクラスのインスタンスが利用される。
6
+ # 例えば、`/path/to/ENV_NAME`というパラメータ名であればENV['ENV_NAME']にパラメータ値がインジェクションされる。
7
+ #
8
+ # @author Ryohei Sonoda
9
+ # @since 0.1.0
10
+ class BasenameNamingStrategy < NamingStrategy
11
+ # @see AwsSsmEnv::NamingStrategy#parse_name
12
+ #
13
+ # パラメータ名の最後の階層を変数名として返す。
14
+ # @return [String] 環境変数名
15
+ def parse_name(parameter)
16
+ File.basename(parameter.name)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,68 @@
1
+ require 'aws-ssm-env/naming_strategy'
2
+
3
+ module AwsSsmEnv
4
+ # 環境変数名を導出するためのNamingStrategyクラスを取得もしくは生成するファクトリクラス。
5
+ #
6
+ # @author Ryohei Sonoda
7
+ # @since 0.1.0
8
+ class NamingStrategyFactory
9
+ BASENAME_STRATEGY = :basename
10
+ SNAKE_CASE_STRATEGY = :snakecase
11
+
12
+ class << self
13
+ # 環境変数名を導出するためのNamingStrategyクラスを取得もしくは生成する。
14
+ #
15
+ # @param [Hash] args AwsSsmEnv#load に渡された引数がそのまま渡される。
16
+ # @option args [Symbol, AwsSsmEnv::NamingStrategy, Object] naming
17
+ # 引数の詳細は AwsSsmEnv#load の説明を参照。
18
+ def create_naming_strategy(**args)
19
+ naming_strategy = args[:naming]
20
+ if naming_strategy.nil?
21
+ return default_strategy(args)
22
+ end
23
+ case naming_strategy
24
+ when BASENAME_STRATEGY
25
+ create_basename_strategy(args)
26
+ when SNAKE_CASE_STRATEGY
27
+ create_snakecase_strategy(args)
28
+ else
29
+ unknown_naming_strategy(naming_strategy)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def default_strategy(**args)
36
+ create_basename_strategy(args)
37
+ end
38
+
39
+ def create_basename_strategy(**args)
40
+ require 'aws-ssm-env/naming_strategies/basename'
41
+ AwsSsmEnv::BasenameNamingStrategy.new(args)
42
+ end
43
+
44
+ def create_snakecase_strategy(**args)
45
+ require 'aws-ssm-env/naming_strategies/snakecase'
46
+ AwsSsmEnv::SnakeCaseNamingStrategy.new(args)
47
+ end
48
+
49
+ def unknown_naming_strategy(naming_strategy)
50
+ unless naming_strategy_instance?(naming_strategy)
51
+ raise ArgumentError, 'Possible values for :naming are either :basename, :snakecase, ' \
52
+ + '"AwsSsmEnv::NamingStrategy" implementation class, an object with "parse_name" method.'
53
+ end
54
+ naming_strategy
55
+ end
56
+
57
+ def naming_strategy_instance?(object)
58
+ if object.is_a?(AwsSsmEnv::NamingStrategy)
59
+ true
60
+ elsif object.respond_to?(:parse_name)
61
+ true
62
+ else
63
+ false
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end