Class: PVE::Cli

Inherits:
Object
  • Object
show all
Defined in:
lib/pve/cli/ct.rb,
lib/pve/cli/ha.rb,
lib/pve/cli/qm.rb,
lib/pve/cli/base.rb,
lib/pve/cli/node.rb,
lib/pve/cli/task.rb,
lib/pve/cli/storage.rb,
lib/pve/cli.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config_file = nil) ⇒ Cli

Returns a new instance of Cli.



39
40
41
42
43
44
45
46
47
# File 'lib/pve/cli.rb', line 39

def initialize config_file = nil
  config_file ||= '/etc/pve/pvecli.yml'
  @cfg =
    YAML.safe_load File.read( config_file), [], [], false,
      config_file, symbolize_names: true
  @cli = DenCli.new File.basename($0), "PVE CLI"
  @interactive = false
  prepare
end

Instance Attribute Details

#cfgObject (readonly)

Returns the value of attribute cfg.



29
30
31
# File 'lib/pve/cli.rb', line 29

def cfg
  @cfg
end

#cliObject (readonly)

Returns the value of attribute cli.



29
30
31
# File 'lib/pve/cli.rb', line 29

def cli
  @cli
end

Instance Method Details

#appliances(node, regexp, system, applications) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/pve/cli.rb', line 217

def appliances node, regexp, system, applications
  system = applications = true  if system.nil? and applications.nil?
  node = node ? Proxmox::Node.find_by_name!( node) : Proxmox::Node.all.first
  to = TablizedOutput.new %w<Section Package Version OS Template Description>, format: %w[> > > > > <]
  node.aplinfo.
    select {|a| 'system' == a.section ? system : applications}.
    each do |apl|
      to.push [
        apl.section,
        apl.package,
        apl.version,
        apl.os,
        apl.template,
        apl.description,
      ]
    end
  to.print order: [1,2]
end

#call(*argv) ⇒ Object



208
209
210
211
212
213
214
215
# File 'lib/pve/cli.rb', line 208

def call *argv
  cli.call *argv
rescue RestClient::ExceptionWithResponse
  STDERR.puts "#$! - #{$!.response} (#{$!.class})" #, $!.backtrace.map {|b|"  #{b}"}
rescue UsageError, DenCli::UsageError
  STDERR.puts $!
  exit 1
end

#cli_baseObject



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/pve/cli/base.rb', line 7

def cli_base
  cli.cmd :list, "List CT/VM-IDs", aliases: ['ls'], &lambda {|target=nil|
    connect
    nodes = Proxmox::Node.all
    nodes.
      flat_map {|n| [ n.method(:lxc), n.method(:qemu) ] }.
      flat_pmap {|m| m.call.map {|c| c.vmid.to_i } }.
      sort.
      each {|c| puts c }
  }

  cli.cmd( :status, "Lists Nodes/VMs/CTs with status", &lambda {|target=nil, sort: 'n', node: nil|
    connect
    node &&= /\A#{node}\z/
    to = TablizedOutput.new %w[Status HA ID Name Host Uptime CPU/% Mem/MiB Mem/% Disk/MiB Disk/%]
    push =
      if target
        target = /\A#{target}\z/
        lambda {|n| to.virt n  if n === target }
      else
        lambda {|n| to.virt n }
      end
    nodes = node ? Proxmox::Node.find_by_name( name) : Proxmox::Node.all
    nodes.each &push
    nodes.
      flat_map {|n| [ Thread.new( n, &:lxc), Thread.new( n, &:qemu) ] }.
      each {|n| n.value.each &push }
    to.print order: sort.each_char.map {|c| (2*c.ord[5]-1) * (' sainhucmd'.index( c.downcase)) }
  }).
    opt( :sort, '-s', '--sort=COLUMNS', "Sort by COLUMNs eg hn for host and name ([s]tatus, h[a], [i]d, [n]ame (default), [h]ost, [u]ptime, [c]pu, [m]em, [d]isk)").
    opt( :node, '-n', '--node=NODE', "List only hosted by this NODE")

  def prepare_show_config cnf
    r = {}
    cnf.each do |k,v|
      case k
      when :network
        v.each do |net|
          s =
            net.
              reject {|k, v| :card == k }.
              sort_by {|k, v| :name == k ? :AAAAAAAAA : k }.
              map {|k, v| case v when true then [k,1] when false then [k,0] else [k,v] end }.
              map {|k, v| "#{k}=#{v}" }
          r[net[:card].to_sym] = s.join(",")
        end
      when :sshkeys
        r[k] = CGI.unescape(v).gsub( /^/, " "*14).gsub /\A {14}|\n\z/, ''
      else
        case v
        when true then v = 1
        when false then v = 0
        end
        r[k] = v.to_s.gsub( /$^/, " "*14).gsub /\n\z/, ''
      end
    end
    r
  end

  def show_config cnf, old = nil
    cnf = prepare_show_config cnf
    if old
      old = prepare_show_config old
      (cnf.keys+old.keys).uniq.sort.each do |k|
        v, o = cnf[k], old[k]
        if v == o
          puts "#{k}:#{' ' * (12-k.length)} #{v}"
        else
          puts "\e[31m#{k}:#{' ' * (12-k.length)} #{o}\e[0m"  unless o.nil?
          puts "\e[32m#{k}:#{' ' * (12-k.length)} #{v}\e[0m"  unless v.nil?
        end
      end
    else
      cnf.sort_by{|k,v|k}.each do |k,v|
        puts "#{k}:#{' ' * (12-k.length)} #{v}"
      end
    end
  end

  cli.sub :config, "CT/VM Configuration", min: 2, aliases: %w[cnf] do |ccli|
    ccli.cmd :help, '', aliases: [nil, '-h', '--help'], &lambda {|*args| help ccli, *args }

    ccli.cmd :set, "Set Configs for CT/VM", min: 3, &lambda {|name_or_id, *args|
      if %w[-h --help].include? name_or_id
        STDERR.puts "Usage: set -h|--help # Show help"
        STDERR.puts "       set ct|vm --CNF1=VAL1 --CNF2=VAL2 ... # Set config-value.  Empty value clears field."
        exit 1
      end
      opts = {}
      until args.empty?
        case arg = args.shift
        when /\A--(\w+)=(.*)\z/
          opts[$1.to_sym] = $2
        when /\A--(\w+)\z/
          opts[$1.to_sym] = args.shift
        else
          raise UsageError, "Expection option to set. What do you mean with: #{arg}"
        end
      end
      opts.each do |k, v|
        opts[k] =
          case v = opts[k]
          when '' then nil
          else v
          end
      end
      %i[migrate_downtime].each do |k|
        next  unless opts.has_key? k
        opts[k] =
          case v = opts[k]
          when nil, '', 'nil' then nil
          else v.to_f
          end
      end
      %i[memory background_delay balloon cores cpulimit cpuunits migrate_speed shares smp sockets vcpus swap tty].each do |k|
        next  unless opts.has_key? k
        opts[k] =
          case v = opts[k]
          when nil, '', 'nil' then nil
          else v.to_i
          end
      end
      %i[unprivileged debug onboot protection template].each do |k|
        next  unless opts.has_key? k
        opts[k] =
          case v = opts[k]
          when *%w[1 T TRUE t true True Y YES y yes Yes] then true
          when *%w[0 F FALSE f false False N NO n no No] then false
          when '', 'nil', nil then nil
          else raise UsageError, "Boolean expected, given: #{v.inspect}"
          end
      end
      connect
      th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
      old = th.config
      opts[:digest] ||= old[:digest]
      th.cnfset opts
      show_config th.config, old
    }

    ccli.cmd :show, "Show Config of CT/VM", aliases: %w[s], &lambda {|name_or_id|
      connect
      th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
      show_config th.config
    }
  end

  cli.cmd( :enter, "Enter Console of CT/Node", &lambda {|name_or_id|
    connect
    th = Proxmox::LXC.find( name_or_id) || Proxmox::Node.find_by_name( name_or_id)
    raise UsageError, "Container or Node not found: #{name_or_id}"  unless th
    STDERR.puts "! #{$?.exitstatus}"  unless th.enter
  }).
    completion do |*pre, arg|
      completion_helper *pre, arg do |f|
        complete_lxc( f) + complete_node( f)
      end
    end

  cli.cmd( :run, "Starts CT/VM", aliases: %w[start star], &lambda {|name_or_id|
    connect
    th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
    raise UsageError, "Container or Node not found: #{name_or_id}"  unless th
    start th
  }).
    completion do |*pre, arg|
      completion_helper *pre, arg do |f|
        complete_lxc( f) + complete_qemu( f)
      end
    end

  #cli.cmd :reboot, "Reboot CT/VM (not implemented, yet)", min: 6, &lambda {|name_or_id|
  #  connect
  #  th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
  #  raise UsageError, "Container or Node not found: #{name_or_id}"  unless th
  #  reboot th
  #}

  cli.cmd( :stop, "Stops CT/VM", min: 4, &lambda {|name_or_id|
    connect
    th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
    raise UsageError, "Container or Node not found: #{name_or_id}"  unless th
    stop th
  }).
    completion do |*pre, arg|
      completion_helper *pre, arg do |f|
        complete_lxc( f) + complete_qemu( f)
      end
    end

  cli.cmd( :help, '', aliases: ['-h', '--help'], &lambda {|*args, full:|
    if full
      cli.help_full *args, output: STDERR
    else
      cli.help *args, output: STDERR
    end
  }).
    opt( :full, '-f', '--[no-]full', 'Includes all commands of all subcommands.', default: false)

  cli.cmd :cli, 'Opens interactive console', min: 3, aliases: [nil], &lambda {
    @interactive = true
    cli.interactive( File.basename($0,'.rb')).run
  }
end

#cli_ctObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
# File 'lib/pve/cli/ct.rb', line 2

def cli_ct
  cli.sub :ct, "Containers", aliases: %w[lx lxc] do |ct_cli|
    ct_cli.cmd :list, "List CT-IDs", aliases: ['ls'], &lambda {|node=nil|
      connect
      nodes = node ? Proxmox::Node.find_by_name( name) : Proxmox::Node.all
      nodes.flat_map do |n|
        n.lxc.map {|c| c.vmid.to_i }
      end.sort.each {|c| puts c }
    }

    ct_cli.cmd( :status, "Lists CTs with status", aliases: [nil], &lambda {|target=nil, sort: 'n', node: nil|
      connect
      node &&= /\A#{node}\z/
      to = TablizedOutput.new %w[Status HA ID Name Host Uptime CPU/% Mem/MiB Mem/% Disk/MiB Disk/%]
      push =
        if target
          target = /\A#{target}\z/
          lambda {|n| to.virt n  if n === target }
        else
          lambda {|n| to.virt n }
        end
      nodes = node ? Proxmox::Node.find_by_name( name) : Proxmox::Node.all
      nodes.
        map {|n| Thread.new( n, &:lxc) }.
        each {|n| n.value.each &push }
      to.print order: sort.each_char.map {|c| (2*c.ord[5]-1) * (' sainhucmd'.index( c.downcase)) }
    }).
      opt( :sort, '-s', '--sort=COLUMNS', "Sort by COLUMNs eg hn for host and name ([s]tatus, h[a], [i]d, [n]ame (default), [h]ost, [u]ptime, [c]pu, [m]em, [d]isk)").
      opt( :node, '-n', '--node=NODE', "List only hosted by this NODE")

    ct_cli.cmd :enter, "Enter Console of CT", &lambda {|name_or_id|
      connect
      STDERR.puts "! #{$?.exitstatus}"  unless Proxmox::LXC.find!( name_or_id).enter
    }

    ct_cli.cmd :exec, "Executes Command in CT", min: 4, &lambda {|name_or_id, *command|
      connect
      STDERR.puts "! #{$?.exitstatus}"  unless Proxmox::LXC.find!( name_or_id).exec *command
    }

    ct_cli.cmd( :start, "Starts CT", min: 3, &lambda {|name_or_id, node: nil, fire:, timeout:, secs:|
      connect
      ct = Proxmox::LXC.find! name_or_id
      start ct, node: node, fire: fire, timeout: timeout, secs: secs
    }).
      opt( :node, "-nNODE", "--node=NODE", "On NODE (default, as is, so without migration)").
      tap {|c| opts_wait c }

    ct_cli.cmd( :stop, "Stops CT", min: 3, &lambda {|name_or_id, fire: nil, timeout:, secs:|
      connect
      ct = Proxmox::LXC.find! name_or_id
      stop ct, fire: fire, timeout: timeout, secs: secs
    }).tap {|c| opts_wait c }

    ct_cli.cmd( :wait, "Wait till CT is in state", &lambda {|name_or_id, state, timeout: nil, secs: nil|
      connect
      ct = Proxmox::LXC.find! name_or_id
      wait ct, state, timeout: timeout, secs: secs
    }).
      opt( :timeout, "-tTIMEOUT", "--timeout=TIMEOUT", "Wait for max TIMEOUT seconds (default: endless)", default: nil).
      opt( :secs, "-sSECONDS", "--seconds=SECONDS", "Check every SECONDS for state (default: 0.2)", default: 0.2)

    ct_cli.cmd( :create, "Creates a new container", &lambda {|template, *options| #, fire:, timeout:, secs:, start:|
      if %w[-h --help].include? template
        STDERR.puts "Usage: ct create TEMPLATE -h        # Shows template-related options"
        STDERR.puts "       ct create TEMPLATE [OPTIONS] # Creates a container"
        STDERR.puts "       ct create -l                 # Listing available templates"
        exit 1
      elsif %w[-l --list].include? template
        STDERR.puts PVE::CTTemplate.constants.reject {|c|:Base==c}.map {|c|c.to_s.titlecase.dasherize.downcase}
        exit 0
      end
      ctopts = {}
      OptionParser.new do |opts|
        ctt = PVE::CTTemplate.const_get template.classify
        opts.banner = <<EOU
Usage: ct create #{template} [options]

#{ctt.help}
Options: (*=Required)
EOU
        opts.on '-h', '--help', " Help!" do
          STDERR.puts opts
          exit 1  unless interactive?
          return
        end
        opts.on( '-r', '--[no-]-start', " Start container after creation") {|v| ctopts[:start] = v }
        opts.on( '-f', '--[no-]-fire', " Do not wait till running") {|v| ctopts[:start] = v }
        opts.on( '-t', '--timeout=TIMEOUT', " Wait for max TIMEOUT seconds (default: endless)") {|v| ctopts[:timeout] = v }
        opts.on( '-s', '--seconds=SECONDS', " Check every SECONDS for state (default: 0.2)") {|v| ctopts[:seconds] = v }
        ctt.requirements.each do |name, (type, req, desc, *args)|
          req = req ? "*" : " "
          case type
          when :boolean
            opts.on( "--[no-]#{name}", "#{req}#{desc}") {|v| ctopts[name] = v }
          when :string, :numeric
            opts.on( "--#{name}=#{type.upcase}", "#{req}#{desc}") {|v| ctopts[name] = v }
          when :enum
            opts.on( "--#{name}=#{type.upcase}", "#{req}#{desc} (#{args.first.join ', '})") do |v|
              ctopts[name] = v
            end
          end
        end
      end.parse! options
      connect
      create Proxmox::LXC, template, **ctopts
    })

    ct_cli.cmd( :config, 'Shows current config', aliases: %w[cnf], &lambda {|name_or_id|
      connect
      ct = Proxmox::LXC.find! name_or_id
      STDOUT.puts JSON.dump( ct.config)
    })

    ct_cli.cmd( :resize, 'Resize a disk', &lambda {|name_or_id, disk, size|
      connect
      ct = Proxmox::LXC.find! name_or_id
      task = ct.resize disk, size
      wait task, text: "Resizing #{ct.sid} #{disk} to #{size}"
    })

    ct_cli.cmd( :destroy, '', min: 7, &lambda {|name_or_id, fire:, secs:, timeout:, i_really_want_to_destroy:|
      raise UsageError, "Name/ID is not what you want to destroy"  unless name_or_id == i_really_want_to_destroy
      connect
      ct = Proxmox::LXC.find! name_or_id
      raise UsageError, "Container is not stopped"  unless ct.stopped?
      destroy ct, fire: fire, timeout: timeout, secs: secs
    }).tap {|c| opts_wait c }.
      opt( :i_really_want_to_destroy, "--i-really-want-to-destroy=NAMEORID", "Repeat the name/ID")

    ct_cli.cmd( :help, '', aliases: ['-h', '--help']) {|*args| help ct_cli, *args }
  end
end

#cli_haObject



11
12
13
14
15
16
17
18
19
20
21
22
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
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/pve/cli/ha.rb', line 11

def cli_ha
  cli.sub :ha, "Inspect High-Availability" do |hacli|
    hacli.cmd( :create, "Create HA for CT/VM", &lambda {|name_or_id, group:, comment: nil, max_relocate:, max_restart:, state:|
      connect
      th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
      raise UsageError, "Container or VirtualMachine not found: #{name_or_id}"  unless th
      ha = th.ha
      raise UsageError, "#{th.sid} is already High-Available"  if ha.active?
      ha.create group: group, comment: comment, max_relocate: max_relocate, max_restart: max_restart
    }).tap {|cl| opts_ha cl }

    hacli.cmd :remove, "Remove CT/VM from HA", &lambda {|name_or_id|
      connect
      th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
      raise UsageError, "Container or VirtualMachine not found: #{name_or_id}"  unless th
      ha = th.ha
      raise UsageError, "#{th.sid} is not High-Available"  unless ha.active?
      ha.delete
    }

    hacli.cmd( :active, "CT/VM should be high-available. Options are only for defaults, if not activated, yet.", &lambda {|name_or_id, group:, comment: nil, max_relocate:, max_restart:, state:|
      connect
      th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
      raise UsageError, "Container or VirtualMachine not found: #{name_or_id}"  unless th
      ha = th.ha
      ha.create group: group, comment: comment, max_relocate: max_relocate, max_restart: max_restart  if ha.active?
    }).tap {|cl| opts_ha cl }

    hacli.cmd :deactive, "CT/VM should NOT be high-available.", &lambda {|name_or_id|
      connect
      th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
      raise UsageError, "Container or VirtualMachine not found: #{name_or_id}"  unless th
      ha = th.ha
      ha.delete  unless ha.active?
    }

    hacli.cmd( :started, "CT/VM should be in state started.  By stopping CT/VM via pct/e state will be changed in HA, too.", &lambda {|name_or_id, force: nil|
      connect
      th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
      raise UsageError, "Container or VirtualMachine not found: #{name_or_id}"  unless th
      ha = th.ha
      ha = ha.create  unless ha.active?
      ha.disabled!  if force and ha.error?
      ha.started!
    }).opt( :force, "-f", "--force", "If CT/VM is in error-state, first reset HA, than try to start.")

    hacli.cmd :stopped, "CT/VM should be in state stopped.  By starting CT/VM via pct/e state will be changed in HA, too.", min: 3, &lambda {|name_or_id|
      connect
      th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
      raise UsageError, "Container or VirtualMachine not found: #{name_or_id}"  unless th
      ha = th.ha
      ha = ha.create  unless ha.active?
      ha.stopped!
    }

    hacli.cmd :reset, "If state of CT/VM is failed, Proxmox will not start/stop it anyway.  You have to reset state (state=disabled), first", &lambda {|name_or_id|
      connect
      th = Proxmox::LXC.find( name_or_id) || Proxmox::Qemu.find_by_name( name_or_id)
      raise UsageError, "Container or VirtualMachine not found: #{name_or_id}"  unless th
      ha = th.ha
      raise UsageError, "#{th.sid} is not High-Available"  if ha.active?
      ha.state = :disabled
    }

    hacli.cmd 'help', '', aliases: ['-h', '--help'], &lambda {|*args| help hacli, *args }
  end
end

#cli_nodeObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/pve/cli/node.rb', line 2

def cli_node
  cli.sub :node, "Nodes" do |nod_cli|
    nod_cli.cmd :status, "Lists nodes with status", aliases: [nil], &lambda {|node=nil|
      connect
      to = TablizedOutput.new %w[Status Node Uptime CPU Mem/MiB Disk/MiB]
      nodes = Proxmox::Node.all
      nodes = nodes.select {|n| node == n.name }  if node
      nodes.each do |n|
        to.push [
          n.status,
          n.node,
          Measured.seconds( n.uptime),
          "%.02f/%d" % [n.cpu, n.maxcpu],
          "#{Measured.bytes( n.mem)}/#{Measured.bytes( n.maxmem)}",
          "#{Measured.bytes( n.disk)}/#{Measured.bytes( n.maxdisk)}",
        ]
      end
      to.print order: [1]
    }

    nod_cli.cmd :exec, "Executes command on node", min: 4 do |name, *args|
      connect
      STDERR.puts "! #{$?.exitstatus}"  unless Proxmox::Node.find_by_name!( name).exec *args
    end

    nod_cli.cmd :enter, "Enter Console of node" do |name, *args|
      connect
      STDERR.puts "! #{$?.exitstatus}"  unless Proxmox::Node.find_by_name!( name).enter *args
    end

    nod_cli.sub :task, "Inspect tasks" do |tcli|
      tcli.cmd :list, "List done tasks", aliases: [nil, 'ls'], &lambda {|node|
        connect
        Proxmox::Node.find_by_name!( node).
          tasks.
          map( &:upid).
          sort.
          each {|upid| puts upid }
      }
    end

    nod_cli.cmd( 'help', '', aliases: ['-h', '--help']) {|*args| help nod_cli, *args }
  end
end

#cli_qmObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
# File 'lib/pve/cli/qm.rb', line 2

def cli_qm
  cli.sub :qm, "Virtual Machines", aliases: %w[v vm qemu], &lambda {|qm|
    qm.cmd :list, "List VM-IDs", aliases: ['ls'], &lambda {|node=nil|
      connect
      nodes = Proxmox::Node.all
      nodes = nodes.select {|n| node == n.name }  if node
      nodes.flat_map do |n|
        n.qemu.map {|c| c.vmid.to_i }
      end.sort.each {|c| puts c }
    }

    qm.cmd( :status, "Lists CTs with status", aliases: [nil], &lambda {|target=nil, sort: 'n', node: nil|
      connect
      node &&= /\A#{node}\z/
      to = TablizedOutput.new %w[Status HA ID Name Host Uptime CPU/% Mem/MiB Mem/% Disk/MiB Disk/%]
      push =
        if target
          target = /\A#{target}\z/
          lambda {|n| to.virt n  if n === target }
        else
          lambda {|n| to.virt n }
        end
      nodes = node ? Proxmox::Node.find_by_name( name) : Proxmox::Node.all
      nodes.
        map {|n| Thread.new( n, &:qemu) }.
        each {|n| n.value.each &push }
      to.print order: sort.each_char.map {|c| (2*c.ord[5]-1) * (' sainhucmd'.index( c.downcase)) }
    }).
      opt( :sort, '-s', '--sort=COLUMNS', "Sort by COLUMNs eg hn for host and name ([s]tatus, h[a], [i]d, [n]ame (default), [h]ost, [u]ptime, [c]pu, [m]em, [d]isk)").
      opt( :node, '-n', '--node=NODE', "List only hosted by this NODE")

    qm.cmd :exec, "Executes Command in VM via qemu-guest-agent", min: 4, &lambda {|name_or_id, *command|
      connect
      STDERR.puts "! #{$?.exitstatus}"  unless Proxmox::Qemu.find!( name_or_id).exec *command
    }

    qm.cmd( :resize, 'Resize a disk', &lambda {|name_or_id, disk, size|
      connect
      qm = Proxmox::Qemu.find! name_or_id
      task = qm.resize disk, size
      wait task, text: "Resizing #{qm.sid} #{disk} to #{size}"
    })

    qm.cmd 'help', '', aliases: ['-h', '--help'], &lambda {|*args| help qm, *args }
  }
end

#cli_storageObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/pve/cli/storage.rb', line 2

def cli_storage
  cli.sub :storage, "Storages", min: 3 do |cli_sm|
    cli_sm.cmd :list, "List Storages", aliases: ['ls'], &lambda {|node=nil|
      connect
      nodes = node ? [Proxmox::Node.find_by_name!( node)] : Proxmox::Node.all
      nodes.flat_map do |n|
        n.lxc.map {|c| c.vmid.to_i }
      end.sort.each {|c| puts c }
    }

    cli_sm.cmd :status, "List Storages with status", aliases: [nil], &lambda {|node=nil|
      connect
      to = TablizedOutput.new %w[A E S Storage Host Type]
      nodes = node ? [Proxmox::Node.find_by_name!( node)] : Proxmox::Node.all
      nodes.each do |n|
        n.storage.each do |v|
          to.push [ 
            case v.active
            when 1 then ColoredString.new 'Y', "32"
            when 0 then ColoredString.new 'n', "31"
            else v.active.to_s
            end,
            case v.enabled
            when 1 then ColoredString.new 'Y', "32"
            when 0 then ColoredString.new 'n', "31"
            else v.enabled.to_s
            end,
            1 == v.shared ? 's' : 'l', v.storage, v.node.node, v.type
          ]
        end
      end
      to.print order: [4,5]
    }

    cli_sm.sub :content, "Content of Storage", aliases: ['cnt'] do |cli_cnt|
      cli_cnt.cmd :list, "List Content", aliases: ['ls'], &lambda {|node=nil, storage|
        connect
        node = node ? Proxmox::Node.find_by_name!( node) : Proxmox::Node.all.first
        storage = node.storage.select {|sm| storage == sm.storage }.first
        storage.content.each {|c| puts c.to_s }
      }
      cli_cnt.cmd( :help, '', aliases: ['-h', '--help']) {|*args| help cli_cnt, *args }
    end

    cli_sm.cmd( :help, '', aliases: ['-h', '--help']) {|*args| help cli_sm, *args }
    #cli_sm.provide_help
  end

  cli.sub :apl, "Appliances - Downloadable container images", min: 3 do |cli_apl|

    cli_apl.cmd( :content, "Table of all provided appliances", aliases: [nil], &lambda {|node:, regexp:, system:, applications:|
      connect
      appliances node, regexp, system, applications
    }).
    opt( :node, '-n=NODE', '--node', 'Ask this node for appliances (any node should list the same)', default: nil).
    opt( :regexp, '-r=REGEXP', '--regexp', 'Filter by template', default: nil).
    opt( :system, '-s', '--system', 'Only system templates', default: nil).
    opt( :applications, '-a', '--applications', 'Only applications (non system) templates', default: nil)

    cli_apl.cmd( :system, "Table of provided systems", aliases: [nil], &lambda {|node:, regexp:|
      connect
      appliances node, regexp, true, nil
    }).
    opt( :node, '-n=NODE', '--node', 'Ask this node for appliances (any node should list the same)', default: nil).
    opt( :regexp, '-r=REGEXP', '--regexp', 'Filter by template', default: nil)

    cli_apl.cmd( :applications, "Table of provided applications", aliases: [nil], &lambda {|node:, regexp:|
      connect
      appliances node, regexp, nil, true
    }).
    opt( :node, '-n=NODE', '--node', 'Ask this node for appliances (any node should list the same)', default: nil).
    opt( :regexp, '-r=REGEXP', '--regexp', 'Filter by template', default: nil)

    cli_apl.cmd( :list, "List provided appliances", aliases: ['ls'], &lambda {|node=nil, regexp:|
      connect
      node = node ? Proxmox::Node.find_by_name!( node) : Proxmox::Node.all.first
      node.aplinfo.each do |apl|
        puts apl.template
      end
    }).
      opt( :regexp, '-r=REGEXP', '--regexp', 'Filters by name', default: nil)

    cli_apl.cmd :download, "Download appliance", aliases: ['dl'], min: 2, &lambda {|template, node, storage=nil|
      storage ||= 'local'
      connect
      node = Proxmox::Node.find_by_name! node
      apl = node.aplinfo.find {|apl| apl.template == template }
      raise UsageError, "Appliance not found"  unless apl
      task = apl.download storage
      wait task, text: "Download #{apl.template} on #{node.node} to #{storage}"
    }

    cli_apl.cmd( :help, '', aliases: ['-h', '--help']) {|*args| help cli_apl, *args }
  end
end

#cli_taskObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/pve/cli/task.rb', line 2

def cli_task
  cli.sub :task, "Inspect tasks" do |tcli|
    tcli.cmd :list, "List done tasks", &lambda {|node=nil|
      connect
      nodes = Proxmox::Node.all
      nodes = nodes.select {|n| node == n.name }  if node
      nodes.flat_map do |n|
        n.tasks.map &:upid
      end.sort.each {|upid| puts upid }
    }

    tcli.cmd :get, "Inspect a task", &lambda {|upid|
      connect
      Proxmox::Node.all.each do |n|
        n.tasks.each do |t|
          next  unless t.upid == upid
          puts t.upid
          t.log( start: 0, limit: 1024).each {|l| puts l[:t] }
          return
        end
      end
    }
  end
end

#complete_lxc(f) ⇒ Object



168
169
170
171
172
# File 'lib/pve/cli.rb', line 168

def complete_lxc f
  Proxmox::LXC.all.
    flat_map {|x| [x.name, x.vmid.to_s] }.
    select {|x| f =~ x }
end

#complete_node(f) ⇒ Object



180
181
182
183
184
# File 'lib/pve/cli.rb', line 180

def complete_node f
  Proxmox::Qemu.all.
    map {|x| x.name }.
    select {|x| f =~ x }
end

#complete_qemu(f) ⇒ Object



174
175
176
177
178
# File 'lib/pve/cli.rb', line 174

def complete_qemu f
  Proxmox::Qemu.all.
    flat_map {|x| [x.name, x.vmid.to_s] }.
    select {|x| f =~ x }
end

#completion_helper(*pre, arg, &exe) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
# File 'lib/pve/cli.rb', line 186

def completion_helper *pre, arg, &exe
  if pre.empty?
    connect
    xs = yield /\A#{Regexp.quote arg}/
    STDOUT.print "\a"  if xs.empty?
    xs
  else
    STDOUT.print "\a"
    []
  end
end

#connectObject



31
32
33
34
35
36
37
# File 'lib/pve/cli.rb', line 31

def connect
  @conn ||=
    Proxmox.connect cfg[:auth][:username], cfg[:auth][:password],
      realm: cfg[:auth][:realm], **cfg[:connect]
  #RestClient.log = STDERR
  @conn
end

#create(klass, template, timeout: nil, fire: nil, secs: nil, start: nil, **options) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/pve/cli.rb', line 136

def create klass, template, timeout: nil, fire: nil, secs: nil, start: nil, **options
  options[:start] = fire && start
  task = klass.create template, **options
  return  if fire
  status = wait task, text: "Creating"
  if status.successfull?
    host = task.host.refresh!
    start host, timeout: timeout, secs: secs  if start
  elsif not interactive?
    exit 1
  end
end

#destroy(ct, timeout: nil, fire: nil, secs: nil) ⇒ Object



149
150
151
152
153
154
# File 'lib/pve/cli.rb', line 149

def destroy ct, timeout: nil, fire: nil, secs: nil
  task = ct.destroy
  unless fire
    wait task, text: "Destroying"
  end
end

#enter(host, *args) ⇒ Object



53
54
55
56
# File 'lib/pve/cli.rb', line 53

def enter host, *args
  r = host.enter *args
  STDERR.puts "! #{$?.exitstatus}"  unless r
end

#help(cl, *args) ⇒ Object



156
157
158
159
# File 'lib/pve/cli.rb', line 156

def help cl, *args
  STDERR.puts cl.help( *args)
  exit 1  unless interactive?
end

#interactive?Boolean

Returns:

  • (Boolean)


49
50
51
# File 'lib/pve/cli.rb', line 49

def interactive?
  @interactive
end

#opts_ha(cl) ⇒ Object



2
3
4
5
6
7
8
9
# File 'lib/pve/cli/ha.rb', line 2

def opts_ha cl
  cl.
    opt( :group, "-gGROUP", "--group=GROUP", "Put host in GROUP", default: 'all').
    opt( :comment, "-cCOMMENT", "--comment=COMMENT", "Set comment").
    opt( :max_relocate, "-lCOUNT", "--max-relocate=COUNT", "How often host can be relocate before givingup?", default: 2).
    opt( :max_restart, "-rCOUNT", "--max-restart=COUNT", "How often host can be restarted before givingup?", default: 2).
    opt( :state, "-sSTATE", "--state=STATE", "Host should have STATE.  If you start/stop be `pct/qm/e start/stop` STATE will be set before action.", default: "started")
end

#opts_wait(cl) ⇒ Object



161
162
163
164
165
166
# File 'lib/pve/cli.rb', line 161

def opts_wait cl
  cl.
    opt( :timeout, "-t", "--timeout=TIMEOUT", "Wait for max TIMEOUT seconds (default: endless)", default: nil).
    opt( :secs, "-s", "--seconds=SECONDS", "Check every SECONDS for state (default: 0.2)", default: 0.2).
    opt( :fire, "-f", "--[no-]fire", "Do not wait till running", default: false)
end

#prepareObject



198
199
200
201
202
203
204
205
206
# File 'lib/pve/cli.rb', line 198

def prepare
  cli_node
  cli_ct
  cli_qm
  cli_task
  cli_ha
  cli_base
  cli_storage
end

#prepare_show_config(cnf) ⇒ Object



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/pve/cli/base.rb', line 39

def prepare_show_config cnf
  r = {}
  cnf.each do |k,v|
    case k
    when :network
      v.each do |net|
        s =
          net.
            reject {|k, v| :card == k }.
            sort_by {|k, v| :name == k ? :AAAAAAAAA : k }.
            map {|k, v| case v when true then [k,1] when false then [k,0] else [k,v] end }.
            map {|k, v| "#{k}=#{v}" }
        r[net[:card].to_sym] = s.join(",")
      end
    when :sshkeys
      r[k] = CGI.unescape(v).gsub( /^/, " "*14).gsub /\A {14}|\n\z/, ''
    else
      case v
      when true then v = 1
      when false then v = 0
      end
      r[k] = v.to_s.gsub( /$^/, " "*14).gsub /\n\z/, ''
    end
  end
  r
end

#show_config(cnf, old = nil) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/pve/cli/base.rb', line 66

def show_config cnf, old = nil
  cnf = prepare_show_config cnf
  if old
    old = prepare_show_config old
    (cnf.keys+old.keys).uniq.sort.each do |k|
      v, o = cnf[k], old[k]
      if v == o
        puts "#{k}:#{' ' * (12-k.length)} #{v}"
      else
        puts "\e[31m#{k}:#{' ' * (12-k.length)} #{o}\e[0m"  unless o.nil?
        puts "\e[32m#{k}:#{' ' * (12-k.length)} #{v}\e[0m"  unless v.nil?
      end
    end
  else
    cnf.sort_by{|k,v|k}.each do |k,v|
      puts "#{k}:#{' ' * (12-k.length)} #{v}"
    end
  end
end

#start(host, node: nil, timeout: nil, fire: nil, secs: nil) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/pve/cli.rb', line 110

def start host, node: nil, timeout: nil, fire: nil, secs: nil
  timeout ||= 30
  if node
    task = host.migrate Proxmox::Node.find_by_name!( node)
    wait task, text: "Migrating"
  end
  if host.running?
    STDERR.puts "Already running."
    return
  end
  task = host.start
  wait task, text: "Starting"  unless fire
end

#stop(host, timeout: nil, fire: nil, secs: nil) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
# File 'lib/pve/cli.rb', line 124

def stop host, timeout: nil, fire: nil, secs: nil
  timeout ||= 30
  if host.stopped?
    STDERR.puts "Already stopped."
    return
  end
  task = host.stop
  unless fire
    wait task, text: "Stopping"
  end
end

#task_log(task, logn, limit = 1024) ⇒ Object



70
71
72
73
74
75
76
77
78
79
# File 'lib/pve/cli.rb', line 70

def task_log task, logn, limit = 1024
  log = task.log start: logn, limit: limit
  log = []  if [{n: 1, t: 'no content'}] == log
  unless log.empty?
    STDERR.printf "\r\e[J"
    log.each {|l| puts l[:t] }
    logn = log.last[:n]
  end
  logn
end

#wait(task, secs: nil, text: nil) ⇒ Object



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
# File 'lib/pve/cli.rb', line 81

def wait task, secs: nil, text: nil
  secs ||= 0.1
  spinners, spin, logn = "▖▘▝▗", 0, 0
  STDERR.puts task.upid
  host = task.host&.name
  loop do
    s = task.status
    logn = self.task_log task, logn
    if s.finished?
      loop do
        r = self.task_log task, logn
        break  if 0 == logn - r
        logn = r
      end
      STDERR.printf "\r[%s] %s %s %s\e[J\n",
        host || s.id,
        s.successfull? ? "\e[32;1m✓\e[0m" : "\e[31;1m✗\e[0m",
        text && "#{text}:",
        s.stopped? ? :finished : s.status
      return s
    end
    STDERR.printf "\r[%s] \e[33;1m%s\e[0m %s...\e[J",
      host || s[:id],
      spinners[spin = (spin + 1) % 4],
      text || "Working"
    sleep secs
  end
end

#wait_state(host, state, timeout: nil, lock: nil) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
# File 'lib/pve/cli.rb', line 58

def wait_state host, state, timeout: nil, lock: nil
  spinners = %w[- / | \\]
  spin = 0
  r =
    host.wait state, lock: lock, timeout: timeout, secs: 0.2 do |state, lock|
      lock = " (locked: #{lock})"  if lock
      STDERR.printf "\r[%s] %s Still %s%s...\e[J\n", host.name, spinners[spin = (spin + 1) % 4], state, lock
    end
  STDERR.printf "\r\e[J"
  exit 1  unless interactive?  and  r
end