Class: Pvectl::Commands::Pull

Inherits:
Object
  • Object
show all
Defined in:
lib/pvectl/commands/pull.rb

Overview

Pull command – exports resource configuration from the Proxmox cluster as kubectl-like YAML manifest files.

Examples:

Register with CLI

Pull.register(cli)

Constant Summary collapse

RESOURCE_TYPES =
{
  "vm" => :vm,
  "vms" => :vm,
  "container" => :container,
  "containers" => :container,
  "ct" => :container
}.freeze
FILE_PREFIXES =
{
  vm: "vm",
  container: "ct"
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args, options, global_options) ⇒ Pull

Returns a new instance of Pull.

Parameters:

  • args (Array<String>)

    command arguments

  • options (Hash)

    command options

  • global_options (Hash)

    global CLI options



81
82
83
84
85
# File 'lib/pvectl/commands/pull.rb', line 81

def initialize(args, options, global_options)
  @args = args
  @options = options
  @global_options = global_options
end

Class Method Details

.register(cli) ⇒ void

This method returns an undefined value.

Registers the pull command with the CLI.

Parameters:

  • cli (GLI::App)

    the CLI application object



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
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/pvectl/commands/pull.rb', line 30

def self.register(cli)
  cli.desc "Pull resource configuration to YAML manifest"
  cli.long_desc <<~HELP
    DESCRIPTION
      Exports resource configuration from the Proxmox cluster as kubectl-like
      YAML manifest files. Supports single resources, multiple IDs, selectors,
      and bulk export with --all.

      When writing to files (-f), shows a diff of changes and asks for
      confirmation before overwriting existing files. Use --yes to skip
      confirmation or --dry-run to preview changes without writing.

    EXAMPLES
      $ pvectl pull vm 100
      $ pvectl pull vm 100 -f vm-100.yaml
      $ pvectl pull vm 100 -f vm-100.yaml --dry-run
      $ pvectl pull vm 100 101 102 -f ./manifests/
      $ pvectl pull vm --all -f ./manifests/ --yes
      $ pvectl pull vm -f ./manifests/
      $ pvectl pull vm -l tags=prod -f ./manifests/
      $ pvectl pull container 200

    NOTES
      Without -f, YAML is printed to stdout (pipe-friendly).
      With -f, shows diff against existing files and asks to confirm.
      With --all or -l, -f must point to a directory.
      When -f points to a directory with existing manifests and no IDs
      are given, IDs are inferred from file names (vm-{vmid}.yaml).
      File naming convention: vm-{vmid}.yaml or ct-{vmid}.yaml.

    SEE ALSO
      push, get, describe, edit
  HELP

  cli.command :pull do |c|
    c.flag [:f, :file], desc: "Output file or directory"
    c.flag [:l, :selector], desc: "Filter by selector (e.g. tags=prod,status=running)", multiple: true
    c.switch [:all], desc: "Pull all resources of given type", negatable: false
    c.switch [:y, :yes], desc: "Auto-confirm without prompting", negatable: false
    c.switch [:"dry-run"], desc: "Show diff without writing files", negatable: false
    c.flag [:node], desc: "Limit to specific node"

    c.action do |global_options, options, args|
      Pull.new(args, options, global_options).execute
    end
  end
end

Instance Method Details

#executeInteger

Executes the pull command.

Returns:

  • (Integer)

    exit code



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
# File 'lib/pvectl/commands/pull.rb', line 90

def execute
  resource_type_str = @args.shift
  return usage_error("Resource type is required (vm, container)") unless resource_type_str

  type = RESOURCE_TYPES[resource_type_str.downcase]
  return usage_error("Unknown resource type '#{resource_type_str}'. Valid: vm, container") unless type

  ids = @args.map(&:to_i)
  all = @options[:all]
  node = @options[:node]
  output = @options[:file]

  # Auto-infer IDs from existing manifest files in directory
  if ids.empty? && !all && @options[:selector].nil? && output && directory_output?(output)
    ids = infer_ids_from_directory(output, type)
  end

  if ids.empty? && !all && @options[:selector].nil?
    return usage_error("Provide resource IDs, --all, or -l selector")
  end

  if (all || @options[:selector]) && output && !directory_output?(output)
    return usage_error("--all and -l require -f to be a directory (end with /)")
  end

  selector = build_selector(type)

  load_config
  connection = Pvectl::Connection.new(@config)
  service = build_service(connection)

  result = service.execute(type: type, ids: ids, all: all, node: node, selector: selector)

  result[:errors].each { |e| $stderr.puts "Error: #{e}" }

  write_output(result[:manifests], type, output)

  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.message}"
  ExitCodes::GENERAL_ERROR
end