legion-settings 1.3.25 → 1.3.27

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: '028759fea7f4684e657e735676b17ebe147f71781e8bf3aac758fb226c8be81d'
4
- data.tar.gz: 56d57df1ca491e5976c2f654701aad0d93fd8d3cf8347c4a4e2c9eb3a487941a
3
+ metadata.gz: 6a096c46e4b074a4e77c104f21b1dc89b953ad15370e514ccd7face8cf8a7b8f
4
+ data.tar.gz: 06475bdc9bf41c6219e2c61fef17006346bb57b833a9a6c992a02f1d1178047a
5
5
  SHA512:
6
- metadata.gz: c75da04c00df4cd9db84c50c3327e9b5c80ad5a4137531cb1aad9d5e8efc8809736d97359c8de2c791aed1848e1b2b0c2948d27a50fee24ed3263e54de9d63ab
7
- data.tar.gz: 51d80d06a89f8ccc4284d6ff90741e2f083f08827ecd8c6ab853d7ff913aad24780ac22e25b0999fcd9464544161b15f5983bd824ea62ac3cfce6ca4e3b1acd2
6
+ metadata.gz: 30cc986020bd6c2f4c783193f65fb07b935e08ddadd1a00608a90001a8312da19e88612e99449bc283ae4d716d119f22517c37809670f50e854a8658502c645f
7
+ data.tar.gz: 7d41547be02a86b3afc065e8865dbe16c02ebde7b592bd7f9a83585b22d7f07cfc1fe078eb171b754f2398d9fce9942410e8b9b999e744a73ee6abf378ada1bb
data/.gitignore CHANGED
@@ -9,7 +9,8 @@
9
9
  /tmp/
10
10
  /legion/.idea/
11
11
  /.idea/
12
+ *.gem
12
13
  *.key
13
14
  # rspec failure tracking
14
15
  .rspec_status
15
- legionio.key
16
+ legionio.key
@@ -0,0 +1,29 @@
1
+ # Standard LegionIO pre-commit configuration
2
+ # Install: pre-commit install
3
+ # Manual: pre-commit run --all-files
4
+ repos:
5
+ - repo: https://github.com/pre-commit/pre-commit-hooks
6
+ rev: v5.0.0
7
+ hooks:
8
+ - id: trailing-whitespace
9
+ - id: end-of-file-fixer
10
+ - id: check-yaml
11
+ - id: check-json
12
+ exclude: Gemfile\.lock
13
+ - id: check-merge-conflict
14
+
15
+ - repo: local
16
+ hooks:
17
+ - id: rubocop
18
+ name: RuboCop (autofix)
19
+ entry: scripts/pre-commit-rubocop.sh
20
+ language: script
21
+ types: [ruby]
22
+ pass_filenames: true
23
+
24
+ - id: ruby-syntax
25
+ name: Ruby syntax check
26
+ entry: ruby -c
27
+ language: system
28
+ types: [ruby]
29
+ pass_filenames: true
data/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Legion::Settings Changelog
2
2
 
3
+ ## [1.3.27] - 2026-04-27
4
+
5
+ ### Added
6
+ - `Settings.reload!` — re-reads all previously loaded config files and re-resolves vault://, env://, and lease:// references; returns a hash of changed keys with old/new values; thread-safe via internal mutex
7
+ - `Settings.watch!` — registers a SIGHUP handler that triggers `reload!` in a background thread; optionally accepts a block for change notification
8
+ - `Settings.on_reload(&block)` — register callbacks invoked after `reload!` detects changes; multiple callbacks supported, called in order, rescue-safe
9
+ - Private `diff_settings` helper for deep comparison of old vs new config hashes
10
+ - Private `fire_reload_callbacks` for executing registered change callbacks
11
+
12
+ ### Fixed
13
+ - `reload!` preserves programmatic module merges and reapplies `.legionio.env` overrides to the reloaded settings loader
14
+ - `watch!` no-ops when SIGHUP is unavailable and coalesces repeated SIGHUP events through a single reload worker
15
+ - Replaced deprecated helper logging method calls with direct `log.debug/info/warn/error` usage
16
+
17
+ ## [1.3.26] - 2026-04-02
18
+
19
+ ### Changed
20
+ - Added a runtime dependency on `legion-logging >= 1.4.0` and moved `Settings`, `Loader`, `Resolver`, `ProjectEnv`, and `AgentLoader` onto component-aware logging helpers
21
+ - Added `Legion::Logging::Helper` integration to top-level `Settings` access and DNS bootstrap logging so component tags and per-component levels apply consistently
22
+ - `extensions.default_extension_settings` now defaults to `{}` instead of injecting a synthetic logger config
23
+ - `Legion::Settings::Helper#settings` now returns `{}` when an extension has no explicit settings
24
+ - `region.default_affinity` now defaults to `any`
25
+ - `[]`, `dig`, and no-arg `load` now share the same project-env-aware load path, and the README now documents the actual `load`/`Loader.default_directories` split
26
+
27
+ ### Fixed
28
+ - `validate!` now clears stale validation errors before rebuilding state, and nested schema branches now fail when a hash-shaped setting is replaced with a scalar
29
+ - Loader mutation paths now invalidate indifferent-access and digest caches consistently, `load_module_default` no longer overwrites existing scalar settings, and resolver traversal now handles arrays of hashes
30
+ - `Resolver` now treats clustered Vault connectivity from `Legion::Crypt` as available for `vault://` resolution instead of relying only on the top-level `crypt.vault.connected` flag
31
+ - Logger-backed settings code paths now use consistent `log.debug/info/warn/error` behavior instead of mixed direct `Legion::Logging.*` calls
32
+
3
33
  ## [1.3.25] - 2026-03-31
4
34
 
5
35
  ### Added
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Configuration management module for the [LegionIO](https://github.com/LegionIO/LegionIO) framework. Loads settings from JSON files, directories, and environment variables. Provides a unified `Legion::Settings[:key]` accessor used by all other Legion gems.
4
4
 
5
- **Version**: 1.3.22
5
+ **Version**: 1.3.27
6
6
 
7
7
  ## Installation
8
8
 
@@ -21,20 +21,73 @@ gem 'legion-settings'
21
21
  ```ruby
22
22
  require 'legion/settings'
23
23
 
24
- Legion::Settings.load(config_dir: './') # loads all .json files in the directory
24
+ Legion::Settings.load # loads defaults, env, DNS bootstrap, and nearest .legionio.env
25
+ Legion::Settings.load(config_dir: './settings') # also loads all .json files in the directory
25
26
 
26
27
  Legion::Settings[:client][:hostname]
27
- Legion::Settings[:transport][:connection][:host]
28
+ Legion::Settings.dig(:transport, :connection, :host)
28
29
  ```
29
30
 
30
- ### Config Paths (checked in order)
31
+ `[]` and `dig` will auto-load settings on first access, and implicit access follows the same overlay/project-env/base precedence as explicit `load`.
31
32
 
32
- 1. `/etc/legionio/`
33
- 2. `~/.legionio/settings/`
34
- 3. `~/legionio/`
35
- 4. `./settings/`
33
+ ### Config Loading
36
34
 
37
- Each Legion module registers its own defaults via `merge_settings` during startup.
35
+ `Legion::Settings.load` only consumes the paths you pass via `config_file`, `config_dir`, or `config_dirs`.
36
+
37
+ If a caller wants the canonical Legion search directories, use `Legion::Settings::Loader.default_directories`:
38
+
39
+ 1. `~/.legionio/settings`
40
+ 2. `/etc/legionio/settings` on Unix-like systems
41
+ 3. `%APPDATA%\\legionio\\settings` on Windows when `APPDATA` is present
42
+
43
+ `LegionIO` uses those directories during daemon boot. Library consumers can choose to pass any directory set they want.
44
+
45
+ Each Legion module registers its own defaults via `merge_settings` during startup, and the nearest `.legionio.env` file is merged on top of base settings. Request overlays applied through `with_overlay` take highest precedence.
46
+
47
+ ### Hot Reload
48
+
49
+ `Legion::Settings.reload!` re-reads the config files that were previously loaded, reapplies module defaults and the nearest `.legionio.env`, re-resolves secret references, and returns a hash describing the changed keys.
50
+
51
+ ```ruby
52
+ changes = Legion::Settings.reload!
53
+
54
+ changes
55
+ # {
56
+ # "llm.default_model" => { old: "old-model", new: "new-model" }
57
+ # }
58
+ ```
59
+
60
+ Callbacks run only when changes are detected:
61
+
62
+ ```ruby
63
+ Legion::Settings.on_reload do |changes|
64
+ Legion::Settings.logger.info("Settings changed: #{changes.keys.join(', ')}")
65
+ end
66
+ ```
67
+
68
+ `watch!` installs a SIGHUP handler when the platform supports it. Repeated signals are coalesced through one background reload worker, so rapid SIGHUP bursts do not create unbounded reload threads.
69
+
70
+ ```ruby
71
+ Legion::Settings.watch! do |changes|
72
+ Legion::Settings.logger.info("Reloaded #{changes.size} setting(s)")
73
+ end
74
+
75
+ # Later, from a shell:
76
+ # kill -HUP <daemon_pid>
77
+ ```
78
+
79
+ On platforms without `HUP`, `watch!` logs and returns without raising. Direct `reload!` remains available for API endpoints, tests, or environments that use a different process-control mechanism.
80
+
81
+ ### Project Environment Overrides
82
+
83
+ When present, the nearest `.legionio.env` file is loaded after base settings and module defaults. Dot notation maps to nested settings:
84
+
85
+ ```dotenv
86
+ llm.default_model=claude-sonnet
87
+ cache.driver=redis
88
+ ```
89
+
90
+ Hot reload picks up changes to this file as part of the same `reload!` flow.
38
91
 
39
92
  ### Secret Resolution
40
93
 
@@ -76,25 +129,25 @@ Legion::Settings.validate! # raises ValidationError if any settings are invalid
76
129
 
77
130
  # In development, warn instead of raising:
78
131
  # Set LEGION_DEV=true or Legion::Settings.set_prop(:dev, true)
79
- # validate! will warn to $stderr (or Legion::Logging) instead of raising
132
+ # validate! will warn through the configured logger instead of raising
80
133
  ```
81
134
 
82
135
  ### Logging Defaults
83
136
 
84
- The `logging` key includes a `transport` sub-section (new in 1.3.22) that controls whether log events are forwarded over the message bus:
137
+ The `logging` key includes a `transport` sub-section that controls whether log events are forwarded over the message bus:
85
138
 
86
139
  ```json
87
140
  {
88
141
  "logging": {
89
142
  "level": "info",
90
143
  "format": "text",
91
- "log_file": null,
144
+ "log_file": "./legionio/logs/legion.log",
92
145
  "log_stdout": true,
93
146
  "trace": true,
94
147
  "async": true,
95
148
  "include_pid": false,
96
149
  "transport": {
97
- "enabled": false,
150
+ "enabled": true,
98
151
  "forward_logs": true,
99
152
  "forward_exceptions": true
100
153
  }
@@ -102,7 +155,7 @@ The `logging` key includes a `transport` sub-section (new in 1.3.22) that contro
102
155
  }
103
156
  ```
104
157
 
105
- When `transport.enabled` is `true`, log events and unhandled exceptions are published to the AMQP bus so a central log consumer can aggregate them. Disabled by default to avoid a dependency on `legion-transport` at boot.
158
+ When `transport.enabled` is `true`, log events and unhandled exceptions are published to the AMQP bus so a central log consumer can aggregate them.
106
159
 
107
160
  ## Requirements
108
161
 
@@ -27,4 +27,5 @@ Gem::Specification.new do |spec|
27
27
  }
28
28
 
29
29
  spec.add_dependency 'legion-json', '>= 1.2.0'
30
+ spec.add_dependency 'legion-logging', '>= 1.5.0'
30
31
  end
@@ -2,10 +2,13 @@
2
2
 
3
3
  require 'yaml'
4
4
  require 'json'
5
+ require 'legion/logging'
5
6
 
6
7
  module Legion
7
8
  module Settings
8
9
  module AgentLoader
10
+ extend Legion::Logging::Helper
11
+
9
12
  EXTENSIONS = %w[.yaml .yml .json].freeze
10
13
  GLOB = '*.{yaml,yml,json}'
11
14
 
@@ -17,7 +20,7 @@ module Legion
17
20
  definition = load_file(path)
18
21
  next unless definition && valid?(definition)
19
22
 
20
- log_debug("Agent loaded: #{definition[:name]} (#{path})")
23
+ log.debug("Agent loaded: #{definition[:name]} (#{path})")
21
24
  definition.merge(_source_path: path, _source_mtime: File.mtime(path))
22
25
  end
23
26
  end
@@ -29,7 +32,7 @@ module Legion
29
32
  when '.json' then ::JSON.parse(content, symbolize_names: true)
30
33
  end
31
34
  rescue StandardError => e
32
- log_warn("Failed to parse agent file #{path}: #{e.message}")
35
+ log.warn("Failed to parse agent file #{path}: #{e.message}")
33
36
  nil
34
37
  end
35
38
 
@@ -44,12 +47,9 @@ module Legion
44
47
 
45
48
  private
46
49
 
47
- def log_debug(message)
48
- Legion::Logging.debug(message) if defined?(Legion::Logging)
49
- end
50
-
51
- def log_warn(message)
52
- defined?(Legion::Logging) ? Legion::Logging.warn(message) : warn(message)
50
+ def resolve_logger_settings
51
+ raw_logging = Legion::Settings.loader&.settings&.dig(:logging) if Legion::Settings.respond_to?(:loader)
52
+ raw_logging.is_a?(Hash) ? raw_logging : Legion::Logging::Settings.default
53
53
  end
54
54
  end
55
55
  end
@@ -5,10 +5,13 @@ require 'json'
5
5
  require 'net/http'
6
6
  require 'resolv'
7
7
  require 'uri'
8
+ require 'legion/logging'
8
9
 
9
10
  module Legion
10
11
  module Settings
11
12
  class DnsBootstrap
13
+ include Legion::Logging::Helper
14
+
12
15
  CACHE_FILENAME = '_dns_bootstrap.json'
13
16
  HOSTNAME_PREFIX = 'legion-bootstrap'
14
17
  URL_PATH = '/legion/bootstrap.json'
@@ -28,7 +31,7 @@ module Legion
28
31
  Resolv.getaddress(@hostname)
29
32
  true
30
33
  rescue Resolv::ResolvError, Resolv::ResolvTimeout => e
31
- log_debug("Legion::Settings::DnsBootstrap#resolve? could not resolve #{@hostname}: #{e.message}")
34
+ log.debug("Legion::Settings::DnsBootstrap#resolve? could not resolve #{@hostname}: #{e.message}")
32
35
  false
33
36
  end
34
37
 
@@ -45,7 +48,7 @@ module Legion
45
48
 
46
49
  ::JSON.parse(response.body, symbolize_names: true)
47
50
  rescue StandardError => e
48
- log_warn("DNS bootstrap fetch failed for #{@url}: #{e.message}")
51
+ log.warn("DNS bootstrap fetch failed for #{@url}: #{e.message}")
49
52
  nil
50
53
  end
51
54
 
@@ -67,11 +70,11 @@ module Legion
67
70
  return nil unless File.exist?(@cache_path)
68
71
 
69
72
  raw = ::JSON.parse(File.read(@cache_path), symbolize_names: true)
70
- log_debug("DNS bootstrap cache hit: #{@cache_path}")
73
+ log.debug("DNS bootstrap cache hit: #{@cache_path}")
71
74
  raw.delete(:_dns_bootstrap_meta)
72
75
  raw
73
76
  rescue ::JSON::ParserError
74
- log_warn("DNS bootstrap cache corrupt, deleting: #{@cache_path}")
77
+ log.warn("DNS bootstrap cache corrupt, deleting: #{@cache_path}")
75
78
  FileUtils.rm_f(@cache_path)
76
79
  nil
77
80
  end
@@ -82,12 +85,9 @@ module Legion
82
85
 
83
86
  private
84
87
 
85
- def log_debug(message)
86
- Legion::Logging.debug(message) if defined?(Legion::Logging)
87
- end
88
-
89
- def log_warn(message)
90
- defined?(Legion::Logging) ? Legion::Logging.warn(message) : warn(message)
88
+ def resolve_logger_settings
89
+ raw_logging = Legion::Settings.loader&.settings&.dig(:logging) if Legion::Settings.respond_to?(:loader)
90
+ raw_logging.is_a?(Hash) ? raw_logging : Legion::Logging::Settings.default
91
91
  end
92
92
  end
93
93
  end
@@ -8,7 +8,7 @@ module Legion
8
8
  if Legion::Settings[:extensions]&.key?(ext_key)
9
9
  Legion::Settings[:extensions][ext_key]
10
10
  else
11
- { logger: { level: 'info', extended: false, internal: false } }
11
+ {}
12
12
  end
13
13
  end
14
14
 
@@ -2,6 +2,9 @@
2
2
 
3
3
  require 'resolv'
4
4
  require 'socket'
5
+ require 'digest'
6
+ require 'tmpdir'
7
+ require 'legion/logging'
5
8
  require 'legion/settings/os'
6
9
  require_relative 'dns_bootstrap'
7
10
 
@@ -9,9 +12,10 @@ module Legion
9
12
  module Settings
10
13
  class Loader
11
14
  include Legion::Settings::OS
15
+ include Legion::Logging::Helper
12
16
 
13
17
  class Error < RuntimeError; end
14
- attr_reader :warnings, :errors, :loaded_files, :settings
18
+ attr_reader :warnings, :errors, :loaded_files, :settings, :merged_modules
15
19
 
16
20
  def self.default_directories
17
21
  env_dirs = ENV.fetch('LEGION_SETTINGS_DIRS', nil)
@@ -36,6 +40,8 @@ module Legion
36
40
  @settings = default_settings
37
41
  @indifferent_access = false
38
42
  @loaded_files = []
43
+ @merged_modules = {}
44
+ log.debug('Initialized Legion::Settings::Loader with default settings')
39
45
  end
40
46
 
41
47
  def dns_defaults
@@ -138,16 +144,14 @@ module Legion
138
144
  reload: false,
139
145
  reloading: false,
140
146
  auto_install_missing_lex: true,
141
- default_extension_settings: {
142
- logger: { level: 'info', trace: false, extended: false }
143
- },
147
+ default_extension_settings: {},
144
148
  logging: logging_defaults,
145
149
  absorbers: absorbers_defaults,
146
150
  transport: { connected: false },
147
151
  data: { connected: false },
148
152
  role: { profile: nil, extensions: [] },
149
153
  region: { current: nil, primary: nil, failover: nil, peers: [],
150
- default_affinity: 'prefer_local', data_residency: {} },
154
+ default_affinity: 'any', data_residency: {} },
151
155
  process: { role: 'full' },
152
156
  dns: dns_defaults
153
157
  }
@@ -171,7 +175,7 @@ module Legion
171
175
 
172
176
  def []=(key, value)
173
177
  @settings[key] = value
174
- @indifferent_access = false
178
+ mark_dirty!
175
179
  end
176
180
 
177
181
  def hexdigest
@@ -218,46 +222,44 @@ module Legion
218
222
 
219
223
  def load_module_settings(config)
220
224
  mod_name = config.keys.first
221
- log_debug("Loading module settings: #{mod_name}")
225
+ log.debug("Loading module settings: #{mod_name}")
226
+ @merged_modules = deep_merge(@merged_modules, config)
222
227
  @settings = deep_merge(config, @settings)
223
- @indifferent_access = false
228
+ mark_dirty!
224
229
  end
225
230
 
226
231
  def load_module_default(config)
227
232
  mod_name = config.keys.first
228
- log_debug("Loading module defaults: #{mod_name}")
229
- merged = deep_merge(@settings, config)
230
- deep_diff(@settings, merged) unless @loaded_files.empty?
231
- @settings = merged
232
- @indifferent_access = false
233
+ log.debug("Loading module defaults: #{mod_name}")
234
+ @settings = deep_merge(config, @settings)
235
+ mark_dirty!
233
236
  end
234
237
 
235
238
  def load_file(file)
236
- log_debug("Trying to load file #{file}")
239
+ log.debug("Trying to load file #{file}")
237
240
  if File.file?(file) && File.readable?(file)
238
241
  begin
239
242
  contents = read_config_file(file)
240
243
  config = contents.empty? ? {} : Legion::JSON.load(contents)
241
- merged = deep_merge(@settings, config)
242
- deep_diff(@settings, merged) unless @loaded_files.empty?
243
- @settings = merged
244
- # @indifferent_access = false
244
+ @settings = deep_merge(@settings, config)
245
+ mark_dirty!
245
246
  @loaded_files << file
247
+ log.debug("Loaded settings file #{file}")
246
248
  rescue Legion::JSON::ParseError => e
247
- log_error("config file must be valid json: #{file}")
248
- log_error(" parse error: #{e.message}")
249
+ log.error("config file must be valid json: #{file}")
250
+ log.error(" parse error: #{e.message}")
249
251
  end
250
252
  else
251
- log_warn("Config file does not exist or is not readable file:#{file}")
253
+ log.warn("Config file does not exist or is not readable file:#{file}")
252
254
  end
253
255
  end
254
256
 
255
257
  def load_directory(directory)
256
258
  path = directory.gsub(/\\(?=\S)/, '/')
257
259
  if File.readable?(path) && File.executable?(path)
258
- files = Dir.glob(File.join(path, '**{,/*/**}/*.json')).uniq
260
+ files = Dir.glob(File.join(path, '**', '*.json'))
259
261
  files.each { |file| load_file(file) }
260
- log_info("Settings: loaded directory #{path} (#{files.size} files)")
262
+ log.info("Settings: loaded directory #{path} (#{files.size} files)")
261
263
  else
262
264
  load_error('insufficient permissions for loading', directory: directory)
263
265
  end
@@ -268,9 +270,9 @@ module Legion
268
270
  if @settings[:client][:subscriptions].is_a?(Array)
269
271
  @settings[:client][:subscriptions] << "client:#{@settings[:client][:name]}"
270
272
  @settings[:client][:subscriptions].uniq!
271
- @indifferent_access = false
273
+ mark_dirty!
272
274
  else
273
- log_warn('unable to apply legion client overrides, reason: client subscriptions is not an array')
275
+ log.warn('unable to apply legion client overrides, reason: client subscriptions is not an array')
274
276
  end
275
277
  end
276
278
 
@@ -290,6 +292,11 @@ module Legion
290
292
 
291
293
  private
292
294
 
295
+ def resolve_logger_settings
296
+ raw_logging = instance_variable_defined?(:@settings) ? @settings&.[](:logging) : nil
297
+ raw_logging.is_a?(Hash) ? raw_logging : Legion::Logging::Settings.default
298
+ end
299
+
293
300
  def load_dns_from_cache(bootstrap)
294
301
  config = bootstrap.read_cache
295
302
  start_dns_background_refresh(bootstrap) if config
@@ -297,7 +304,7 @@ module Legion
297
304
  end
298
305
 
299
306
  def load_dns_first_boot(bootstrap)
300
- log_debug("DNS bootstrap: first boot, fetching from #{bootstrap.url}")
307
+ log.debug("DNS bootstrap: first boot, fetching from #{bootstrap.url}")
301
308
  config = bootstrap.fetch
302
309
  bootstrap.write_cache(config) if config
303
310
  config
@@ -311,7 +318,7 @@ module Legion
311
318
  hostname: bootstrap.hostname,
312
319
  url: bootstrap.url
313
320
  }
314
- @indifferent_access = false
321
+ mark_dirty!
315
322
  end
316
323
 
317
324
  def start_dns_background_refresh(bootstrap)
@@ -319,7 +326,7 @@ module Legion
319
326
  fresh = bootstrap.fetch
320
327
  bootstrap.write_cache(fresh) if fresh
321
328
  rescue StandardError => e
322
- log_warn("DNS background refresh failed: #{e.message}")
329
+ log.warn("DNS background refresh failed: #{e.message}")
323
330
  end
324
331
  end
325
332
 
@@ -356,15 +363,15 @@ module Legion
356
363
 
357
364
  @settings[:api] ||= {}
358
365
  @settings[:api][:port] = ENV['LEGION_API_PORT'].to_i
359
- log_warn("using api port environment variable, api: #{@settings[:api]}")
360
- @indifferent_access = false
366
+ log.warn("using api port environment variable, api: #{@settings[:api]}")
367
+ mark_dirty!
361
368
  end
362
369
 
363
370
  def load_privacy_env
364
371
  return unless ENV['LEGION_ENTERPRISE_PRIVACY'] == 'true'
365
372
 
366
373
  @settings[:enterprise_data_privacy] = true
367
- @indifferent_access = false
374
+ mark_dirty!
368
375
  end
369
376
 
370
377
  def read_config_file(file)
@@ -394,19 +401,6 @@ module Legion
394
401
  merged
395
402
  end
396
403
 
397
- def deep_diff(hash_one, hash_two)
398
- keys = hash_one.keys.concat(hash_two.keys).uniq
399
- keys.each_with_object({}) do |key, diff|
400
- next if hash_one[key] == hash_two[key]
401
-
402
- diff[key] = if hash_one[key].is_a?(Hash) && hash_two[key].is_a?(Hash)
403
- deep_diff(hash_one[key], hash_two[key])
404
- else
405
- [hash_one[key], hash_two[key]]
406
- end
407
- end
408
- end
409
-
410
404
  def create_loaded_tempfile!
411
405
  dir = ENV['LEGION_LOADED_TEMPFILE_DIR'] || Dir.tmpdir
412
406
  file_name = "legion_#{legion_service_name}_loaded_files"
@@ -415,6 +409,15 @@ module Legion
415
409
  path
416
410
  end
417
411
 
412
+ public
413
+
414
+ def mark_dirty!
415
+ @indifferent_access = false
416
+ @hexdigest = nil
417
+ end
418
+
419
+ private
420
+
418
421
  def legion_service_name
419
422
  File.basename($PROGRAM_NAME).split('-').last
420
423
  end
@@ -422,7 +425,7 @@ module Legion
422
425
  def system_hostname
423
426
  Socket.gethostname
424
427
  rescue StandardError => e
425
- Legion::Logging.debug("Legion::Settings::Loader#system_hostname failed: #{e.message}") if defined?(Legion::Logging)
428
+ log.debug("Legion::Settings::Loader#system_hostname failed: #{e.message}")
426
429
  'unknown'
427
430
  end
428
431
 
@@ -431,7 +434,7 @@ module Legion
431
434
  preferred = addresses.find { |a| rfc1918?(a.ip_address) }
432
435
  (preferred || addresses.first)&.ip_address || 'unknown'
433
436
  rescue StandardError => e
434
- Legion::Logging.debug("Legion::Settings::Loader#system_address failed: #{e.message}") if defined?(Legion::Logging)
437
+ log.debug("Legion::Settings::Loader#system_address failed: #{e.message}")
435
438
  'unknown'
436
439
  end
437
440
 
@@ -441,34 +444,18 @@ module Legion
441
444
  ip.start_with?('192.168.')
442
445
  end
443
446
 
444
- def log_info(message)
445
- defined?(Legion::Logging) ? Legion::Logging.info(message) : $stdout.puts(message)
446
- end
447
-
448
- def log_debug(message)
449
- Legion::Logging.debug(message) if defined?(Legion::Logging)
450
- end
451
-
452
- def log_warn(message)
453
- defined?(Legion::Logging) ? Legion::Logging.warn(message) : warn(message)
454
- end
455
-
456
- def log_error(message)
457
- defined?(Legion::Logging) ? Legion::Logging.error(message) : warn(message)
458
- end
459
-
460
447
  def warning(message, data = {})
461
448
  @warnings << {
462
449
  message: message
463
450
  }.merge(data)
464
- log_warn(message)
451
+ log.warn(message)
465
452
  end
466
453
 
467
454
  def load_error(message, data = {})
468
455
  @errors << {
469
456
  message: message
470
457
  }.merge(data)
471
- log_error(message)
458
+ log.error(message)
472
459
  raise(Error, message)
473
460
  end
474
461
 
@@ -479,7 +466,7 @@ module Legion
479
466
  nameservers: config[:nameserver]&.map(&:to_s)&.uniq
480
467
  }
481
468
  rescue StandardError => e
482
- log_warn("Failed to read resolv config: #{e.message}")
469
+ log.warn("Failed to read resolv config: #{e.message}")
483
470
  { search_domains: [], nameservers: [] }
484
471
  end
485
472
 
@@ -490,10 +477,10 @@ module Legion
490
477
 
491
478
  fqdn.include?('.') ? fqdn : nil
492
479
  rescue Timeout::Error
493
- log_debug('FQDN detection skipped (DNS timeout)')
480
+ log.debug('FQDN detection skipped (DNS timeout)')
494
481
  nil
495
482
  rescue StandardError => e
496
- log_debug("FQDN detection skipped (#{e.message.split(':').first})")
483
+ log.debug("FQDN detection skipped (#{e.message.split(':').first})")
497
484
  nil
498
485
  end
499
486
  end