config_o_mat 0.3.0 → 0.4.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 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