Class: Pvectl::Config::Provider
- Inherits:
-
Object
- Object
- Pvectl::Config::Provider
- Defined in:
- lib/pvectl/config/provider.rb,
sig/pvectl/config/provider.rbs
Overview
Loads and resolves configuration from multiple sources.
Provider handles loading configuration from YAML files and environment variables, then merging them with proper priority to produce a final ResolvedConfig. Priority order (highest to lowest):
- CLI options
- Environment variables
- Configuration file
Constant Summary collapse
- ENV_VARS =
Mapping of environment variables to configuration keys
{ "PROXMOX_HOST" => :server, "PROXMOX_TOKEN_ID" => :token_id, "PROXMOX_TOKEN_SECRET" => :token_secret, "PROXMOX_USER" => :username, "PROXMOX_PASSWORD" => :password, "PROXMOX_VERIFY_SSL" => :verify_ssl, "PVECTL_CONTEXT" => :context, "PVECTL_CONFIG" => :config_path, # Retry/timeout settings "PROXMOX_TIMEOUT" => :timeout, "PROXMOX_RETRY_COUNT" => :retry_count, "PROXMOX_RETRY_DELAY" => :retry_delay, "PROXMOX_MAX_RETRY_DELAY" => :max_retry_delay, "PROXMOX_RETRY_WRITES" => :retry_writes }.freeze
- INTEGER_VARS =
Keys that should be parsed as integers
i[timeout retry_count retry_delay max_retry_delay].freeze
- BOOLEAN_VARS =
Keys that should be parsed as booleans
i[verify_ssl retry_writes].freeze
Instance Method Summary collapse
-
#build_resolved_config(context:, cluster:, user:, env_config:, cli_options:) ⇒ Models::ResolvedConfig
Builds a ResolvedConfig from merged sources.
-
#file_exists?(path) ⇒ Boolean
Checks if a configuration file exists at the given path.
-
#find_cluster(config, name) ⇒ Models::Cluster
Finds a cluster by name in the configuration.
-
#find_context(config, name) ⇒ Models::Context
Finds a context by name in the configuration.
-
#find_user(config, name) ⇒ Models::User
Finds a user by name in the configuration.
-
#insecure_permissions?(path) ⇒ Boolean
Checks if a file has insecure permissions (readable by group/others).
-
#load_env ⇒ Hash
Loads configuration from environment variables.
-
#load_file(path) ⇒ Hash
Loads configuration from a YAML file.
-
#parse_boolean_env(value) ⇒ Boolean
Parses a boolean environment variable.
-
#parse_env_value(key, value) ⇒ Object
Parses an environment variable value to the appropriate type.
-
#parse_integer_env(key, value) ⇒ Integer
Parses an integer environment variable with validation.
-
#resolve(config_path:, cli_options:, cluster_override: nil) ⇒ Models::ResolvedConfig
Resolves full configuration by merging all sources.
-
#resolve_auth(user, env_config) ⇒ Array
Resolves authentication type and credentials.
-
#resolve_context_name(cli_options:, file_config: {}) ⇒ String?
Resolves the context name from various sources.
Instance Method Details
#build_resolved_config(context:, cluster:, user:, env_config:, cli_options:) ⇒ Models::ResolvedConfig
Builds a ResolvedConfig from merged sources.
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/pvectl/config/provider.rb', line 238 def build_resolved_config(context:, cluster:, user:, env_config:, cli_options:) # Merge with priority: CLI > ENV > file server = env_config[:server] || cluster.server verify_ssl = env_config.key?(:verify_ssl) ? env_config[:verify_ssl] : cluster.verify_ssl # Determine auth type and credentials auth_type, token_id, token_secret, username, password = resolve_auth(user, env_config) # Resolve retry/timeout settings with priority: ENV > file timeout = env_config[:timeout] || cluster.timeout retry_count = env_config[:retry_count] || cluster.retry_count retry_delay = env_config[:retry_delay] || cluster.retry_delay max_retry_delay = env_config[:max_retry_delay] || cluster.max_retry_delay retry_writes = env_config.key?(:retry_writes) ? env_config[:retry_writes] : cluster.retry_writes Models::ResolvedConfig.new( context_name: context.name, server: server, verify_ssl: verify_ssl, certificate_authority: cluster., auth_type: auth_type, token_id: token_id, token_secret: token_secret, username: username, password: password, default_node: context.default_node, timeout: timeout, retry_count: retry_count, retry_delay: retry_delay, max_retry_delay: max_retry_delay, retry_writes: retry_writes ) end |
#file_exists?(path) ⇒ Boolean
Checks if a configuration file exists at the given path.
52 53 54 |
# File 'lib/pvectl/config/provider.rb', line 52 def file_exists?(path) File.exist?(path) end |
#find_cluster(config, name) ⇒ Models::Cluster
Finds a cluster by name in the configuration.
206 207 208 209 210 211 212 213 |
# File 'lib/pvectl/config/provider.rb', line 206 def find_cluster(config, name) clusters = config["clusters"] || [] cluster_hash = clusters.find { |c| c["name"] == name } raise ClusterNotFoundError, "Cluster '#{name}' not found in configuration" if cluster_hash.nil? Models::Cluster.from_hash(cluster_hash) end |
#find_context(config, name) ⇒ Models::Context
Finds a context by name in the configuration.
188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/pvectl/config/provider.rb', line 188 def find_context(config, name) contexts = config["contexts"] || [] context_hash = contexts.find { |c| c["name"] == name } if context_hash.nil? available = contexts.map { |c| c["name"] }.join(", ") raise ContextNotFoundError, "Context '#{name}' not found. Available: #{available}" end Models::Context.from_hash(context_hash) end |
#find_user(config, name) ⇒ Models::User
Finds a user by name in the configuration.
221 222 223 224 225 226 227 228 |
# File 'lib/pvectl/config/provider.rb', line 221 def find_user(config, name) users = config["users"] || [] user_hash = users.find { |u| u["name"] == name } raise UserNotFoundError, "User '#{name}' not found in configuration" if user_hash.nil? Models::User.from_hash(user_hash) end |
#insecure_permissions?(path) ⇒ Boolean
Checks if a file has insecure permissions (readable by group/others).
60 61 62 63 64 65 |
# File 'lib/pvectl/config/provider.rb', line 60 def (path) return false unless File.exist?(path) mode = File.stat(path).mode & 0o777 (mode & 0o077) != 0 end |
#load_env ⇒ Hash
Loads configuration from environment variables.
86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/pvectl/config/provider.rb', line 86 def load_env result = {} ENV_VARS.each do |env_var, config_key| value = ENV[env_var] next if value.nil? || value.empty? result[config_key] = parse_env_value(config_key, value) end result end |
#load_file(path) ⇒ Hash
Loads configuration from a YAML file.
73 74 75 76 77 78 79 80 |
# File 'lib/pvectl/config/provider.rb', line 73 def load_file(path) raise ConfigNotFoundError, "Configuration file not found: #{path}" unless File.exist?(path) content = File.read(path) YAML.safe_load(content, permitted_classes: [Symbol]) rescue Psych::SyntaxError => e raise InvalidConfigError, "Invalid YAML in #{path}: #{e.message}" end |
#parse_boolean_env(value) ⇒ Boolean
Parses a boolean environment variable.
162 163 164 |
# File 'lib/pvectl/config/provider.rb', line 162 def parse_boolean_env(value) %w[true 1 yes].include?(value.to_s.downcase) end |
#parse_env_value(key, value) ⇒ Object
Parses an environment variable value to the appropriate type.
148 149 150 151 152 153 154 155 156 |
# File 'lib/pvectl/config/provider.rb', line 148 def parse_env_value(key, value) if BOOLEAN_VARS.include?(key) parse_boolean_env(value) elsif INTEGER_VARS.include?(key) parse_integer_env(key, value) else value end end |
#parse_integer_env(key, value) ⇒ Integer
Parses an integer environment variable with validation.
172 173 174 175 176 177 178 179 180 |
# File 'lib/pvectl/config/provider.rb', line 172 def parse_integer_env(key, value) unless value.to_s.match?(/\A\d+\z/) raise InvalidConfigError, "Invalid integer for #{key.to_s.tr('_', '-')}: '#{value}' " \ "(must be a non-negative integer)" end value.to_i end |
#resolve(config_path:, cli_options:, cluster_override: nil) ⇒ Models::ResolvedConfig
Resolves full configuration by merging all sources.
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/pvectl/config/provider.rb', line 120 def resolve(config_path:, cli_options:, cluster_override: nil) file_config = load_file(config_path) env_config = load_env context_name = resolve_context_name(cli_options: , file_config: file_config) context = find_context(file_config, context_name) cluster_name = cluster_override || context.cluster_ref cluster = find_cluster(file_config, cluster_name) user = find_user(file_config, context.user_ref) build_resolved_config( context: context, cluster: cluster, user: user, env_config: env_config, cli_options: ) end |
#resolve_auth(user, env_config) ⇒ Array
Resolves authentication type and credentials.
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 |
# File 'lib/pvectl/config/provider.rb', line 277 def resolve_auth(user, env_config) # ENV token auth takes precedence if env_config[:token_id] && env_config[:token_secret] return [:token, env_config[:token_id], env_config[:token_secret], nil, nil] end # ENV password auth if env_config[:username] && env_config[:password] return [:password, nil, nil, env_config[:username], env_config[:password]] end # Fall back to user from config if user.token_auth? [:token, user.token_id, user.token_secret, nil, nil] else [:password, nil, nil, user.username, user.password] end end |
#resolve_context_name(cli_options:, file_config: {}) ⇒ String?
Resolves the context name from various sources.
Priority: CLI > ENV > file
106 107 108 |
# File 'lib/pvectl/config/provider.rb', line 106 def resolve_context_name(cli_options:, file_config: {}) [:context] || ENV["PVECTL_CONTEXT"] || file_config["current-context"] end |