Class: Pvectl::Services::SetNode
- Inherits:
-
Object
- Object
- Pvectl::Services::SetNode
- Defined in:
- lib/pvectl/services/set_node.rb,
sig/pvectl/services/set_node.rbs
Overview
Orchestrates non-interactive node configuration updates.
Takes key-value pairs directly (no editor), computes diff against current config, and applies changes via the API. Supports dry-run mode and optimistic locking via digest.
The timezone key is special: it lives at PUT /nodes/{node}/time
rather than PUT /nodes/{node}/config, so the service splits it off
and routes it to a separate time_repository.
Constant Summary collapse
- TIMEZONE_KEY =
Key recognized by this service as the node timezone (handled by the
/timeendpoint rather than/config). Accepted as Symbol or String. :timezone
Instance Method Summary collapse
-
#apply_config_changes(node_name, changes, original_config) ⇒ void
Applies non-timezone diff entries via the node config endpoint.
-
#build_result(resource_info, **attrs) ⇒ Models::NodeOperationResult
Builds a NodeOperationResult with the :set operation.
-
#build_update_params(changes, original_config) ⇒ Hash
Builds API update parameters from diff (excluding timezone, which is routed elsewhere).
-
#compute_diff(config, params) ⇒ Hash
Computes diff between current config and requested params.
-
#compute_timezone_change(current, requested) ⇒ Hash{from: String, to: String}?
Builds a timezone-change descriptor when requested != current.
-
#config_changes_empty?(changes, tz_change) ⇒ Boolean
True when the only changes in the diff are timezone-related.
-
#current_timezone(node_name) ⇒ String?
Fetches the current timezone for the node.
-
#execute(node_name:, params:) ⇒ Models::NodeOperationResult?
Executes the non-interactive node config update.
-
#initialize(node_repository:, time_repository: nil, options: {}) ⇒ SetNode
constructor
Creates a new SetNode service.
-
#merge_timezone_into_diff(changes, tz_change) ⇒ void
Folds a timezone change into the diff structure so it shows up in the operation result alongside config-key changes.
-
#no_changes?(changes) ⇒ Boolean
Returns true when the diff contains no actionable change.
-
#not_found_result(node_name) ⇒ Models::NodeOperationResult
Builds a not-found error result.
-
#split_timezone(params) ⇒ Array(Hash, String?)
Separates the
:timezonekey (accepted as Symbol or String) from other config params.
Constructor Details
#initialize(node_repository:, time_repository: nil, options: {}) ⇒ SetNode
Creates a new SetNode service.
38 39 40 41 42 |
# File 'lib/pvectl/services/set_node.rb', line 38 def initialize(node_repository:, time_repository: nil, options: {}) @node_repository = node_repository @time_repository = time_repository = end |
Instance Method Details
#apply_config_changes(node_name, changes, original_config) ⇒ void
This method returns an undefined value.
Applies non-timezone diff entries via the node config endpoint.
173 174 175 176 177 178 |
# File 'lib/pvectl/services/set_node.rb', line 173 def apply_config_changes(node_name, changes, original_config) update_params = build_update_params(changes, original_config) return if update_params.empty? || update_params.keys == [:digest] @node_repository.update(node_name, update_params) end |
#build_result(resource_info, **attrs) ⇒ Models::NodeOperationResult
Builds a NodeOperationResult with the :set operation.
220 221 222 223 224 225 |
# File 'lib/pvectl/services/set_node.rb', line 220 def build_result(resource_info, **attrs) node_model = Models::Node.new(name: resource_info[:node_name]) Models::NodeOperationResult.new( operation: :set, node_model: node_model, resource: resource_info, **attrs ) end |
#build_update_params(changes, original_config) ⇒ Hash
Builds API update parameters from diff (excluding timezone, which is routed elsewhere).
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/pvectl/services/set_node.rb', line 199 def build_update_params(changes, original_config) params = {} changes[:changed].each do |key, (_old, new_val)| next if key == TIMEZONE_KEY params[key] = new_val end changes[:added].each do |key, val| next if key == TIMEZONE_KEY params[key] = val end params[:digest] = original_config[:digest] if original_config[:digest] && !params.empty? params end |
#compute_diff(config, params) ⇒ Hash
Computes diff between current config and requested params.
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/pvectl/services/set_node.rb', line 141 def compute_diff(config, params) changed = {} added = {} params.each do |key, value| sym_key = key.to_sym current = config[sym_key] if current.nil? added[sym_key] = value elsif current.to_s != value.to_s changed[sym_key] = [current.to_s, value.to_s] end end { changed: changed, added: added, removed: [] } end |
#compute_timezone_change(current, requested) ⇒ Hash{from: String, to: String}?
Builds a timezone-change descriptor when requested != current.
115 116 117 118 119 120 |
# File 'lib/pvectl/services/set_node.rb', line 115 def compute_timezone_change(current, requested) return nil if requested.nil? return nil if current.to_s == requested.to_s { from: current.to_s, to: requested.to_s } end |
#config_changes_empty?(changes, tz_change) ⇒ Boolean
True when the only changes in the diff are timezone-related.
185 186 187 188 189 190 191 |
# File 'lib/pvectl/services/set_node.rb', line 185 def config_changes_empty?(changes, tz_change) return false if tz_change.nil? non_tz_changed = changes[:changed].keys - [TIMEZONE_KEY] non_tz_added = changes[:added].keys - [TIMEZONE_KEY] non_tz_changed.empty? && non_tz_added.empty? end |
#current_timezone(node_name) ⇒ String?
Fetches the current timezone for the node.
104 105 106 107 108 |
# File 'lib/pvectl/services/set_node.rb', line 104 def current_timezone(node_name) return nil unless @time_repository @time_repository.fetch(node_name).timezone end |
#execute(node_name:, params:) ⇒ Models::NodeOperationResult?
Executes the non-interactive node config update.
Fetches current config and (if timezone is requested) current timezone, computes diffs against the requested params, and applies changes via the appropriate API endpoint (unless dry-run).
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/pvectl/services/set_node.rb', line 53 def execute(node_name:, params:) node = @node_repository.get(node_name) return not_found_result(node_name) unless node config = @node_repository.fetch_config(node_name) config_params, requested_tz = split_timezone(params) current_tz = requested_tz ? current_timezone(node_name) : nil changes = compute_diff(config, config_params) tz_change = compute_timezone_change(current_tz, requested_tz) merge_timezone_into_diff(changes, tz_change) if tz_change return nil if no_changes?(changes) resource_info = { node_name: node_name, status: node.status, diff: changes } return build_result(resource_info, success: true) if [:dry_run] apply_config_changes(node_name, changes, config) unless config_changes_empty?(changes, tz_change) @time_repository.set_timezone(node_name, tz_change[:to]) if tz_change build_result(resource_info, success: true) rescue StandardError => e build_result({ node_name: node_name }, success: false, error: e.) end |
#merge_timezone_into_diff(changes, tz_change) ⇒ void
This method returns an undefined value.
Folds a timezone change into the diff structure so it shows up in the operation result alongside config-key changes.
128 129 130 131 132 133 134 |
# File 'lib/pvectl/services/set_node.rb', line 128 def merge_timezone_into_diff(changes, tz_change) if tz_change[:from].nil? || tz_change[:from].empty? changes[:added][TIMEZONE_KEY] = tz_change[:to] else changes[:changed][TIMEZONE_KEY] = [tz_change[:from], tz_change[:to]] end end |
#no_changes?(changes) ⇒ Boolean
Returns true when the diff contains no actionable change.
163 164 165 |
# File 'lib/pvectl/services/set_node.rb', line 163 def no_changes?(changes) changes[:changed].empty? && changes[:added].empty? && changes[:removed].empty? end |
#not_found_result(node_name) ⇒ Models::NodeOperationResult
Builds a not-found error result.
231 232 233 |
# File 'lib/pvectl/services/set_node.rb', line 231 def not_found_result(node_name) build_result({ node_name: node_name }, success: false, error: "Node #{node_name} not found") end |
#split_timezone(params) ⇒ Array(Hash, String?)
Separates the :timezone key (accepted as Symbol or String) from
other config params.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/pvectl/services/set_node.rb', line 85 def split_timezone(params) remaining = {} requested_tz = nil params.each do |key, value| if key.to_sym == TIMEZONE_KEY requested_tz = value.to_s else remaining[key] = value end end [remaining, requested_tz] end |