legion-settings 1.3.4 → 1.3.5
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 +4 -4
- data/CHANGELOG.md +9 -0
- data/CLAUDE.md +1 -0
- data/README.md +2 -0
- data/lib/legion/settings/dns_bootstrap.rb +80 -0
- data/lib/legion/settings/loader.rb +88 -1
- data/lib/legion/settings/version.rb +1 -1
- data/lib/legion/settings.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 220738db670fe9bbda99acc5d64d903de88268afdc83bb0ef6c482f550b7b769
|
|
4
|
+
data.tar.gz: b46f3ff6ed58a9a7a0269230814ea2a9204953970c93877747c5d28c23410c8b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2e8a71bf3705c3f2a809b454c1bc676f7ab1f7c2e3c2dd1b3d6b317f5126ba84bde201b75a1f65e5111d31f1b67408e173eef8da3683d4d19ee48af83c69bdc0
|
|
7
|
+
data.tar.gz: a537874627f4d55ee13bb3ec4cbce0e869337a15c0f6c1bfd012a16f081c79f8bb7a56f39d7431c7eb6c1e6e525cda8a98155f534bfac32177a135b1aa96e7cd
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Legion::Settings Changelog
|
|
2
2
|
|
|
3
|
+
## [1.3.5] - 2026-03-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- DNS-based bootstrap discovery: auto-detect corporate config from `legion-bootstrap.<search-domain>`
|
|
7
|
+
- `Settings[:dns]` populated with FQDN, default domain, search domains, and nameservers
|
|
8
|
+
- `DnsBootstrap` class with DNS resolution, HTTPS fetch, local caching, and background refresh
|
|
9
|
+
- First boot blocks on fetch; subsequent boots use cache with async refresh
|
|
10
|
+
- Opt-out via `LEGION_DNS_BOOTSTRAP=false` environment variable
|
|
11
|
+
|
|
3
12
|
## [1.3.4] - 2026-03-18
|
|
4
13
|
|
|
5
14
|
### Fixed
|
data/CLAUDE.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
Hash-like configuration store for the LegionIO framework. Loads settings from JSON files, directories, and environment variables. Provides a unified `Legion::Settings[:key]` accessor used by all other Legion gems. Includes schema-based validation with type inference, enum constraints, and cross-module checks.
|
|
9
9
|
|
|
10
10
|
**GitHub**: https://github.com/LegionIO/legion-settings
|
|
11
|
+
**Version**: 1.3.4
|
|
11
12
|
**License**: Apache-2.0
|
|
12
13
|
|
|
13
14
|
## Architecture
|
data/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
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.4
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'net/http'
|
|
6
|
+
require 'resolv'
|
|
7
|
+
require 'uri'
|
|
8
|
+
|
|
9
|
+
module Legion
|
|
10
|
+
module Settings
|
|
11
|
+
class DnsBootstrap
|
|
12
|
+
CACHE_FILENAME = '_dns_bootstrap.json'
|
|
13
|
+
HOSTNAME_PREFIX = 'legion-bootstrap'
|
|
14
|
+
URL_PATH = '/legion/bootstrap.json'
|
|
15
|
+
HTTP_TIMEOUT = 10
|
|
16
|
+
|
|
17
|
+
attr_reader :default_domain, :hostname, :url, :cache_path
|
|
18
|
+
|
|
19
|
+
def initialize(default_domain:, cache_dir: nil)
|
|
20
|
+
@default_domain = default_domain
|
|
21
|
+
@hostname = "#{HOSTNAME_PREFIX}.#{default_domain}"
|
|
22
|
+
@url = "https://#{@hostname}#{URL_PATH}"
|
|
23
|
+
dir = cache_dir || File.expand_path('~/.legionio/settings')
|
|
24
|
+
@cache_path = File.join(dir, CACHE_FILENAME)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def resolve?
|
|
28
|
+
Resolv.getaddress(@hostname)
|
|
29
|
+
true
|
|
30
|
+
rescue Resolv::ResolvError, Resolv::ResolvTimeout
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def fetch
|
|
35
|
+
return nil unless resolve?
|
|
36
|
+
|
|
37
|
+
uri = URI.parse(@url)
|
|
38
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
39
|
+
http.use_ssl = true
|
|
40
|
+
http.open_timeout = HTTP_TIMEOUT
|
|
41
|
+
http.read_timeout = HTTP_TIMEOUT
|
|
42
|
+
response = http.request(Net::HTTP::Get.new(uri))
|
|
43
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
44
|
+
|
|
45
|
+
::JSON.parse(response.body, symbolize_names: true)
|
|
46
|
+
rescue StandardError
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def write_cache(config)
|
|
51
|
+
FileUtils.mkdir_p(File.dirname(@cache_path))
|
|
52
|
+
payload = config.merge(
|
|
53
|
+
_dns_bootstrap_meta: {
|
|
54
|
+
fetched_at: Time.now.utc.iso8601,
|
|
55
|
+
hostname: @hostname,
|
|
56
|
+
url: @url
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
tmp = "#{@cache_path}.tmp"
|
|
60
|
+
File.write(tmp, ::JSON.pretty_generate(payload))
|
|
61
|
+
File.rename(tmp, @cache_path)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def read_cache
|
|
65
|
+
return nil unless File.exist?(@cache_path)
|
|
66
|
+
|
|
67
|
+
raw = ::JSON.parse(File.read(@cache_path), symbolize_names: true)
|
|
68
|
+
raw.delete(:_dns_bootstrap_meta)
|
|
69
|
+
raw
|
|
70
|
+
rescue ::JSON::ParserError
|
|
71
|
+
FileUtils.rm_f(@cache_path)
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def cache_exists?
|
|
76
|
+
File.exist?(@cache_path)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'resolv'
|
|
3
4
|
require 'socket'
|
|
4
5
|
require 'legion/settings/os'
|
|
6
|
+
require_relative 'dns_bootstrap'
|
|
5
7
|
|
|
6
8
|
module Legion
|
|
7
9
|
module Settings
|
|
@@ -19,6 +21,17 @@ module Legion
|
|
|
19
21
|
@loaded_files = []
|
|
20
22
|
end
|
|
21
23
|
|
|
24
|
+
def dns_defaults
|
|
25
|
+
resolv_config = read_resolv_config
|
|
26
|
+
{
|
|
27
|
+
fqdn: detect_fqdn,
|
|
28
|
+
default_domain: resolv_config[:search_domains]&.first,
|
|
29
|
+
search_domains: resolv_config[:search_domains] || [],
|
|
30
|
+
nameservers: resolv_config[:nameservers] || [],
|
|
31
|
+
bootstrap: { enabled: true }
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
22
35
|
def client_defaults
|
|
23
36
|
{
|
|
24
37
|
hostname: system_hostname,
|
|
@@ -71,7 +84,8 @@ module Legion
|
|
|
71
84
|
},
|
|
72
85
|
transport: { connected: false },
|
|
73
86
|
data: { connected: false },
|
|
74
|
-
role: { profile: nil, extensions: [] }
|
|
87
|
+
role: { profile: nil, extensions: [] },
|
|
88
|
+
dns: dns_defaults
|
|
75
89
|
}
|
|
76
90
|
end
|
|
77
91
|
|
|
@@ -116,6 +130,27 @@ module Legion
|
|
|
116
130
|
load_api_env
|
|
117
131
|
end
|
|
118
132
|
|
|
133
|
+
def load_dns_bootstrap(cache_dir: nil)
|
|
134
|
+
return if ENV['LEGION_DNS_BOOTSTRAP'] == 'false'
|
|
135
|
+
|
|
136
|
+
domain = @settings.dig(:dns, :default_domain)
|
|
137
|
+
return unless domain
|
|
138
|
+
return unless @settings.dig(:dns, :bootstrap, :enabled)
|
|
139
|
+
|
|
140
|
+
dir = cache_dir || File.expand_path('~/.legionio/settings')
|
|
141
|
+
bootstrap = DnsBootstrap.new(default_domain: domain, cache_dir: dir)
|
|
142
|
+
|
|
143
|
+
config = if bootstrap.cache_exists?
|
|
144
|
+
load_dns_from_cache(bootstrap)
|
|
145
|
+
else
|
|
146
|
+
load_dns_first_boot(bootstrap)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
return unless config
|
|
150
|
+
|
|
151
|
+
merge_dns_config(config, bootstrap)
|
|
152
|
+
end
|
|
153
|
+
|
|
119
154
|
def load_module_settings(config)
|
|
120
155
|
@settings = deep_merge(config, @settings)
|
|
121
156
|
end
|
|
@@ -184,6 +219,39 @@ module Legion
|
|
|
184
219
|
|
|
185
220
|
private
|
|
186
221
|
|
|
222
|
+
def load_dns_from_cache(bootstrap)
|
|
223
|
+
config = bootstrap.read_cache
|
|
224
|
+
start_dns_background_refresh(bootstrap) if config
|
|
225
|
+
config
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def load_dns_first_boot(bootstrap)
|
|
229
|
+
Legion::Logging.debug("DNS bootstrap: first boot, fetching from #{bootstrap.url}")
|
|
230
|
+
config = bootstrap.fetch
|
|
231
|
+
bootstrap.write_cache(config) if config
|
|
232
|
+
config
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def merge_dns_config(config, bootstrap)
|
|
236
|
+
@settings = deep_merge(config, @settings)
|
|
237
|
+
@settings[:dns] ||= {}
|
|
238
|
+
@settings[:dns][:corp_bootstrap] = {
|
|
239
|
+
discovered: true,
|
|
240
|
+
hostname: bootstrap.hostname,
|
|
241
|
+
url: bootstrap.url
|
|
242
|
+
}
|
|
243
|
+
@indifferent_access = false
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def start_dns_background_refresh(bootstrap)
|
|
247
|
+
Thread.new do
|
|
248
|
+
fresh = bootstrap.fetch
|
|
249
|
+
bootstrap.write_cache(fresh) if fresh
|
|
250
|
+
rescue StandardError
|
|
251
|
+
# Background refresh is best-effort
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
187
255
|
def setting_category(category)
|
|
188
256
|
@settings[category].map do |name, details|
|
|
189
257
|
details.merge(name: name.to_s)
|
|
@@ -301,6 +369,25 @@ module Legion
|
|
|
301
369
|
Legion::Logging.error(message)
|
|
302
370
|
raise(Error, message)
|
|
303
371
|
end
|
|
372
|
+
|
|
373
|
+
def read_resolv_config
|
|
374
|
+
config = Resolv::DNS::Config.default_config_hash
|
|
375
|
+
{
|
|
376
|
+
search_domains: config[:search]&.map(&:to_s)&.uniq,
|
|
377
|
+
nameservers: config[:nameserver]&.map(&:to_s)&.uniq
|
|
378
|
+
}
|
|
379
|
+
rescue StandardError
|
|
380
|
+
{ search_domains: [], nameservers: [] }
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def detect_fqdn
|
|
384
|
+
fqdn = Addrinfo.getaddrinfo(Socket.gethostname, nil).first&.canonname
|
|
385
|
+
return nil if fqdn.nil?
|
|
386
|
+
|
|
387
|
+
fqdn.include?('.') ? fqdn : nil
|
|
388
|
+
rescue StandardError
|
|
389
|
+
nil
|
|
390
|
+
end
|
|
304
391
|
end
|
|
305
392
|
end
|
|
306
393
|
end
|
data/lib/legion/settings.rb
CHANGED
|
@@ -17,6 +17,7 @@ module Legion
|
|
|
17
17
|
def load(options = {})
|
|
18
18
|
@loader = Legion::Settings::Loader.new
|
|
19
19
|
@loader.load_env
|
|
20
|
+
@loader.load_dns_bootstrap
|
|
20
21
|
@loader.load_file(options[:config_file]) if options[:config_file]
|
|
21
22
|
@loader.load_directory(options[:config_dir]) if options[:config_dir]
|
|
22
23
|
options[:config_dirs]&.each do |directory|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: legion-settings
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -46,6 +46,7 @@ files:
|
|
|
46
46
|
- docs/plans/2026-03-17-config-error-filename-implementation.md
|
|
47
47
|
- legion-settings.gemspec
|
|
48
48
|
- lib/legion/settings.rb
|
|
49
|
+
- lib/legion/settings/dns_bootstrap.rb
|
|
49
50
|
- lib/legion/settings/loader.rb
|
|
50
51
|
- lib/legion/settings/os.rb
|
|
51
52
|
- lib/legion/settings/resolver.rb
|