aws2-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,160 @@
1
+ require 'aws-ssm-env/loader'
2
+ #
3
+ # AWS EC2 Parameters Storeからパラメータを取得してENVに書き込むモジュール。
4
+ #
5
+ # @author Ryohei Sonoda
6
+ # @since 0.1.0
7
+ module AwsSsmEnv
8
+ module_function
9
+
10
+ # メイン処理。EC2 Parameter Storeからパラメータを取得して環境変数にインジェクションする。
11
+ #
12
+ # @param [Hash] args この処理で利用するすべての引数をまとめて渡す。
13
+ #
14
+ # @option args [Boolean] decryption
15
+ #
16
+ # SecureStringのパラメータを復号化するかどうかを表すフラグ。
17
+ # `true`を指定した場合は取得したSecureStringパラメータの値は復号化されている。
18
+ # `false`の場合は暗号化されたまた環境変数値として設定される。
19
+ # なお、このためのgemなのでデフォルトは`true`(復号化する)。
20
+ #
21
+ # @option args [Boolean] overwrite
22
+ #
23
+ # すでに設定されている環境変数を上書きするかどうかを指定する。
24
+ # `true`を指定した場合、環境変数が設定されていても取得したパラメータ値で上書きする。
25
+ # `false`を指定した場合はすでに設定されている環境変数を上書きしない。
26
+ # デフォルトは`false`(上書きしない)。
27
+ # なお、`AwsSsmEnv#load!`を実行した場合、このフラグは自動的に`true`になる。
28
+ #
29
+ # @option args [Aws::SSM::Client] :client
30
+ #
31
+ # `Aws::SSM::Client`のインスタンスを指定する。
32
+ # すでに生成済みのインスタンスがある場合にそれを設定するためのオプション。
33
+ # 生成済みのインスタンスがない場合は`ssm_client_args`を利用する。
34
+ #
35
+ # @option args [Hash] :ssm_client_args
36
+ #
37
+ # `Aws::SSM::Client`のコンストラクタに渡すハッシュを指定する。
38
+ # 指定しなかった場合は引数なしで`Aws::SSM::Client.new`が呼ばれる。
39
+ # 環境変数やEC2インスタンスプロファイルによる認証情報を利用する場合は不要。
40
+ #
41
+ # @option args [Symbol, AwsSsmEnv::Fetcher, Object] :fetch
42
+ #
43
+ # パラメータ取得方法を指定する。
44
+ # 指定可能な値は`:path`, `:begins_with`または`AwsSsmEnv::Fetcher`を実装したクラスのインスタンス、`each`メソッドを
45
+ # 持ったクラスのインスタンスのいずれか。
46
+ # 何も指定されていない場合は`:path`として扱われるが、後述の`begins_with`が指定されていた場合は自動的に`:begins_with`となる。
47
+ #
48
+ # `:path`を指定した場合はパラメータ階層をパス指定で取得する`AwsSsmEnv::PathFetcher`が利用される。
49
+ # この場合は後述の`path`引数が必須となる。また、後述の`recursive`引数を利用する。
50
+ # この方法でパラメータを取得する場合は指定するパスに対して`ssm:GetParametersByPath`の権限が必要。
51
+ # 以下、IAMポリシーの例を示す。
52
+ # {
53
+ # "Version": "2012-10-17",
54
+ # "Statement": [
55
+ # {
56
+ # "Sid": "",
57
+ # "Effect": "Allow",
58
+ # "Action": "ssm:GetParametersByPath",
59
+ # "Resource": "arn:aws:ssm:YOUR_REGION:YOUR_ACCOUNT_ID:parameter/your_path"
60
+ # }
61
+ # ]
62
+ # }
63
+ #
64
+ # `:begins_with`を指定した場合はパラメータ名が指定した文字列から開始するパラメータを取得する`AwsSsmEnv::BeginsWithFetcher`が利用される。
65
+ # この場合は後述の`begins_with`引数が必須となる。
66
+ # この方法でパラメータを取得する場合は指定するパスに対して`ssm:DescribeParameters`および`ssm:GetParameters`の権限が必要。
67
+ # 以下、IAMポリシーの例を示す。
68
+ # {
69
+ # "Version": "2012-10-17",
70
+ # "Statement": [
71
+ # {
72
+ # "Sid": "",
73
+ # "Effect": "Allow",
74
+ # "Action": "ssm:DescribeParameters",
75
+ # "Resource": "arn:aws:ssm:YOUR_REGION:YOUR_ACCOUNT_ID:parameter"
76
+ # },
77
+ # {
78
+ # "Sid": "",
79
+ # "Effect": "Allow",
80
+ # "Action": "ssm:GetParameters",
81
+ # "Resource": "arn:aws:ssm:YOUR_REGION:YOUR_ACCOUNT_ID:parameter/your_path/*"
82
+ # }
83
+ # ]
84
+ # }
85
+ #
86
+ # `fetch`に`AwsSsmEnv::Fetcher`を実装したクラスのインスタンス、もしくは`each`メソッドを持つ
87
+ # インスタンスを指定した場合はそのインスタンスをそのまま利用する。
88
+ #
89
+ # @option args [Symbol, AwsSsmEnv::NamingStrategy, Object] :naming
90
+ #
91
+ # 環境変数名を導出方法を指定する。
92
+ # 指定可能な値は`:basename`, `:snakecase`または`AwsSsmEnv::NamingStrategy`を実装したクラスのインスタンス、`parse_name`メソッドを持ったクラスのインスタンスのいずれか。
93
+ # デフォルトは`:basename`。
94
+ #
95
+ # `naming`を指定しなかった場合、もしくは`:basename`を指定した場合はパラメータ階層の最後の階層を変数名とする`AwsSsmEnv::BasenameNamingStrategy`が利用される。
96
+ # この場合、例えば`/myapp/production/DB_PASSWORD`というパラメータ名であれば`ENV['DB_PASSWORD']`にパラメータ値がインジェクションされる。
97
+ #
98
+ # `:snakecase`を指定した場合はパラメータ名のスラッシュ区切りをアンダースコア区切りにした結果を大文字に変換して環境変数名とする`AwsSsmEnv::SnakeCaseNamingStrategy`が利用される。
99
+ # この場合、例えば`/myapp/production/DB_PASSWORD`というパラメータ名であれば`ENV['MYAPP_PRODUCTION_DB_PASSWORD']`にパラメータ値がインジェクションされる。
100
+ # 後述の`removed_prefix`引数で除外する先頭文字列を指定することができる。
101
+ # また、後述の`delimiter`オプションでアンダースコアに変換する文字を指定できる。
102
+ # 以下の例では`/myapp/production/db/password`というパラメータが`ENV['DB_PASSWORD']`にインジェクションされる。
103
+ # > AwsSsmEnv.load(naming: :snakecase, removed_prefix: '/myapp/production')
104
+ #
105
+ # `AwsSsmEnv::NamingStrategy`を実装したクラスのインスタンス、もしくは`parse_name`メソッドを持つ
106
+ # インスタンスを指定した場合はそのインスタンスをそのまま利用する。
107
+ #
108
+ # @option args [String] :path
109
+ #
110
+ # `fetch`に何も指定していない場合、もしくは`:path`を指定した場合は必須となる。
111
+ # パラメータを取得するパス階層を指定する。
112
+ # 下の例では`/myapp/web/production`直下のパラメータが取得される。
113
+ # > AwsSsmEnv.load(path: '/myapp/web/production')
114
+ #
115
+ # @option args [Boolean] :recursive
116
+ #
117
+ # `fetch`に何も指定していない場合、もしくは`:path`を指定した場合に利用する。
118
+ # 指定したパス階層以下のパラメータをすべて取得する。
119
+ # 下の例では`/myapp/web/production`以下すべてのパラメータが取得される。
120
+ # > AwsSsmEnv.load(path: '/myapp/web/production', recursive: true)
121
+ #
122
+ # @option args [String, Array<String>] :begins_with
123
+ #
124
+ # `fetch`に`:begins_with`を指定した場合は必須となる。
125
+ # 取得するパラメータ名のプレフィクスを指定する。配列で複数指定することも可能(OR条件となる)。
126
+ # 下の例では`myapp.web.production`で始まる名前のパラメータが取得される。
127
+ #
128
+ # 下の例では 'myapp.web.production' で始まる名前のパラメータが取得される。
129
+ # irb> AwsSsmEnv.load(path: 'myapp.web.production')
130
+ #
131
+ # @option args [String] :removed_prefix
132
+ #
133
+ # `naming`に`:snakecase`を指定した場合に利用される。
134
+ # 環境変数名から除外するパラメータ名のプレフィクスを指定する。
135
+ # `:removed_prefix`が指定されておらず、`:begins_with`もしくは`:path`が指定されていた場合はそれを利用する。
136
+ #
137
+ # @option args [String, Regexp] :delimiter
138
+ #
139
+ # `naming`に`:snakecase`を指定した場合に利用される。
140
+ # アンダースコアに変換する文字列もしくは正規表現を指定する。
141
+ # デフォルトはスラッシュ(`/`)。
142
+ #
143
+ # @option args [Integer] fetch_size
144
+ #
145
+ # 一度のAWS API実行で取得するパラメータ数を指定する。 `:path`指定の場合は最大値は`10`でデフォルトも`10`。
146
+ # `:begins_with`指定の場合は最大値は`50`でデフォルトも`50`である。通常このパラメータを指定することはない。
147
+ #
148
+ # AwsSsmEnv::Loader#load の委譲メソッド。
149
+ #
150
+ # @see AwsSsmEnv::Loader#load
151
+ def load(**args)
152
+ AwsSsmEnv::Loader.load(args)
153
+ end
154
+
155
+ # `overwrite`オプションを付与した AwsSsmEnv::Loader#load の委譲メソッド。
156
+ # @see AwsSsmEnv::Loader#load
157
+ def load!(**args)
158
+ AwsSsmEnv::Loader.load(args.merge(overwrite: true))
159
+ end
160
+ end
@@ -0,0 +1,120 @@
1
+ require 'aws-sdk'
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