ssm_utils 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8dcad91e6c48a3d501224e57692ae7d55d6f7871
4
+ data.tar.gz: 5706e830fbf3304a8fb1c2f0b5e9dea711a4f62c
5
+ SHA512:
6
+ metadata.gz: ff369cd39c2d9103250f8a1cb4c705027a0482846c4c5161e07fab08fccf88eb81d54e27ce5faa6f47de2b0e00e3b57bb275ca54ab06784f070f15f10d8a8599
7
+ data.tar.gz: bb98c3485d7732ff1871ab8678c888f69d4b1785f796cc1be9c33def553a0cce684fdc08d7fef1263d8f7cebad61708bca8c871103dedc599254e81433e02386
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # vi: set ft=ruby
3
+ require 'ssm_utils'
4
+
5
+ SsmUtils::ManageParamsApp.new.run
@@ -0,0 +1,27 @@
1
+ require 'ssm_utils/ssm_reader_driver'
2
+ require 'yaml'
3
+
4
+ module SsmUtils
5
+ class GetParamsCommand
6
+ def initialize(options)
7
+ opt = {
8
+ decrypt: true,
9
+ ssm_root: '/',
10
+ }.merge(options)
11
+
12
+ raise ArgumentError.new("No file path specified") if !opt.key?(:file_out)
13
+ @file_out = opt[:file_out]
14
+ @decrypt = opt[:decrypt]
15
+ @ssm_root = opt[:ssm_root]
16
+ end
17
+
18
+ def execute
19
+ ssm = SsmUtils::SsmReaderDriver.new(decrypt: @decrypt, ssm_root: @ssm_root)
20
+ params = ssm.account_params
21
+
22
+ File.open(@file_out, 'w') do |f|
23
+ YAML.dump(params, f, line_width: -1)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,71 @@
1
+ module SsmUtils
2
+ module HashWalker
3
+
4
+ class Walker
5
+ def initialize(hash, path_delim, block)
6
+ @hash = hash
7
+ @path_delim = path_delim
8
+ @block = block
9
+ end
10
+
11
+ def walk
12
+ walker(nil, @hash)
13
+ end
14
+
15
+
16
+ private
17
+
18
+ def walker(path, node)
19
+ if valid_value_hash? node
20
+ @block.yield(path, node)
21
+ elsif node.is_a? Hash
22
+ node.each do |key, value|
23
+ #nil path = "" + "" + {key}
24
+ #non-nill path = {path} + {path_delim} + {key}
25
+ walker("#{path}#{path.nil? ? "" : @path_delim}#{key}", value)
26
+ end
27
+ else
28
+ @block.yield(path, node)
29
+ end
30
+ end
31
+
32
+ def valid_value_hash?(node)
33
+ return false unless node.is_a?(Hash) && node.has_key?('_value')
34
+ if node.has_key?('_type') && node['_type'] == 'SecureString'
35
+ return false unless node.has_key? '_key'
36
+ end
37
+ true
38
+ end
39
+ end
40
+
41
+ # For a hash { 'a' => { 'b' => { 'c' => 'd' } } } this function will
42
+ # yield to the provided block ('a/b/c', 'd'). Furthermore, hashs containing
43
+ # the keys '_value' and '_type' will be interpreted as leaves and will not
44
+ # be walked into and instead yielded to the block.
45
+ def walk_hash(hash, path_delim, &block)
46
+ raise ArgumentError.new("Block required") unless block_given?
47
+
48
+ if !hash.is_a? Hash
49
+ raise ArgumentError.new("Cannot walk things that aren't hashes")
50
+ end
51
+
52
+ Walker.new(hash, path_delim.to_s, block).walk
53
+ hash
54
+ end
55
+
56
+ #Recursive safe-setting of keys
57
+ def dig_set(hash, key_list, value)
58
+ if !key_list.is_a? Array || key_list.length < 1
59
+ raise ArgumentError.new("Key list cannot be empty or a non-array")
60
+ elsif key_list.length == 1
61
+ hash[key_list[0]] = value
62
+ else
63
+ if hash[key_list[0]].nil?
64
+ hash[key_list[0]] = {}
65
+ end
66
+ dig_set(hash[key_list[0]], key_list[1..-1], value)
67
+ hash
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,57 @@
1
+ require 'commander'
2
+ require 'ssm_utils/hash_walker'
3
+ require 'ssm_utils/version'
4
+ require 'ssm_utils/get_params_command'
5
+ require 'ssm_utils/put_params_command'
6
+
7
+ module SsmUtils
8
+ class ManageParamsApp
9
+ include Commander::Methods
10
+
11
+ def run
12
+ program :name, 'Manage SSM Params'
13
+ program :version, SsmUtils::VERSION
14
+ program :description, 'Manages SSM parameters!'
15
+ program :help, 'Author', 'David Kolb <david.kolb@coxautoinc.com>'
16
+
17
+ command :get do |c|
18
+ c.syntax = 'manage_ssm_params get [OPTIONS]'
19
+ c.description = <<~EOF
20
+ Retrieves an entire tree of your SSM parameter store as a well
21
+ structured YAML document.
22
+ EOF
23
+ c.option '--file FILE', String, 'File to retrieve account to.'
24
+ c.option '--[no-]decrypt', 'Decrypt SecureStrings, default true'
25
+ c.option '--ssm_root PATH_ROOT', String,
26
+ "A path root to retrieve from, default is '/'"
27
+ c.when_called do |args, options|
28
+ options.default(decrypt: true, ssm_root: '/')
29
+ GetParamsCommand.new(
30
+ file_out: options.file,
31
+ decrypt: options.decrypt,
32
+ ssm_root: options.ssm_root
33
+ ).execute
34
+ end
35
+ end
36
+
37
+ command :put do |c|
38
+ c.syntax = 'manage_ssm_params put [OPTIONS]'
39
+ c.description = <<~EOF
40
+ Writes the supplied YAML structure into SSM parameter store using
41
+ the reverse of the mappings used by get.
42
+ EOF
43
+ c.option '--file FILE', String, 'File to retrieve account to.'
44
+ c.option '--[no-]overwrite', 'Overwrite exitings strings, default true'
45
+ c.when_called do |args, options|
46
+ options.default(overwrite: true)
47
+ PutParamsCommand.new(
48
+ in_file: options.file,
49
+ overwrite: options.overwrite
50
+ ).execute
51
+ end
52
+ end
53
+
54
+ run!
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,26 @@
1
+ require 'yaml'
2
+ require 'aws-sdk-ssm'
3
+ require 'ssm_utils/ssm_writer_driver'
4
+
5
+ module SsmUtils
6
+ class PutParamsCommand
7
+ def initialize(options)
8
+ options = {
9
+ overwrite: false
10
+ }.merge(options)
11
+
12
+ raise ArgumentError.new("No input file") unless options.key? :in_file
13
+
14
+ @overwrite = options[:overwrite]
15
+ @in_file = options[:in_file]
16
+ end
17
+
18
+ def execute
19
+ parameters = YAML.load_file(@in_file)
20
+ SsmWriterDriver.new(
21
+ parameters: parameters,
22
+ overwrite: @overwrite
23
+ ).write_parameters
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,75 @@
1
+ require 'aws-sdk-ssm'
2
+ require 'ssm_utils/hash_walker'
3
+
4
+ module SsmUtils
5
+ class SsmReaderDriver
6
+ include HashWalker
7
+
8
+ def initialize(options={})
9
+ options = {
10
+ decrypt: true,
11
+ ssm_root: '/'
12
+ }.merge(options)
13
+ @ssm = Aws::SSM::Client.new()
14
+ @decrypt_flag = options[:decrypt]
15
+ @ssm_root = options[:ssm_root]
16
+ end
17
+
18
+ def raw_account_params
19
+ return @raw_params if @raw_params
20
+
21
+ params = []
22
+
23
+ @ssm.get_parameters_by_path(
24
+ path: @ssm_root,
25
+ recursive: 'true',
26
+ with_decryption: @decrypt_flag
27
+ ).each do |r|
28
+ params += r.to_h[:parameters]
29
+ end
30
+
31
+ @raw_params = params
32
+ end
33
+
34
+ def account_params
35
+ params = {}
36
+ raw_account_params.each do |r|
37
+ if r[:type] == 'String'
38
+ value = r[:value]
39
+ elsif r[:type] == 'SecureString'
40
+ value = {
41
+ '_value' => r[:value],
42
+ '_type' => r[:type],
43
+ '_key' => encryption_key(r)
44
+ }
45
+ else
46
+ value = {
47
+ '_value' => r[:value],
48
+ '_type' => r[:type]
49
+ }
50
+ end
51
+ dig_set(params, key_list(r[:name]), value)
52
+ end
53
+ params
54
+ end
55
+
56
+ def encryption_key(param)
57
+ response = @ssm.get_parameter_history(name: param[:name])
58
+ matched_version = nil
59
+
60
+ # Apparently this is the idiomatic way to do/while. :-/
61
+ loop do
62
+ matched_version = response.parameters.find do |p|
63
+ p.version == param[:version]
64
+ end
65
+ response = response.next_page if response.next_page?
66
+ break unless matched_version.nil? && response.next_page?
67
+ end
68
+ matched_version.nil? ? nil : matched_version.key_id
69
+ end
70
+
71
+ def key_list(param_name)
72
+ param_name[0] == '/' ? param_name[1..-1].split('/') : param_name.split('/')
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,69 @@
1
+ require 'aws-sdk-ssm'
2
+ require 'ssm_utils/hash_walker'
3
+
4
+ module SsmUtils
5
+ class SsmWriterDriver
6
+ include HashWalker
7
+
8
+ def initialize(options)
9
+ options = {
10
+ overwrite: false
11
+ }.merge(options)
12
+
13
+ raise ArgumentError('Missing parameters') unless options.key? :parameters
14
+
15
+ @parameters = options[:parameters]
16
+ @ssm = Aws::SSM::Client.new
17
+ @overwrite = options[:overwrite]
18
+ end
19
+
20
+ def write_parameters
21
+ ssm_requests.each do |request|
22
+ @ssm.put_parameter(request)
23
+ end
24
+ end
25
+
26
+ def ssm_requests
27
+ return @calls if @calls
28
+
29
+ @calls = []
30
+
31
+ walk_hash(@parameters, '/') { |path, value|
32
+ path = "/#{path}"
33
+ if value.is_a? Hash
34
+ @calls.push process_hash_value(path, value)
35
+ else
36
+ @calls.push process_literal_value(path,value)
37
+ end
38
+ }
39
+
40
+ @calls
41
+ end
42
+
43
+ private
44
+
45
+ def process_literal_value(path, value)
46
+ {
47
+ name: path,
48
+ value: value.to_s,
49
+ type: "String",
50
+ overwrite: @overwrite
51
+ }
52
+ end
53
+
54
+ def process_hash_value(path, value)
55
+ call = {
56
+ name: path,
57
+ value: value['_value'].to_s,
58
+ type: value['_type'],
59
+ overwrite: @overwrite
60
+ }
61
+
62
+ if value['_type'] == 'SecureString'
63
+ call[:key_id] = value['_key']
64
+ end
65
+
66
+ call
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module SsmUtils
2
+ VERSION = "0.1.0"
3
+ end
data/lib/ssm_utils.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "ssm_utils/version"
2
+ require "ssm_utils/manage_params_app"
3
+
4
+ module SsmUtils
5
+ # Your code goes here...
6
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ssm_utils
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - David Kolb
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-04-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.11.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.11.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: paint
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: commander
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '4'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: aws-sdk-ssm
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1'
111
+ description: Provides some CLI interfaces into the SSM parameter store with opinions.
112
+ email:
113
+ - david.kolb@krinchan.com
114
+ executables:
115
+ - manage_ssm_params
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - exe/manage_ssm_params
120
+ - lib/ssm_utils.rb
121
+ - lib/ssm_utils/get_params_command.rb
122
+ - lib/ssm_utils/hash_walker.rb
123
+ - lib/ssm_utils/manage_params_app.rb
124
+ - lib/ssm_utils/put_params_command.rb
125
+ - lib/ssm_utils/ssm_reader_driver.rb
126
+ - lib/ssm_utils/ssm_writer_driver.rb
127
+ - lib/ssm_utils/version.rb
128
+ homepage: https://github.com/ssm_utils
129
+ licenses:
130
+ - MIT
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - "~>"
139
+ - !ruby/object:Gem::Version
140
+ version: '2.3'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.6.14
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Utility scripts for managing SSM params
152
+ test_files: []