ultra_settings 2.8.0 → 2.8.1
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/AGENTS.md +191 -0
- data/CHANGELOG.md +8 -0
- data/MIT-LICENSE.txt +1 -1
- data/README.md +68 -0
- data/VERSION +1 -1
- data/app/_data_source.html.erb +39 -0
- data/app/application.css +150 -10
- data/app/application_vars.css.erb +10 -0
- data/app/configuration.html.erb +12 -67
- data/lib/ultra_settings/audit_data_sources.rb +98 -0
- data/lib/ultra_settings/configuration.rb +23 -3
- data/lib/ultra_settings/configuration_view.rb +39 -0
- data/lib/ultra_settings/railtie.rb +8 -0
- data/lib/ultra_settings/tasks/audit_data_sources.rake +76 -0
- data/lib/ultra_settings/tasks/utils.rb +23 -0
- data/ultra_settings.gemspec +2 -0
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d6db7d9c5a607d4c0318190ba8357f0577f9d542d08fbc77e6253e3776372476
|
|
4
|
+
data.tar.gz: 52171f7843724be2e867f2cf3547b934a530249173f8f2c583548f442f9ceb22
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 660219db715918d464d403064ac96920dde3acf7be57e85b3de921ecc7fe4b3ef641f9ef8b386ea931a603ad7cfab5f4f9ddd9cbc4f151226e4d3359d5a25dce
|
|
7
|
+
data.tar.gz: b4b40f05c524a3bd75fbb237c631e4643f229c50134fadf819acd1dded92855ebb3070eeaa8ee508a36bdbb13ec759a3fd6eb9c140db90d092ebd649a88bb8cf
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# UltraSettings AI Coding Agent Instructions
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
UltraSettings is a Ruby gem for managing application configuration from multiple sources (environment variables, runtime settings, YAML files) with built-in type safety, validation, and a web UI for documentation.
|
|
5
|
+
|
|
6
|
+
## Core Architecture
|
|
7
|
+
|
|
8
|
+
### Configuration Classes (Singleton Pattern)
|
|
9
|
+
- All configuration classes extend `UltraSettings::Configuration` and use Ruby's `Singleton` module
|
|
10
|
+
- Each configuration class has one instance accessible via `.instance`
|
|
11
|
+
- Register configurations globally: `UltraSettings.add(:test)` creates `UltraSettings.test` accessor
|
|
12
|
+
- Configuration classes support inheritance; subclasses maintain separate singleton instances
|
|
13
|
+
|
|
14
|
+
### Field Definition DSL
|
|
15
|
+
Fields are defined using the `field` class method with extensive options:
|
|
16
|
+
```ruby
|
|
17
|
+
field :timeout, type: :float, default: 1.0,
|
|
18
|
+
default_if: ->(val) { val <= 0 },
|
|
19
|
+
description: "Network timeout in seconds",
|
|
20
|
+
secret: false
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Multi-Source Value Resolution (Precedence Order)
|
|
24
|
+
1. **Environment Variables** - Highest priority (e.g., `MY_SERVICE_TIMEOUT`)
|
|
25
|
+
2. **Runtime Settings** - Dynamic configuration from external stores (e.g., Redis, `super_settings` gem)
|
|
26
|
+
3. **YAML Files** - File-based defaults with ERB support
|
|
27
|
+
4. **Default Values** - Fallback specified in field definition
|
|
28
|
+
|
|
29
|
+
### Type System
|
|
30
|
+
Supported types: `:string` (default), `:symbol`, `:integer`, `:float`, `:boolean`, `:datetime`, `:array`
|
|
31
|
+
- Boolean fields auto-generate `?` predicate methods (e.g., `enabled?`)
|
|
32
|
+
- Array type parses CSV strings from env vars, supports proper arrays from YAML
|
|
33
|
+
- Empty strings coerce to `nil` across all sources
|
|
34
|
+
|
|
35
|
+
## Key Conventions
|
|
36
|
+
|
|
37
|
+
### Naming Patterns
|
|
38
|
+
- **Configuration class names**: Must end in `Configuration` (e.g., `MyServiceConfiguration`)
|
|
39
|
+
- **Field names**: Match `/\A[a-z_][a-zA-Z0-9_]*\z/` pattern (snake_case)
|
|
40
|
+
- **Environment variable defaults**: `MY_SERVICE_FOO` for `MyServiceConfiguration#foo`
|
|
41
|
+
- **Runtime setting defaults**: `my_service.foo` (lowercase with dots)
|
|
42
|
+
- **YAML keys**: Match field name by default
|
|
43
|
+
|
|
44
|
+
### Customization Attributes
|
|
45
|
+
Set on configuration classes to override defaults:
|
|
46
|
+
- `env_var_prefix`, `env_var_delimiter`, `env_var_upcase` - Control environment variable naming
|
|
47
|
+
- `runtime_setting_prefix`, `runtime_setting_delimiter`, `runtime_setting_upcase` - Control runtime setting naming
|
|
48
|
+
- `configuration_file` - Specify explicit YAML file path
|
|
49
|
+
- `fields_secret_by_default` - Default secret status (true by default for security)
|
|
50
|
+
- Disable sources entirely: `environment_variables_disabled`, `runtime_settings_disabled`, `yaml_config_disabled`
|
|
51
|
+
|
|
52
|
+
### Thread Safety
|
|
53
|
+
- All memoized values protected by `Mutex`
|
|
54
|
+
- Override values are thread-local (keyed by `Thread.current.object_id`)
|
|
55
|
+
- Static fields are cached permanently after first access
|
|
56
|
+
|
|
57
|
+
## Testing Patterns
|
|
58
|
+
|
|
59
|
+
### Test Setup (spec/spec_helper.rb)
|
|
60
|
+
```ruby
|
|
61
|
+
# Configure YAML path and environment
|
|
62
|
+
UltraSettings.yaml_config_path = Pathname.new(__dir__) + "config"
|
|
63
|
+
UltraSettings.yaml_config_env = "test"
|
|
64
|
+
|
|
65
|
+
# Register configurations
|
|
66
|
+
UltraSettings.add(:test)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Overriding Configuration Values
|
|
70
|
+
Use `override!` method for temporary value changes in tests:
|
|
71
|
+
```ruby
|
|
72
|
+
# Via UltraSettings module
|
|
73
|
+
UltraSettings.override!(test: {foo: "bar"}) { ... }
|
|
74
|
+
|
|
75
|
+
# Via configuration class
|
|
76
|
+
TestConfiguration.override!(foo: "bar") { ... }
|
|
77
|
+
|
|
78
|
+
# Via configuration instance
|
|
79
|
+
TestConfiguration.instance.override!(foo: "bar") { ... }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### RSpec Integration Example
|
|
83
|
+
```ruby
|
|
84
|
+
RSpec.configure do |config|
|
|
85
|
+
config.around do |example|
|
|
86
|
+
if example.metadata[:ultra_settings]
|
|
87
|
+
UltraSettings.override!(example.metadata[:ultra_settings]) do
|
|
88
|
+
example.run
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Climate Control for Environment Variables
|
|
96
|
+
Uses `climate_control` gem to safely modify environment variables in tests:
|
|
97
|
+
```ruby
|
|
98
|
+
RSpec.describe "config", env: {TIMEOUT: "5"} do
|
|
99
|
+
# Test with environment variable set
|
|
100
|
+
end
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Web UI Features
|
|
104
|
+
|
|
105
|
+
### Mounting the Rack App
|
|
106
|
+
```ruby
|
|
107
|
+
# config.ru or Rails routes
|
|
108
|
+
mount UltraSettings::RackApp.new(color_scheme: :system), at: "/settings"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Key Capabilities
|
|
112
|
+
- Displays all registered configurations with descriptions
|
|
113
|
+
- Shows field metadata: type, description, sources, but **NOT actual values**
|
|
114
|
+
- Respects secret field marking (values hidden)
|
|
115
|
+
- Includes links to edit runtime settings via `UltraSettings.runtime_settings_url`
|
|
116
|
+
- Views in `lib/ultra_settings/*_view.rb` use ERB templates from `app/` directory
|
|
117
|
+
|
|
118
|
+
## Common Development Workflows
|
|
119
|
+
|
|
120
|
+
### Running Tests
|
|
121
|
+
```bash
|
|
122
|
+
bundle exec rake spec # Default task runs all specs
|
|
123
|
+
bundle exec rspec spec/ultra_settings/configuration_spec.rb # Specific file
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Checking Code Style
|
|
127
|
+
Uses Standard Ruby style guide (testdouble/standard):
|
|
128
|
+
```bash
|
|
129
|
+
bundle exec standardrb --fix
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Release Process
|
|
133
|
+
- Can only release from `main` branch (enforced in Rakefile)
|
|
134
|
+
- Version stored in `VERSION` file (no hardcoded version in gemspec)
|
|
135
|
+
|
|
136
|
+
## Special Features
|
|
137
|
+
|
|
138
|
+
### Static Fields
|
|
139
|
+
- Marked with `static: true`, values never change after first access
|
|
140
|
+
- Cannot be set from runtime settings (initialization-time only)
|
|
141
|
+
- Use for settings referenced during app boot
|
|
142
|
+
|
|
143
|
+
### Secret Fields
|
|
144
|
+
- All fields secret by default unless `fields_secret_by_default = false`
|
|
145
|
+
- Masked in `__to_hash__` output
|
|
146
|
+
- Runtime settings disabled on secret fields unless `UltraSettings.runtime_settings_secure = true`
|
|
147
|
+
- Secret status can be dynamic via Proc
|
|
148
|
+
|
|
149
|
+
### Conditional Defaults
|
|
150
|
+
Use `default_if` with Proc or method name to apply defaults based on loaded value:
|
|
151
|
+
```ruby
|
|
152
|
+
field :timeout, default: 1.0, default_if: ->(val) { val <= 0 }
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Introspection Methods
|
|
156
|
+
- `__source__(name)` - Returns which source provided the value (`:env`, `:runtime`, `:yaml`, `:default`)
|
|
157
|
+
- `__value_from_source__(name, source)` - Fetch value from specific source
|
|
158
|
+
- `__to_hash__` - Serialize current configuration as hash (secrets masked)
|
|
159
|
+
|
|
160
|
+
## Important Implementation Details
|
|
161
|
+
|
|
162
|
+
### Dynamic Method Generation
|
|
163
|
+
Fields create getter methods via `class_eval` for performance (avoids `method_missing`)
|
|
164
|
+
|
|
165
|
+
### YAML Configuration
|
|
166
|
+
- Supports environment-specific sections (e.g., `development`, `test`, `production`)
|
|
167
|
+
- Special `shared` section merged with environment-specific config
|
|
168
|
+
- ERB templates evaluated before YAML parsing
|
|
169
|
+
- Files searched in `UltraSettings.yaml_config_path`
|
|
170
|
+
|
|
171
|
+
### Runtime Settings Integration
|
|
172
|
+
- Must implement `[]` method accepting string argument
|
|
173
|
+
- Optional `array` method for native array support
|
|
174
|
+
- Use `UninitializedRuntimeSettings` during boot to catch premature access
|
|
175
|
+
- `super_settings` gem is recommended companion implementation
|
|
176
|
+
|
|
177
|
+
## File Organization
|
|
178
|
+
|
|
179
|
+
- `lib/ultra_settings/configuration.rb` - Core configuration class (630 lines)
|
|
180
|
+
- `lib/ultra_settings/field.rb` - Field metadata and resolution logic
|
|
181
|
+
- `lib/ultra_settings/coerce.rb` - Type coercion utilities
|
|
182
|
+
- `lib/ultra_settings/*.rb` - Supporting classes for web UI, YAML, helpers
|
|
183
|
+
- `spec/test_configs/` - Example configuration classes for testing
|
|
184
|
+
- `spec/config/` - YAML configuration files for tests
|
|
185
|
+
- `app/` - ERB templates and assets for web UI
|
|
186
|
+
|
|
187
|
+
## Key Dependencies
|
|
188
|
+
- Ruby >= 2.5
|
|
189
|
+
- Development: `rspec`, `climate_control`, `nokogiri`, `bundler`
|
|
190
|
+
- Optional: `super_settings` gem for runtime settings store
|
|
191
|
+
- Rails integration via `railtie.rb` when Rails is detected
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 2.8.1
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Improved web UI indication for which data sources on fields have been overridden by data sources with higher precedence.
|
|
12
|
+
- Added rake tasks for auditing configuration data sources.
|
|
13
|
+
- A companion gem `yard-ultra_settings` is now available to provide YARD integration for documenting UltraSettings configuration classes. See the [yard-ultra_settings](https://github.com/bdurand/ultra_settings/yard_plugin) gem for more information.
|
|
14
|
+
|
|
7
15
|
## 2.8.0
|
|
8
16
|
|
|
9
17
|
### Added
|
data/MIT-LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -520,6 +520,74 @@ class MyServiceConfiguration < UltraSettings::Configuration
|
|
|
520
520
|
end
|
|
521
521
|
```
|
|
522
522
|
|
|
523
|
+
### Rails Tasks
|
|
524
|
+
|
|
525
|
+
You can audit your configuration data sources with rake tasks to identify potential optimizations with these rake tasks:
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
# Output showing environment variables that are set, but which do not need to be set
|
|
529
|
+
# since they match the default values.
|
|
530
|
+
bundle exec rails ultra_settings:unnecessary_env_vars
|
|
531
|
+
|
|
532
|
+
# Output showing runtime settings that are set, but which do not need to be set
|
|
533
|
+
# since they match the default values.
|
|
534
|
+
bundle exec rails ultra_settings:unnecessary_runtime_settings
|
|
535
|
+
|
|
536
|
+
# Output showing environment variables that are set but which could be loaded from
|
|
537
|
+
# runtime settings instead.
|
|
538
|
+
bundle exec rails ultra_settings:env_vars_can_be_runtime_setting
|
|
539
|
+
|
|
540
|
+
# Output showing environment variables that are set that could be candidates for
|
|
541
|
+
# adding default values to the configuration fields.
|
|
542
|
+
bundle exec rails ultra_settings:env_vars_without_default
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
## Generating Documentation
|
|
546
|
+
|
|
547
|
+
### YARD Plugin
|
|
548
|
+
|
|
549
|
+
For projects using [YARD](https://yardoc.org/) for documentation, there is a companion plugin gem that automatically generates documentation for configuration fields.
|
|
550
|
+
|
|
551
|
+
Add to your Gemfile:
|
|
552
|
+
|
|
553
|
+
```ruby
|
|
554
|
+
group :development, :test do
|
|
555
|
+
gem 'yard-ultra_settings'
|
|
556
|
+
end
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
Then add the following to your `.yardopts` file:
|
|
560
|
+
|
|
561
|
+
```
|
|
562
|
+
--plugin ultra_settings
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Once configured, the plugin automatically enhances YARD documentation for any classes that inherit from `UltraSettings::Configuration`. Field definitions will automatically generate method documentation with proper types and return values.
|
|
566
|
+
|
|
567
|
+
See the [yard-ultra_settings](https://github.com/bdurand/ultra_settings/tree/main/yard_plugin) gem for more details.
|
|
568
|
+
|
|
569
|
+
### Using YARD Macros
|
|
570
|
+
|
|
571
|
+
If you prefer not to install the plugin gem, you can use YARD's built-in macro feature by adding documentation comments before field definitions:
|
|
572
|
+
|
|
573
|
+
```ruby
|
|
574
|
+
class MyServiceConfiguration < UltraSettings::Configuration
|
|
575
|
+
self.fields_secret_by_default = false
|
|
576
|
+
|
|
577
|
+
# @!method host
|
|
578
|
+
# The hostname for the service
|
|
579
|
+
# @return [String, nil]
|
|
580
|
+
field :host, type: :string
|
|
581
|
+
|
|
582
|
+
# @!method port
|
|
583
|
+
# The port for the service
|
|
584
|
+
# @return [Integer]
|
|
585
|
+
field :port, type: :integer, default: 80
|
|
586
|
+
end
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
This approach gives you full control over the documentation but requires more manual effort for each field.
|
|
590
|
+
|
|
523
591
|
## Installation
|
|
524
592
|
|
|
525
593
|
Add this line to your application's Gemfile:
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.8.
|
|
1
|
+
2.8.1
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<% source_value = configuration.__value_from_source__(field.name, source) %>
|
|
2
|
+
<% overridden_by = source_overridden_by(source, current_source) %>
|
|
3
|
+
<div class="ultra-settings-source <%= 'ultra-settings-source-active' if current_source == source %> <%= 'ultra-settings-source-overridden' if overridden_by && !source_value.nil? %>">
|
|
4
|
+
<span class="ultra-settings-source-type">
|
|
5
|
+
<%= source_type %>
|
|
6
|
+
</span>
|
|
7
|
+
|
|
8
|
+
<div class="ultra-settings-source-value-container">
|
|
9
|
+
<% if source_name %>
|
|
10
|
+
<code class="ultra-settings-source-value">
|
|
11
|
+
<%= source_name %>
|
|
12
|
+
</code>
|
|
13
|
+
<% end %>
|
|
14
|
+
|
|
15
|
+
<%= show_defined_value(source_name, source_value, field.secret?) %>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<% if current_source == source %>
|
|
19
|
+
<span class="ultra-settings-source-indicator">
|
|
20
|
+
Currently active
|
|
21
|
+
</span>
|
|
22
|
+
<% elsif overridden_by && !source_value.nil? %>
|
|
23
|
+
<%= override_indicator(overridden_by) %>
|
|
24
|
+
<% end %>
|
|
25
|
+
|
|
26
|
+
<% if source == :settings %>
|
|
27
|
+
<% edit_url = UltraSettings.runtime_settings_url(name: field.runtime_setting, type: field.type, description: field.description) %>
|
|
28
|
+
|
|
29
|
+
<% if edit_url %>
|
|
30
|
+
<a
|
|
31
|
+
href="<%= html_escape(edit_url) %>"
|
|
32
|
+
class="ultra-settings-edit-link"
|
|
33
|
+
title="Edit <%= html_escape(field.runtime_setting) %>"
|
|
34
|
+
>
|
|
35
|
+
<%= edit_icon %>
|
|
36
|
+
</a>
|
|
37
|
+
<% end %>
|
|
38
|
+
<% end %>
|
|
39
|
+
</div>
|
data/app/application.css
CHANGED
|
@@ -132,6 +132,10 @@ code.ultra-settings-field-data-value {
|
|
|
132
132
|
gap: 0.5rem;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
.ultra-settings-field-description svg {
|
|
136
|
+
margin-top: 0.125rem;
|
|
137
|
+
}
|
|
138
|
+
|
|
135
139
|
.ultra-settings-description-text {
|
|
136
140
|
flex: 1;
|
|
137
141
|
}
|
|
@@ -166,13 +170,19 @@ code.ultra-settings-field-data-value {
|
|
|
166
170
|
min-width: 120px;
|
|
167
171
|
}
|
|
168
172
|
|
|
173
|
+
.ultra-settings-source-value-container {
|
|
174
|
+
display: flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
gap: 0.5rem;
|
|
177
|
+
flex: 1;
|
|
178
|
+
margin: 0 0.75rem;
|
|
179
|
+
}
|
|
180
|
+
|
|
169
181
|
.ultra-settings-source-value {
|
|
170
182
|
font-family: monospace;
|
|
171
183
|
font-size: 0.875rem;
|
|
172
184
|
color: var(--source-value-color);
|
|
173
185
|
font-weight: 550;
|
|
174
|
-
flex: 1;
|
|
175
|
-
margin: 0 0.75rem;
|
|
176
186
|
word-break: break-all;
|
|
177
187
|
}
|
|
178
188
|
|
|
@@ -189,19 +199,95 @@ code.ultra-settings-field-data-value {
|
|
|
189
199
|
font-size: 1rem;
|
|
190
200
|
}
|
|
191
201
|
|
|
202
|
+
.ultra-settings-source-overridden {
|
|
203
|
+
background-color: var(--source-overridden-bg-color);
|
|
204
|
+
border-color: var(--source-overridden-border-color);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.ultra-settings-source-overridden .ultra-settings-source-type {
|
|
208
|
+
color: var(--source-overridden-type-color);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.ultra-settings-source-overridden .ultra-settings-source-value-container .ultra-settings-source-value {
|
|
212
|
+
color: var(--source-overridden-value-color);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.ultra-settings-source-override {
|
|
216
|
+
font-size: 0.75rem;
|
|
217
|
+
font-weight: 600;
|
|
218
|
+
color: var(--source-overridden-warning-color);
|
|
219
|
+
display: flex;
|
|
220
|
+
align-items: center;
|
|
221
|
+
gap: 0.25rem;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.ultra-settings-source-override svg {
|
|
225
|
+
flex-shrink: 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.ultra-settings-source-override-text {
|
|
229
|
+
text-transform: uppercase;
|
|
230
|
+
letter-spacing: 0.025em;
|
|
231
|
+
}
|
|
232
|
+
|
|
192
233
|
.ultra-settings-icon-info {
|
|
193
234
|
color: var(--info-color);
|
|
194
235
|
cursor: pointer;
|
|
236
|
+
padding: 0.25rem;
|
|
237
|
+
border-radius: 0.25rem;
|
|
238
|
+
transition: all 0.15s ease;
|
|
239
|
+
display: inline-flex;
|
|
240
|
+
align-items: center;
|
|
241
|
+
justify-content: center;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.ultra-settings-icon-info:hover {
|
|
245
|
+
background-color: var(--source-bg-color);
|
|
246
|
+
transform: scale(1.15);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.ultra-settings-icon-info:active {
|
|
250
|
+
transform: scale(1.05);
|
|
195
251
|
}
|
|
196
252
|
|
|
197
253
|
.ultra-settings-icon-not-set {
|
|
198
254
|
color: var(--warning-color);
|
|
199
255
|
cursor: pointer;
|
|
256
|
+
padding: 0.25rem;
|
|
257
|
+
border-radius: 0.25rem;
|
|
258
|
+
transition: all 0.15s ease;
|
|
259
|
+
display: inline-flex;
|
|
260
|
+
align-items: center;
|
|
261
|
+
justify-content: center;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.ultra-settings-icon-not-set:hover {
|
|
265
|
+
background-color: var(--source-bg-color);
|
|
266
|
+
transform: scale(1.15);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.ultra-settings-icon-not-set:active {
|
|
270
|
+
transform: scale(1.05);
|
|
200
271
|
}
|
|
201
272
|
|
|
202
273
|
.ultra-settings-icon-secret {
|
|
203
274
|
color: var(--disabled-color);
|
|
204
275
|
cursor: pointer;
|
|
276
|
+
padding: 0.25rem;
|
|
277
|
+
border-radius: 0.25rem;
|
|
278
|
+
transition: all 0.15s ease;
|
|
279
|
+
display: inline-flex;
|
|
280
|
+
align-items: center;
|
|
281
|
+
justify-content: center;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.ultra-settings-icon-secret:hover {
|
|
285
|
+
background-color: var(--source-bg-color);
|
|
286
|
+
transform: scale(1.15);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.ultra-settings-icon-secret:active {
|
|
290
|
+
transform: scale(1.05);
|
|
205
291
|
}
|
|
206
292
|
|
|
207
293
|
.ultra-settings-edit-link {
|
|
@@ -219,25 +305,54 @@ code.ultra-settings-field-data-value {
|
|
|
219
305
|
}
|
|
220
306
|
|
|
221
307
|
.ultra-settings-dialog {
|
|
222
|
-
min-width:
|
|
308
|
+
min-width: 24rem;
|
|
309
|
+
max-width: 90vw;
|
|
223
310
|
padding: 0;
|
|
224
|
-
border:
|
|
225
|
-
border-radius: 0.
|
|
311
|
+
border: none;
|
|
312
|
+
border-radius: 0.5rem;
|
|
313
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
314
|
+
animation: ultra-settings-dialog-show 0.2s ease-out;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.ultra-settings-dialog::backdrop {
|
|
318
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
319
|
+
animation: ultra-settings-backdrop-show 0.2s ease-out;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
@keyframes ultra-settings-dialog-show {
|
|
323
|
+
from {
|
|
324
|
+
opacity: 0;
|
|
325
|
+
transform: scale(0.95) translateY(-10px);
|
|
326
|
+
}
|
|
327
|
+
to {
|
|
328
|
+
opacity: 1;
|
|
329
|
+
transform: scale(1) translateY(0);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
@keyframes ultra-settings-backdrop-show {
|
|
334
|
+
from {
|
|
335
|
+
opacity: 0;
|
|
336
|
+
}
|
|
337
|
+
to {
|
|
338
|
+
opacity: 1;
|
|
339
|
+
}
|
|
226
340
|
}
|
|
227
341
|
|
|
228
342
|
.ultra-settings-dialog-header {
|
|
229
|
-
padding:
|
|
343
|
+
padding: 1rem 1.25rem;
|
|
230
344
|
background-color: var(--field-header-bg-color);
|
|
231
345
|
color: var(--field-header-text-color);
|
|
232
346
|
font-size: 1rem;
|
|
233
347
|
display: flex;
|
|
234
|
-
align-items:
|
|
348
|
+
align-items: center;
|
|
349
|
+
border-bottom: 1px solid var(--field-border-color);
|
|
235
350
|
}
|
|
236
351
|
|
|
237
352
|
.ultra-settings-dialog-title {
|
|
238
353
|
flex: 1;
|
|
239
|
-
|
|
240
|
-
font-
|
|
354
|
+
font-weight: 600;
|
|
355
|
+
font-size: 1.125rem;
|
|
241
356
|
}
|
|
242
357
|
|
|
243
358
|
.ultra-settings-dialog-close {
|
|
@@ -246,12 +361,37 @@ code.ultra-settings-field-data-value {
|
|
|
246
361
|
color: var(--field-header-text-color);
|
|
247
362
|
cursor: pointer;
|
|
248
363
|
padding: 0.25rem;
|
|
364
|
+
border-radius: 0.25rem;
|
|
365
|
+
display: flex;
|
|
366
|
+
align-items: center;
|
|
367
|
+
justify-content: center;
|
|
368
|
+
transition: background-color 0.15s ease;
|
|
369
|
+
margin-left: 0.5rem;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.ultra-settings-dialog-close:hover {
|
|
373
|
+
background-color: var(--source-bg-color);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.ultra-settings-dialog-close:active {
|
|
377
|
+
transform: scale(0.95);
|
|
249
378
|
}
|
|
250
379
|
|
|
251
380
|
.ultra-settings-dialog-body {
|
|
252
|
-
padding:
|
|
381
|
+
padding: 1.5rem;
|
|
253
382
|
background-color: var(--background-color);
|
|
254
383
|
color: var(--text-color);
|
|
384
|
+
max-height: 60vh;
|
|
385
|
+
overflow-y: auto;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.ultra-settings-dialog-body .ultra-settings-field-data-value {
|
|
389
|
+
display: block;
|
|
390
|
+
word-break: break-all;
|
|
391
|
+
white-space: pre-wrap;
|
|
392
|
+
font-size: 0.9375rem;
|
|
393
|
+
line-height: 1.6;
|
|
394
|
+
padding: 0.75rem 1rem;
|
|
255
395
|
}
|
|
256
396
|
|
|
257
397
|
/* Dropdown Styles */
|
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
--source-border-color: #e9ecef;
|
|
29
29
|
--source-active-bg-color: #e7f3ff;
|
|
30
30
|
--source-active-border-color: #0d6efd;
|
|
31
|
+
--source-overridden-bg-color: #fff3cd;
|
|
32
|
+
--source-overridden-border-color: #ffc107;
|
|
33
|
+
--source-overridden-type-color: #4d4d4d;
|
|
34
|
+
--source-overridden-value-color: #2b2b2b;
|
|
35
|
+
--source-overridden-warning-color: #996900;
|
|
31
36
|
--source-type-color: #666666;
|
|
32
37
|
--source-value-color: #444444;
|
|
33
38
|
--source-indicator-color: #0d6efd;
|
|
@@ -77,6 +82,11 @@
|
|
|
77
82
|
--source-border-color: #444444;
|
|
78
83
|
--source-active-bg-color: #1a3a52;
|
|
79
84
|
--source-active-border-color: #0d6efd;
|
|
85
|
+
--source-overridden-bg-color: #3d3416;
|
|
86
|
+
--source-overridden-border-color: #997404;
|
|
87
|
+
--source-overridden-type-color: #d4d4d4;
|
|
88
|
+
--source-overridden-value-color: #e8e8e8;
|
|
89
|
+
--source-overridden-warning-color: #ffb84d;
|
|
80
90
|
--source-type-color: #adb5bd;
|
|
81
91
|
--source-value-color: #ced4da;
|
|
82
92
|
--source-indicator-color: #6ea8fe;
|
data/app/configuration.html.erb
CHANGED
|
@@ -55,83 +55,28 @@
|
|
|
55
55
|
<div class="ultra-settings-field-sources">
|
|
56
56
|
<% sources = configuration.__available_sources__(field.name) %>
|
|
57
57
|
<% if sources.include?(:env) %>
|
|
58
|
-
|
|
59
|
-
<span class="ultra-settings-source-type">
|
|
60
|
-
Environment Variable
|
|
61
|
-
</span>
|
|
62
|
-
|
|
63
|
-
<code class="ultra-settings-source-value">
|
|
64
|
-
<%= field.env_var %>
|
|
65
|
-
<%= show_defined_value(field.env_var, configuration.__value_from_source__(field.name, :env), field.secret?) %>
|
|
66
|
-
</code>
|
|
67
|
-
|
|
68
|
-
<% if source == :env %>
|
|
69
|
-
<span class="ultra-settings-source-indicator">
|
|
70
|
-
Currently active
|
|
71
|
-
</span>
|
|
72
|
-
<% end %>
|
|
73
|
-
</div>
|
|
58
|
+
<%= render_partial "data_source", configuration: configuration, field: field, source: :env, current_source: source, source_type: "Environment Variable", source_name: field.env_var %>
|
|
74
59
|
<% end %>
|
|
75
60
|
|
|
76
61
|
<% if sources.include?(:settings) %>
|
|
77
|
-
|
|
78
|
-
<span class="ultra-settings-source-type">Runtime Setting</span>
|
|
79
|
-
|
|
80
|
-
<code class="ultra-settings-source-value">
|
|
81
|
-
<%= field.runtime_setting %>
|
|
82
|
-
<%= show_defined_value(field.runtime_setting, configuration.__value_from_source__(field.name, :settings), field.secret?) %>
|
|
83
|
-
</code>
|
|
84
|
-
|
|
85
|
-
<% if source == :settings %>
|
|
86
|
-
<span class="ultra-settings-source-indicator">
|
|
87
|
-
Currently active
|
|
88
|
-
</span>
|
|
89
|
-
<% end %>
|
|
90
|
-
|
|
91
|
-
<% edit_url = UltraSettings.runtime_settings_url(name: field.runtime_setting, type: field.type, description: field.description) %>
|
|
92
|
-
|
|
93
|
-
<% if edit_url %>
|
|
94
|
-
<a
|
|
95
|
-
href="<%= html_escape(edit_url) %>"
|
|
96
|
-
class="ultra-settings-edit-link"
|
|
97
|
-
title="Edit <%= html_escape(field.runtime_setting) %>"
|
|
98
|
-
>
|
|
99
|
-
<%= edit_icon %>
|
|
100
|
-
</a>
|
|
101
|
-
<% end %>
|
|
102
|
-
</div>
|
|
62
|
+
<%= render_partial "data_source", configuration: configuration, field: field, source: :settings, current_source: source, source_type: "Runtime Setting", source_name: field.runtime_setting %>
|
|
103
63
|
<% end %>
|
|
104
64
|
|
|
105
65
|
<% if sources.include?(:yaml) %>
|
|
106
|
-
|
|
107
|
-
<span class="ultra-settings-source-type">Configuration File</span>
|
|
108
|
-
|
|
109
|
-
<code class="ultra-settings-source-value">
|
|
110
|
-
<%= field.yaml_key %>
|
|
111
|
-
<%= show_defined_value(field.yaml_key, configuration.__value_from_source__(field.name, :yaml), field.secret?) %>
|
|
112
|
-
</code>
|
|
113
|
-
|
|
114
|
-
<% if source == :yaml %>
|
|
115
|
-
<span class="ultra-settings-source-indicator">
|
|
116
|
-
Currently active
|
|
117
|
-
</span>
|
|
118
|
-
<% end %>
|
|
119
|
-
</div>
|
|
66
|
+
<%= render_partial "data_source", configuration: configuration, field: field, source: :yaml, current_source: source, source_type: "Configuration File", source_name: field.yaml_key %>
|
|
120
67
|
<% end %>
|
|
121
68
|
|
|
122
|
-
<% if sources.include?(:default)
|
|
123
|
-
|
|
124
|
-
|
|
69
|
+
<% if sources.include?(:default) %>
|
|
70
|
+
<%= render_partial "data_source", configuration: configuration, field: field, source: :default, current_source: source, source_type: "Default Value", source_name: nil %>
|
|
71
|
+
<% end %>
|
|
125
72
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
</
|
|
73
|
+
<% if source == :default && configuration[field.name].nil? %>
|
|
74
|
+
<div class="ultra-settings-source ultra-settings-source-active">
|
|
75
|
+
<span class="ultra-settings-source-type">Not Set</span>
|
|
129
76
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
</span>
|
|
134
|
-
<% end %>
|
|
77
|
+
<span class="ultra-settings-source-indicator">
|
|
78
|
+
Currently active
|
|
79
|
+
</span>
|
|
135
80
|
</div>
|
|
136
81
|
<% end %>
|
|
137
82
|
</div>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module UltraSettings
|
|
4
|
+
class AuditDataSources
|
|
5
|
+
class << self
|
|
6
|
+
# Find environment variables that are set but have the same value as their default.
|
|
7
|
+
# These environment variables could potentially be removed since they're not changing behavior.
|
|
8
|
+
#
|
|
9
|
+
# @return [Array<Array<(String, Object)>>] An array of tuples containing environment variable names and their default values
|
|
10
|
+
def unnecessary_env_vars
|
|
11
|
+
env_vars_at_default = []
|
|
12
|
+
each_configuration do |config|
|
|
13
|
+
each_field_using_source(config, :env) do |field|
|
|
14
|
+
value = config[field.name]
|
|
15
|
+
default_value = default_config_value(config, field)
|
|
16
|
+
env_vars_at_default << [field.env_var, default_value] if default_value == value
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
env_vars_at_default
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Find runtime settings that are set but have the same value as their default.
|
|
23
|
+
# These runtime settings could potentially be removed since they're not changing behavior.
|
|
24
|
+
#
|
|
25
|
+
# @return [Array<Array<(String, Object)>>] An array of tuples containing runtime setting names and their default values
|
|
26
|
+
def unnecessary_runtime_settings
|
|
27
|
+
unnecessary_runtime_settings = []
|
|
28
|
+
each_configuration do |config|
|
|
29
|
+
each_field_using_source(config, :settings) do |field|
|
|
30
|
+
value = config[field.name]
|
|
31
|
+
default_value = default_config_value(config, field)
|
|
32
|
+
unnecessary_runtime_settings << [field.runtime_setting, default_value] if default_value == value
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
unnecessary_runtime_settings
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Find environment variables that could be moved to runtime settings.
|
|
39
|
+
# These are non-default environment variable values where a runtime setting is also available.
|
|
40
|
+
#
|
|
41
|
+
# @return [Array<Array<(String, String, Object)>>] An array of tuples containing environment variable name, runtime setting name, and current value
|
|
42
|
+
def env_vars_can_be_runtime_setting
|
|
43
|
+
env_vars_can_be_runtime = []
|
|
44
|
+
each_configuration do |config|
|
|
45
|
+
each_field_using_source(config, :env) do |field|
|
|
46
|
+
value = config[field.name]
|
|
47
|
+
default_value = default_config_value(config, field)
|
|
48
|
+
next unless field.runtime_setting && value != default_value
|
|
49
|
+
|
|
50
|
+
env_vars_can_be_runtime << [field.env_var, field.runtime_setting, value]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
env_vars_can_be_runtime
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Find environment variables being used that don't have default values defined.
|
|
57
|
+
# These configurations require an environment variable to be set.
|
|
58
|
+
#
|
|
59
|
+
# @return [Array<Array<(String, Symbol, String, Object)>>] An array of tuples containing class name, field name, environment variable name, and current value
|
|
60
|
+
def env_vars_without_default
|
|
61
|
+
no_default_env_var_fields = []
|
|
62
|
+
each_configuration do |config|
|
|
63
|
+
each_field_using_source(config, :env) do |field|
|
|
64
|
+
value = default_config_value(config, field)
|
|
65
|
+
if value.nil?
|
|
66
|
+
no_default_env_var_fields << [config.class.name, field.name, field.env_var, config[field.name]]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
no_default_env_var_fields
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def each_configuration(&_block)
|
|
76
|
+
UltraSettings::Configuration.descendant_configurations.each do |config_class|
|
|
77
|
+
config = config_class.instance
|
|
78
|
+
yield config
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def each_field_using_source(config, source, &_block)
|
|
83
|
+
config.class.fields.each do |field|
|
|
84
|
+
next if field.secret?
|
|
85
|
+
next unless config.__source__(field.name) == source
|
|
86
|
+
|
|
87
|
+
yield field
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def default_config_value(config, field)
|
|
92
|
+
yaml_value = config.__value_from_source__(field.name, :yaml)
|
|
93
|
+
default_value = config.__value_from_source__(field.name, :default)
|
|
94
|
+
yaml_value || default_value
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -10,6 +10,7 @@ module UltraSettings
|
|
|
10
10
|
@env_var_prefix = nil
|
|
11
11
|
@runtime_setting_prefix = nil
|
|
12
12
|
@description = nil
|
|
13
|
+
@descendants = []
|
|
13
14
|
|
|
14
15
|
class << self
|
|
15
16
|
# Set a description for the configuration. This is optional. It will be displayed
|
|
@@ -84,7 +85,8 @@ module UltraSettings
|
|
|
84
85
|
secret: secret
|
|
85
86
|
)
|
|
86
87
|
|
|
87
|
-
|
|
88
|
+
caller_location = caller_locations(1, 1).first
|
|
89
|
+
class_eval <<~RUBY, caller_location.path, caller_location.lineno # rubocop:disable Security/Eval, Style/EvalWithLocation
|
|
88
90
|
def #{name}
|
|
89
91
|
__get_value__(#{name.inspect})
|
|
90
92
|
end
|
|
@@ -110,7 +112,7 @@ module UltraSettings
|
|
|
110
112
|
name = name.to_s
|
|
111
113
|
return true if defined_fields.include?(name)
|
|
112
114
|
|
|
113
|
-
if superclass
|
|
115
|
+
if superclass < Configuration
|
|
114
116
|
superclass.include_field?(name)
|
|
115
117
|
else
|
|
116
118
|
false
|
|
@@ -369,12 +371,30 @@ module UltraSettings
|
|
|
369
371
|
YamlConfig.new(configuration_file, yaml_config_env).to_h
|
|
370
372
|
end
|
|
371
373
|
|
|
374
|
+
# Get all descendant configuration classes (subclasses and their subclasses, recursively).
|
|
375
|
+
#
|
|
376
|
+
# @return [Array<Class>] All classes that inherit from this class.
|
|
377
|
+
def descendant_configurations
|
|
378
|
+
@descendants ||= []
|
|
379
|
+
@descendants.flat_map { |subclass| [subclass] + subclass.descendant_configurations }
|
|
380
|
+
end
|
|
381
|
+
|
|
372
382
|
private
|
|
373
383
|
|
|
384
|
+
# Hook called when this class is inherited. Tracks all descendant classes.
|
|
385
|
+
#
|
|
386
|
+
# @param subclass [Class] The subclass that is inheriting from this class.
|
|
387
|
+
# @return [void]
|
|
388
|
+
def inherited(subclass)
|
|
389
|
+
super
|
|
390
|
+
@descendants ||= []
|
|
391
|
+
@descendants << subclass
|
|
392
|
+
end
|
|
393
|
+
|
|
374
394
|
def defined_fields
|
|
375
395
|
unless defined?(@defined_fields)
|
|
376
396
|
fields = {}
|
|
377
|
-
if superclass
|
|
397
|
+
if superclass < Configuration
|
|
378
398
|
fields = superclass.send(:defined_fields).dup
|
|
379
399
|
end
|
|
380
400
|
@defined_fields = fields
|
|
@@ -153,5 +153,44 @@ module UltraSettings
|
|
|
153
153
|
this.closest('.ultra-settings-configuration').querySelector('.ultra-settings-dialog-close').blur();
|
|
154
154
|
JAVASCRIPT
|
|
155
155
|
end
|
|
156
|
+
|
|
157
|
+
def source_priority
|
|
158
|
+
[:env, :settings, :yaml, :default]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def source_overridden_by(current_source, active_source)
|
|
162
|
+
return nil if current_source == active_source
|
|
163
|
+
|
|
164
|
+
current_index = source_priority.index(current_source)
|
|
165
|
+
active_index = source_priority.index(active_source)
|
|
166
|
+
|
|
167
|
+
return nil if current_index.nil? || active_index.nil?
|
|
168
|
+
return nil if current_index < active_index
|
|
169
|
+
|
|
170
|
+
active_source
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def override_indicator(overridden_by_source)
|
|
174
|
+
source_names = {
|
|
175
|
+
env: "environment variable",
|
|
176
|
+
settings: "runtime setting",
|
|
177
|
+
yaml: "configuration file",
|
|
178
|
+
default: "default value"
|
|
179
|
+
}
|
|
180
|
+
<<~HTML
|
|
181
|
+
<span class="ultra-settings-source-override" title="Overridden by #{source_names[overridden_by_source]}">
|
|
182
|
+
#{warning_icon(14)}
|
|
183
|
+
<span class="ultra-settings-source-override-text">Overridden by #{source_names[overridden_by_source]}</span>
|
|
184
|
+
</span>
|
|
185
|
+
HTML
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def warning_icon(size = 16)
|
|
189
|
+
<<~HTML
|
|
190
|
+
<svg width="#{size}" height="#{size}" fill="currentColor" viewBox="0 0 16 16">
|
|
191
|
+
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5m.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2"/>
|
|
192
|
+
</svg>
|
|
193
|
+
HTML
|
|
194
|
+
end
|
|
156
195
|
end
|
|
157
196
|
end
|
|
@@ -23,6 +23,8 @@ module UltraSettings
|
|
|
23
23
|
|
|
24
24
|
app_config_dir = Rails.root.join(directory)
|
|
25
25
|
app_config_dir.glob("**/*_configuration.rb").each do |file_path|
|
|
26
|
+
next unless file_path.file? && file_path.readable?
|
|
27
|
+
|
|
26
28
|
relative_path = file_path.relative_path_from(app_config_dir).to_s
|
|
27
29
|
class_name = relative_path.chomp(".rb").classify
|
|
28
30
|
unless UltraSettings.added?(class_name)
|
|
@@ -32,5 +34,11 @@ module UltraSettings
|
|
|
32
34
|
end
|
|
33
35
|
end
|
|
34
36
|
end
|
|
37
|
+
|
|
38
|
+
rake_tasks do
|
|
39
|
+
Dir.glob(File.expand_path("tasks/*.rake", __dir__)).each do |rake_file|
|
|
40
|
+
load rake_file
|
|
41
|
+
end
|
|
42
|
+
end
|
|
35
43
|
end
|
|
36
44
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :ultra_settings do
|
|
4
|
+
desc <<~DOC
|
|
5
|
+
Generates a report of environment variables used in configurations that are set to their default values.
|
|
6
|
+
This report can help identify environment variables that are superfluous and can be removed. It skips any
|
|
7
|
+
environment variables that are used for secrets.
|
|
8
|
+
DOC
|
|
9
|
+
task unnecessary_env_vars: :environment do
|
|
10
|
+
require_relative "utils"
|
|
11
|
+
require_relative "../audit_data_sources"
|
|
12
|
+
|
|
13
|
+
UltraSettings::Tasks::Utils.eager_load!
|
|
14
|
+
env_vars_at_default = UltraSettings::AuditDataSources.unnecessary_env_vars
|
|
15
|
+
|
|
16
|
+
output = env_vars_at_default.collect do |env_var, value|
|
|
17
|
+
"Environment variable #{env_var} is set to its default value: #{value.inspect}"
|
|
18
|
+
end
|
|
19
|
+
puts output
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
desc <<~DOC
|
|
23
|
+
Generates a report of runtime settings used in configurations that are set to their default values.
|
|
24
|
+
This report can help identify runtime settings that are superfluous and can be removed. It skips any
|
|
25
|
+
runtime settings that are used for secrets.
|
|
26
|
+
DOC
|
|
27
|
+
task unnecessary_runtime_settings: :environment do
|
|
28
|
+
require_relative "utils"
|
|
29
|
+
require_relative "../audit_data_sources"
|
|
30
|
+
|
|
31
|
+
UltraSettings::Tasks::Utils.eager_load!
|
|
32
|
+
unnecessary_runtime_settings = UltraSettings::AuditDataSources.unnecessary_runtime_settings
|
|
33
|
+
|
|
34
|
+
output = unnecessary_runtime_settings.collect do |runtime_setting, value|
|
|
35
|
+
"Runtime setting #{runtime_setting} is set to its default value: #{value.inspect}"
|
|
36
|
+
end
|
|
37
|
+
puts output
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
desc <<~DOC
|
|
41
|
+
Generates a report of environment variables used in configurations that can be converted to runtime settings.
|
|
42
|
+
This report can help identify environment variables that can be removed if the corresponding runtime settings
|
|
43
|
+
are set. It skips any environment variables that are used for secrets.
|
|
44
|
+
DOC
|
|
45
|
+
task env_vars_can_be_runtime_setting: :environment do
|
|
46
|
+
require_relative "utils"
|
|
47
|
+
require_relative "../audit_data_sources"
|
|
48
|
+
|
|
49
|
+
UltraSettings::Tasks::Utils.eager_load!
|
|
50
|
+
env_vars_can_be_runtime = UltraSettings::AuditDataSources.env_vars_can_be_runtime_setting
|
|
51
|
+
|
|
52
|
+
output = env_vars_can_be_runtime.collect do |env_var, runtime_setting, value|
|
|
53
|
+
"Environment variable #{env_var} can be converted to runtime setting #{runtime_setting} with value: #{value.inspect}"
|
|
54
|
+
end
|
|
55
|
+
puts output
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
desc <<~DOC
|
|
59
|
+
Generates a report of environment variables used in configurations that do not have a default value.
|
|
60
|
+
This report can help identify settings that could be set in YAML or with a default value rather than via
|
|
61
|
+
environment variables. If these changes are made, then the environment variables could be removed.
|
|
62
|
+
It skips any environment variables that are used for secrets.
|
|
63
|
+
DOC
|
|
64
|
+
task env_vars_without_default: :environment do
|
|
65
|
+
require_relative "utils"
|
|
66
|
+
require_relative "../audit_data_sources"
|
|
67
|
+
|
|
68
|
+
UltraSettings::Tasks::Utils.eager_load!
|
|
69
|
+
env_vars_without_default = UltraSettings::AuditDataSources.env_vars_without_default
|
|
70
|
+
|
|
71
|
+
output = env_vars_without_default.collect do |config, field, env_var, value|
|
|
72
|
+
"Environment variable #{env_var} used by #{config}##{field} does not have a default value (current value: #{value.inspect})"
|
|
73
|
+
end
|
|
74
|
+
puts output
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module UltraSettings
|
|
4
|
+
module Tasks
|
|
5
|
+
module Utils
|
|
6
|
+
class << self
|
|
7
|
+
# Helper for eager loading a Rails application.
|
|
8
|
+
def eager_load!
|
|
9
|
+
return unless defined?(Rails.application.config.eager_load)
|
|
10
|
+
return if Rails.application.config.eager_load
|
|
11
|
+
|
|
12
|
+
if defined?(Rails.application.eager_load!)
|
|
13
|
+
Rails.application.eager_load!
|
|
14
|
+
elsif defined?(Rails.autoloaders.zeitwerk_enabled?) && Rails.autoloaders.zeitwerk_enabled?
|
|
15
|
+
Rails.autoloaders.each(&:eager_load)
|
|
16
|
+
else
|
|
17
|
+
raise "Failed to eager load application."
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/ultra_settings.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ultra_settings
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.8.
|
|
4
|
+
version: 2.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Durand
|
|
@@ -29,6 +29,7 @@ executables: []
|
|
|
29
29
|
extensions: []
|
|
30
30
|
extra_rdoc_files: []
|
|
31
31
|
files:
|
|
32
|
+
- AGENTS.md
|
|
32
33
|
- ARCHITECTURE.md
|
|
33
34
|
- CHANGELOG.md
|
|
34
35
|
- MIT-LICENSE.txt
|
|
@@ -36,6 +37,7 @@ files:
|
|
|
36
37
|
- VERSION
|
|
37
38
|
- app/_config_description.html.erb
|
|
38
39
|
- app/_config_list.html.erb
|
|
40
|
+
- app/_data_source.html.erb
|
|
39
41
|
- app/_select_menu.html.erb
|
|
40
42
|
- app/application.css
|
|
41
43
|
- app/application.js
|
|
@@ -47,6 +49,7 @@ files:
|
|
|
47
49
|
- app/layout_vars.css.erb
|
|
48
50
|
- lib/ultra_settings.rb
|
|
49
51
|
- lib/ultra_settings/application_view.rb
|
|
52
|
+
- lib/ultra_settings/audit_data_sources.rb
|
|
50
53
|
- lib/ultra_settings/coerce.rb
|
|
51
54
|
- lib/ultra_settings/config_helper.rb
|
|
52
55
|
- lib/ultra_settings/configuration.rb
|
|
@@ -55,6 +58,8 @@ files:
|
|
|
55
58
|
- lib/ultra_settings/rack_app.rb
|
|
56
59
|
- lib/ultra_settings/railtie.rb
|
|
57
60
|
- lib/ultra_settings/render_helper.rb
|
|
61
|
+
- lib/ultra_settings/tasks/audit_data_sources.rake
|
|
62
|
+
- lib/ultra_settings/tasks/utils.rb
|
|
58
63
|
- lib/ultra_settings/uninitialized_runtime_settings.rb
|
|
59
64
|
- lib/ultra_settings/version.rb
|
|
60
65
|
- lib/ultra_settings/view_helper.rb
|
|
@@ -82,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
82
87
|
- !ruby/object:Gem::Version
|
|
83
88
|
version: '0'
|
|
84
89
|
requirements: []
|
|
85
|
-
rubygems_version:
|
|
90
|
+
rubygems_version: 4.0.3
|
|
86
91
|
specification_version: 4
|
|
87
92
|
summary: UltraSettings is a Ruby gem that provides a flexible and documented approach
|
|
88
93
|
to managing application configurations from multiple sources, including environment
|