config_o_mat 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd094e1ed00e6019a324570d47475448e924c6c31abe2c9fb7c31cd37064ddfe
4
- data.tar.gz: e9712d360ae7da7adf5c15142bf3a2365465832071998790ebd8e5a089429f9a
3
+ metadata.gz: 26063499446b5cf904e29c5ef7e147c845bc6a2e72e8e37b32148c35f8b4eb96
4
+ data.tar.gz: 0cce14aaef3a7b479bce31bbe8dfeeb4f4159153c999de199227aa8dd49b9602
5
5
  SHA512:
6
- metadata.gz: 1a63b431f2fe289c0defaadc88a7105910d0198bf8dc3862da8effb908953f2b480687cc7750fc4f282cc410dcc6f3dc4e8d54659523e909c07f5e8e8059a1e3
7
- data.tar.gz: 8763d1eff5f21f0cb2cfc603595fb8b4e198dad30ccbb8f2727ee930df1438d8b5a5c5075de674e269b99a25313f6648be160b72ed6caa3d921d595af7c8fb83
6
+ metadata.gz: 94869014f18070dfcb57e3907ad2c16b24d06f5693a353a7979f57e7b5333813ec9a4c458584173ec1622240c3fc6126dae0895ac53facef31fad11775879388
7
+ data.tar.gz: eff0f317b20431cb779765aa04af447a0402fc5ede9f46c69ddcbdc5403b11b85b79989e7315cb0f21262cad1818b06bb6bccf1b7e21a91638e91aba91db53ec
data/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ ## 0.4.0
2
+
3
+ NEW FEATURES:
4
+
5
+ * Facter support. In your configomat config set a top level `facter` key to either truthy or a string. If truthy, facter data will be exposed in a profile named `facter` in all templates. If set to a string, facter data will be exposed in a profile with the given name in all templates.
6
+ * Attempting to access a configuration variable from a profile or secret that does not exist using `#[]` will now raise an exception indicating the key being incorrectly accessed. If you need to access optionally present configuration variables from profiles or secrets use `#fetch(key, nil)`.
7
+
8
+ ## 0.3.0
9
+
10
+ NEW FEATURES:
11
+
12
+ * AWS Secrets Manager support, through an AWS AppConfig configuration. Values to pull from AWS Secrets Manager can be set using an "aws:secrets" key in any loaded AWS AppConfig JSON or YAML configuration. The value must be a dictionary. Keys in this dictionary will be exposed in the #secrets hash on the profile in templates. Values in this dictionary must be a dictionary containing at a minimum a `secret_id` key, which must be the id of an AWS Secrets Manager Secret. The dictionary may also contain a `version_id` or `version_stage` key to indicate which version of the secret to load, and a `content_type` key which may be one of `text/plan`, `application/json`, or `application/x-yaml` to indicate how the secret data should be parsed.
13
+
14
+ ## 0.2.1
15
+
16
+ BUG FIXES:
17
+
18
+ * restart_all actually works.
19
+
20
+ ## 0.2.0
21
+
22
+ NEW FEATURES:
23
+
24
+ * restart_all restart_mode for services to restart all running instances of an instantiated service.
25
+
26
+ ## 0.1.0
27
+
28
+ Initial release
data/Gemfile.lock CHANGED
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- config_o_mat (0.2.1)
4
+ config_o_mat (0.4.0)
5
5
  aws-sdk-appconfig (~> 1.18)
6
6
  aws-sdk-secretsmanager (~> 1.57)
7
+ facter (~> 4.2, >= 4.2.8)
7
8
  lifecycle_vm (~> 0.1.1)
8
9
  logsformyfamily (~> 0.2)
9
10
  ruby-dbus (~> 0.16.0)
@@ -13,25 +14,29 @@ GEM
13
14
  remote: https://rubygems.org/
14
15
  specs:
15
16
  aws-eventstream (1.2.0)
16
- aws-partitions (1.554.0)
17
- aws-sdk-appconfig (1.24.0)
18
- aws-sdk-core (~> 3, >= 3.126.0)
17
+ aws-partitions (1.574.0)
18
+ aws-sdk-appconfig (1.25.0)
19
+ aws-sdk-core (~> 3, >= 3.127.0)
19
20
  aws-sigv4 (~> 1.1)
20
- aws-sdk-core (3.126.1)
21
+ aws-sdk-core (3.130.0)
21
22
  aws-eventstream (~> 1, >= 1.0.2)
22
23
  aws-partitions (~> 1, >= 1.525.0)
23
24
  aws-sigv4 (~> 1.1)
24
25
  jmespath (~> 1.0)
25
- aws-sdk-secretsmanager (1.57.0)
26
- aws-sdk-core (~> 3, >= 3.126.0)
26
+ aws-sdk-secretsmanager (1.59.0)
27
+ aws-sdk-core (~> 3, >= 3.127.0)
27
28
  aws-sigv4 (~> 1.1)
28
29
  aws-sigv4 (1.4.0)
29
30
  aws-eventstream (~> 1, >= 1.0.2)
30
31
  diff-lcs (1.5.0)
31
32
  docile (1.4.0)
32
- jmespath (1.6.0)
33
- lifecycle_vm (0.1.2)
34
- logsformyfamily (0.2.3)
33
+ facter (4.2.9)
34
+ hocon (~> 1.3)
35
+ thor (>= 1.0.1, < 2.0)
36
+ hocon (1.3.1)
37
+ jmespath (1.6.1)
38
+ lifecycle_vm (0.1.3)
39
+ logsformyfamily (0.3.0)
35
40
  rspec (3.11.0)
36
41
  rspec-core (~> 3.11.0)
37
42
  rspec-expectations (~> 3.11.0)
@@ -53,6 +58,7 @@ GEM
53
58
  simplecov_json_formatter (~> 0.1)
54
59
  simplecov-html (0.12.3)
55
60
  simplecov_json_formatter (0.1.4)
61
+ thor (1.2.1)
56
62
 
57
63
  PLATFORMS
58
64
  ruby
data/config_o_mat.gemspec CHANGED
@@ -44,6 +44,7 @@ Gem::Specification.new do |spec|
44
44
  spec.add_dependency('lifecycle_vm', '~> 0.1.1')
45
45
  spec.add_dependency('ruby-dbus', '~> 0.16.0')
46
46
  spec.add_dependency('sd_notify', '~> 0.1.1')
47
+ spec.add_dependency('facter', ['~> 4.2', '>= 4.2.8'])
47
48
 
48
49
  spec.add_development_dependency('simplecov', '~> 0.21.2')
49
50
  spec.add_development_dependency('rspec', '~> 3.10')
@@ -29,56 +29,82 @@ module ConfigOMat
29
29
  self.last_refresh_time = Time.now.to_i
30
30
 
31
31
  profile_defs.each do |(profile_name, definition)|
32
- request = {
33
- application: definition.application, environment: definition.environment,
34
- configuration: definition.profile, client_id: client_id
35
- }
32
+ if definition.kind_of?(ConfigOMat::Profile)
33
+ refresh_appconfig_profile(profile_name, definition)
34
+ elsif definition.kind_of?(ConfigOMat::FacterProfile)
35
+ refresh_facter_profile(profile_name)
36
+ else
37
+ error profile_name, "unknown profile type #{definition.class.name}"
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def refresh_facter_profile(profile_name)
45
+ current_version = applied_profiles&.fetch(profile_name, nil)&.version
46
+ new_profile = ConfigOMat::LoadedFacterProfile.new(profile_name)
36
47
 
37
- current_version = applied_profiles&.fetch(profile_name, nil)&.version
38
- request[:client_configuration_version] = current_version if current_version
48
+ return if new_profile.version == current_version
39
49
 
40
- response =
41
- begin
42
- appconfig_client.get_configuration(request)
43
- rescue StandardError => e
44
- error profile_name, e
45
- nil
46
- end
50
+ logger&.notice(
51
+ :updated_profile,
52
+ name: profile_name, previous_version: current_version, new_version: new_profile.version
53
+ )
47
54
 
48
- next if response.nil?
55
+ profiles_to_apply << LoadedProfile.new(new_profile, nil)
56
+ end
57
+
58
+ def refresh_appconfig_profile(profile_name, definition)
59
+ request = {
60
+ application: definition.application, environment: definition.environment,
61
+ configuration: definition.profile, client_id: client_id
62
+ }
63
+
64
+ current_version = applied_profiles&.fetch(profile_name, nil)&.version
65
+ request[:client_configuration_version] = current_version if current_version
66
+
67
+ response =
68
+ begin
69
+ appconfig_client.get_configuration(request)
70
+ rescue StandardError => e
71
+ error profile_name, e
72
+ nil
73
+ end
49
74
 
50
- loaded_version = response.configuration_version
51
- next if current_version && loaded_version == current_version
75
+ return if response.nil?
52
76
 
53
- logger&.notice(
54
- :updated_profile,
55
- name: profile_name, previous_version: current_version, new_version: loaded_version
56
- )
77
+ loaded_version = response.configuration_version
78
+ return if current_version && loaded_version == current_version
57
79
 
58
- profile = LoadedAppconfigProfile.new(
59
- profile_name, loaded_version, response.content.read, response.content_type
60
- )
80
+ logger&.notice(
81
+ :updated_profile,
82
+ name: profile_name, previous_version: current_version, new_version: loaded_version
83
+ )
61
84
 
62
- loaded_secrets = nil
85
+ profile = LoadedAppconfigProfile.new(
86
+ profile_name, loaded_version, response.content.read, response.content_type
87
+ )
63
88
 
64
- if !profile.secret_defs.empty?
65
- self.secrets_loader_memory ||= ConfigOMat::SecretsLoader::Memory.new(secretsmanager_client: secretsmanager_client)
66
- secrets_loader_memory.update_secret_defs_to_load(profile.secret_defs.values)
89
+ loaded_secrets = nil
67
90
 
68
- vm = ConfigOMat::SecretsLoader::VM.new(secrets_loader_memory).call
91
+ if !profile.secret_defs.empty?
92
+ self.secrets_loader_memory ||= ConfigOMat::SecretsLoader::Memory.new(secretsmanager_client: secretsmanager_client)
93
+ secrets_loader_memory.update_secret_defs_to_load(profile.secret_defs.values)
69
94
 
70
- if vm.errors?
71
- error :"#{profile_name}_secrets", vm.errors
72
- next
73
- end
95
+ vm = ConfigOMat::SecretsLoader::VM.new(secrets_loader_memory).call
74
96
 
75
- loaded_secrets = secrets_loader_memory.loaded_secrets.each_with_object({}) do |(key, value), hash|
76
- hash[value.name] = value
77
- end
97
+ if vm.errors?
98
+ error :"#{profile_name}_secrets", vm.errors
99
+ return
78
100
  end
79
101
 
80
- profiles_to_apply << LoadedProfile.new(profile, loaded_secrets)
102
+ loaded_secrets = secrets_loader_memory.loaded_secrets.each_with_object({}) do |(key, value), hash|
103
+ hash[value.name] = value
104
+ end
81
105
  end
106
+
107
+ profiles_to_apply << LoadedProfile.new(profile, loaded_secrets)
82
108
  end
83
109
  end
84
110
  end
@@ -23,6 +23,38 @@ module ConfigOMat
23
23
  writes :applying_profile, :secrets_loader_memory
24
24
 
25
25
  def call
26
+ if applying_profile.loaded_profile_data.kind_of?(ConfigOMat::LoadedAppconfigProfile)
27
+ refresh_appconfig_profile
28
+ elsif applying_profile.loaded_profile_data.kind_of?(ConfigOMat::LoadedFacterProfile)
29
+ refresh_facter_profile
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def refresh_facter_profile
36
+ profile_name = applying_profile.name
37
+ profile_version = applying_profile.version
38
+ definition = profile_defs[profile_name]
39
+ new_profile = ConfigOMat::LoadedFacterProfile.new(profile_name)
40
+
41
+ if new_profile.version == profile_version
42
+ logger&.warning(
43
+ :no_update,
44
+ name: profile_name, version: profile_version
45
+ )
46
+ return
47
+ end
48
+
49
+ logger&.notice(
50
+ :updated_profile,
51
+ name: profile_name, previous_version: profile_version, new_version: new_profile.version
52
+ )
53
+
54
+ self.applying_profile = LoadedProfile.new(new_profile, nil)
55
+ end
56
+
57
+ def refresh_appconfig_profile
26
58
  profile_name = applying_profile.name
27
59
  profile_version = applying_profile.version
28
60
  definition = profile_defs[profile_name]
@@ -38,7 +38,7 @@ module ConfigOMat
38
38
  @logger = logger
39
39
  end
40
40
 
41
- def update_secret_defs_to_load(defs)
41
+ def update_secret_defs_to_load(defs)
42
42
  @loaded_secrets = {}
43
43
  @secret_defs_to_load = defs
44
44
  self
@@ -126,6 +126,16 @@ module ConfigOMat
126
126
  self.template_defs = instantiate.call(:templates, Template)
127
127
  self.profile_defs = instantiate.call(:profiles, Profile)
128
128
 
129
+ facter = merged_config[:facter]
130
+ if facter
131
+ facter_key = facter.kind_of?(String) ? facter.to_sym : :facter
132
+ if profile_defs.key?(facter_key)
133
+ error :facter, "conflicts with profile #{facter_key}"
134
+ else
135
+ profile_defs[facter_key] = ConfigOMat::FacterProfile.new
136
+ end
137
+ end
138
+
129
139
  self.logger = LogsForMyFamily::Logger.new if !logger
130
140
 
131
141
  log_type = merged_config[:log_type]&.to_sym || :stdout
@@ -14,6 +14,8 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
+ require 'facter'
18
+
17
19
  require 'json'
18
20
  require 'yaml'
19
21
  require 'digest'
@@ -187,6 +189,83 @@ module ConfigOMat
187
189
  end
188
190
  end
189
191
 
192
+ class FacterProfile < ConfigItem
193
+ end
194
+
195
+ class LoadedFacterProfile < ConfigItem
196
+ CLEAR_FROM_FACTER = [
197
+ "memoryfree", "memoryfree_mb", "load_averages", "uptime", "system_uptime", "uptime_seconds", "uptime_hours", "uptime_days",
198
+ {"ec2_metadata" => ["identity-credentials"]},
199
+ {"memory" => [{"system" => ["capacity", "available_bytes", "used", "used_bytes", "available"] }] }
200
+ ].freeze
201
+
202
+ attr_reader :name, :version, :contents
203
+
204
+ def initialize(name)
205
+ @name = name
206
+ load_from_facter
207
+ @version = contents.hash
208
+ end
209
+
210
+ def validate
211
+ error :name, 'must be present' if @name.nil? || @name.empty?
212
+ error :contents, 'must be present' if @contents.nil? || @contents.empty?
213
+ error :contents, 'must be a hash' if !@contents.kind_of?(Hash)
214
+ end
215
+
216
+ def hash
217
+ @name.hash ^ @version
218
+ end
219
+
220
+ def to_h
221
+ @contents
222
+ end
223
+
224
+ def eql?(other)
225
+ return false if !super(other)
226
+ return false if other.version != version || other.name != name
227
+ true
228
+ end
229
+
230
+ private
231
+
232
+ def load_from_facter
233
+ Facter.clear
234
+ # This is to work around a bug in Facter wherein it fails to invalidate a second cache of the
235
+ # IMDSv2 token.
236
+ Facter::Resolvers::Ec2.instance_variable_set(:@v2_token, nil)
237
+ new_facts = Facter.to_hash
238
+ clear(new_facts, CLEAR_FROM_FACTER)
239
+ transform(new_facts)
240
+ @contents = new_facts
241
+ end
242
+
243
+ def clear(hash, diffs)
244
+ diffs.each do |diff|
245
+ if diff.kind_of?(Hash)
246
+ diff.each do |(key, values)|
247
+ clear(hash[key], values) if hash.key?(key)
248
+ end
249
+ elsif hash
250
+ hash.delete(diff)
251
+ end
252
+ end
253
+ end
254
+
255
+ def transform(hash)
256
+ return unless hash.kind_of?(Hash)
257
+ hash.transform_keys!(&:to_sym)
258
+ hash.default_proc = proc { |hash, key| raise KeyError.new("No key #{key.inspect} in profile #{name}", key: key, receiver: hash) }
259
+ hash.each_value do |value|
260
+ if value.kind_of?(Hash)
261
+ transform(value)
262
+ elsif value.kind_of?(Array)
263
+ value.each { |v| transform(v) }
264
+ end
265
+ end
266
+ end
267
+ end
268
+
190
269
  class LoadedAppconfigProfile < ConfigItem
191
270
  attr_reader :name, :version, :contents, :secret_defs
192
271
 
@@ -206,7 +285,12 @@ module ConfigOMat
206
285
  if parser
207
286
  begin
208
287
  @contents = parser.call(contents)
209
- parse_secrets if @contents.kind_of?(Hash)
288
+ if @contents.kind_of?(Hash)
289
+ parse_secrets
290
+ @contents.default_proc = proc do |hash, key|
291
+ raise KeyError.new("No key #{key.inspect} in profile #{name}", key: key, receiver: hash)
292
+ end
293
+ end
210
294
  rescue StandardError => e
211
295
  error :contents, e
212
296
  end
@@ -239,7 +323,7 @@ module ConfigOMat
239
323
  private
240
324
 
241
325
  def parse_secrets
242
- secret_entries = @contents[:"aws:secrets"]
326
+ secret_entries = @contents.fetch(:"aws:secrets", nil)
243
327
  return if secret_entries.nil?
244
328
 
245
329
  error :contents_secrets, 'must be a dictionary' if !secret_entries.kind_of?(Hash)
@@ -305,6 +389,11 @@ module ConfigOMat
305
389
 
306
390
  begin
307
391
  @contents = LoadedAppconfigProfile::PARSERS[content_type].call(secret_string)
392
+ if @contents.kind_of?(Hash)
393
+ @contents.default_proc = proc do |hash, key|
394
+ raise KeyError.new("No key #{key.inspect} in secret #{name}", key: key, receiver: hash)
395
+ end
396
+ end
308
397
  rescue StandardError => e
309
398
  error :contents, e
310
399
  end
@@ -335,22 +424,22 @@ module ConfigOMat
335
424
  class LoadedProfile < ConfigItem
336
425
  extend Forwardable
337
426
 
338
- attr_reader :secrets, :loaded_appconfig_profile
427
+ attr_reader :secrets, :loaded_profile_data
339
428
 
340
- def_delegators :@loaded_appconfig_profile, :name, :version, :contents
429
+ def_delegators :@loaded_profile_data, :name, :version, :contents
341
430
 
342
- def initialize(loaded_appconfig_profile, secrets)
343
- @loaded_appconfig_profile = loaded_appconfig_profile
431
+ def initialize(loaded_profile_data, secrets)
432
+ @loaded_profile_data = loaded_profile_data
344
433
  @secrets = secrets || {}
345
434
 
346
- @errors = @loaded_appconfig_profile.errors if @loaded_appconfig_profile.errors?
435
+ @errors = @loaded_profile_data.errors if @loaded_profile_data.errors?
347
436
  end
348
437
 
349
438
  def validate
350
439
  end
351
440
 
352
441
  def hash
353
- @loaded_appconfig_profile.hash ^ @secrets.hash
442
+ @loaded_profile_data.hash ^ @secrets.hash
354
443
  end
355
444
 
356
445
  def to_h
@@ -359,7 +448,7 @@ module ConfigOMat
359
448
 
360
449
  def eql?(other)
361
450
  return false if !super(other)
362
- return false if other.loaded_appconfig_profile != @loaded_appconfig_profile || other.secrets != @secrets
451
+ return false if other.loaded_profile_data != @loaded_profile_data || other.secrets != @secrets
363
452
  true
364
453
  end
365
454
  end
@@ -15,5 +15,5 @@
15
15
  # limitations under the License.
16
16
 
17
17
  module ConfigOMat
18
- VERSION = "0.3.0"
18
+ VERSION = "0.4.0"
19
19
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: config_o_mat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Scarborough
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-16 00:00:00.000000000 Z
11
+ date: 2022-04-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-appconfig
@@ -94,6 +94,26 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: 0.1.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: facter
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '4.2'
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: 4.2.8
107
+ type: :runtime
108
+ prerelease: false
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - "~>"
112
+ - !ruby/object:Gem::Version
113
+ version: '4.2'
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 4.2.8
97
117
  - !ruby/object:Gem::Dependency
98
118
  name: simplecov
99
119
  requirement: !ruby/object:Gem::Requirement
@@ -135,6 +155,7 @@ files:
135
155
  - ".rspec"
136
156
  - ".ruby-gemset"
137
157
  - ".ruby-version"
158
+ - CHANGELOG.md
138
159
  - Gemfile
139
160
  - Gemfile.lock
140
161
  - LICENSE