Class: Pvectl::Commands::Push
- Inherits:
-
Object
- Object
- Pvectl::Commands::Push
- Defined in:
- lib/pvectl/commands/push.rb
Overview
Push command – applies YAML manifests to the Proxmox cluster. Creates resources that don’t exist, updates those that do.
Constant Summary collapse
- RESOURCE_TYPES =
{ "vm" => :vm, "vms" => :vm, "container" => :container, "containers" => :container, "ct" => :container }.freeze
Class Method Summary collapse
-
.register(cli) ⇒ void
Registers the push command with the CLI.
Instance Method Summary collapse
-
#execute ⇒ Integer
Executes the push command.
-
#initialize(args, options, global_options) ⇒ Push
constructor
A new instance of Push.
Constructor Details
#initialize(args, options, global_options) ⇒ Push
Returns a new instance of Push.
69 70 71 72 73 74 |
# File 'lib/pvectl/commands/push.rb', line 69 def initialize(args, , ) @args = args @options = @global_options = @stdin_mode = false end |
Class Method Details
.register(cli) ⇒ void
This method returns an undefined value.
Registers the push command with the CLI.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/pvectl/commands/push.rb', line 23 def self.register(cli) cli.desc "Push YAML manifest to cluster (create or update)" cli.long_desc <<~HELP DESCRIPTION Applies resource configuration from YAML manifest files to the Proxmox cluster. Creates resources that don't exist, updates those that do. Shows a diff and asks for confirmation before applying. EXAMPLES $ pvectl push vm -f vm-100.yaml $ pvectl push vm -f ./manifests/ $ pvectl push -f ./manifests/ $ pvectl push vm -f vm-100.yaml --dry-run $ pvectl push vm -f vm-100.yaml --yes $ pvectl push vm -f vm-new.yaml # no vmid → auto-assign $ pvectl pull vm 100 | pvectl push --yes $ cat vm-100.yaml | pvectl push vm --dry-run NOTES Without -f, reads YAML from stdin (pipe-friendly). With -f, reads from file or directory (repeatable). Without resource type, reads kind from each manifest. If metadata.vmid is omitted, a new VMID is auto-assigned and the source YAML file is updated with the assigned ID. Stdin mode requires --yes or --dry-run (no interactive prompt). With --yes, skips confirmation (useful for CI/CD). With --dry-run, shows diff without applying changes. SEE ALSO pull, edit, create, delete HELP cli.command :push do |c| c.flag [:f, :file], desc: "YAML file or directory to push", multiple: true c.switch [:y, :yes], desc: "Auto-confirm without prompting", negatable: false c.switch [:"dry-run"], desc: "Show diff without applying", negatable: false c.action do |, , args| Push.new(args, , ).execute end end end |
Instance Method Details
#execute ⇒ Integer
Executes the push command.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/pvectl/commands/push.rb', line 79 def execute args = @args.dup filter_type = parse_resource_type(args) unless args.empty? return usage_error("Unexpected arguments: #{args.join(', ')}. Use -f to specify files.") end yaml_contents = read_input return usage_error("No YAML content provided. Use -f <path> or pipe YAML to stdin.") if yaml_contents.empty? load_config connection = Pvectl::Connection.new(@config) service, pull_service = build_services(connection) result = service.prepare_batch(yaml_contents, filter_type: filter_type) # Report errors and skipped result[:errors].each { |e| $stderr.puts "Error: #{e}" } result[:skipped].each { |s| $stderr.puts "Info: #{s}" } if result[:plans].empty? if result[:errors].empty? $stdout.puts "No changes to apply." end # Refresh unchanged file-backed manifests with server-assigned values # (MAC addresses, volume names, UUIDs, etc.) refresh_unchanged_manifests(result[:unchanged] || [], pull_service) return result[:errors].empty? ? ExitCodes::SUCCESS : ExitCodes::GENERAL_ERROR end # Display plans display_plans(result[:plans]) if @options[:"dry-run"] $stdout.puts "\n(dry-run mode -- no changes applied)" return ExitCodes::SUCCESS end # Confirm unless --yes unless @options[:yes] if @stdin_mode return usage_error("Stdin mode requires --yes or --dry-run (no interactive prompt available)") end $stdout.print "\nApply #{result[:plans].length} change(s)? [y/N] " answer = $stdin.gets&.strip&.downcase unless answer == "y" || answer == "yes" $stdout.puts "Cancelled." return ExitCodes::SUCCESS end end # Apply apply_result = service.apply(result[:plans]) apply_result[:results].each do |r| if r[:success] $stdout.puts "#{r[:action].capitalize}d #{type_label_for(r)} #{r[:vmid]} successfully." else $stderr.puts "Error: Failed to #{r[:action]} #{r[:vmid]}: #{r[:error]}" end end # Refresh source manifest files with current server state refresh_manifests(apply_result[:results], result[:plans], pull_service) refresh_unchanged_manifests(result[:unchanged] || [], pull_service) apply_result[:errors].empty? ? ExitCodes::SUCCESS : ExitCodes::GENERAL_ERROR rescue Pvectl::Config::ConfigNotFoundError, Pvectl::Config::InvalidConfigError, Pvectl::Config::ContextNotFoundError, Pvectl::Config::ClusterNotFoundError, Pvectl::Config::UserNotFoundError raise rescue StandardError => e $stderr.puts "Error: #{e.}" ExitCodes::GENERAL_ERROR end |