Class: Pvectl::Presenters::Container

Inherits:
Base
  • Object
show all
Defined in:
lib/pvectl/presenters/container.rb,
sig/pvectl/presenters/container.rbs

Overview

Presenter for LXC containers.

Defines column layout and formatting for table output. Used by formatters to render container data in various formats.

Standard columns: NAME, CTID, STATUS, NODE, CPU, MEMORY Wide columns add: UPTIME, TEMPLATE, TAGS, SWAP, DISK, NETIN, NETOUT, POOL

Description output is organized by Proxmox VE web UI tabs: Summary, Resources, Network, DNS, Options, Task History, Snapshots, HA.

Examples:

Using with formatter

presenter = Container.new
formatter = Formatters::Table.new
output = formatter.format(containers, presenter)

See Also:

Direct Known Subclasses

TopContainer

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#format_bytes, #format_firewall, #format_firewall_rules, #format_task_history, #tags_array, #tags_display, #template_display, #to_wide_row, #uptime_human, #wide_columns

Instance Attribute Details

#containerModels::Container (readonly) Also known as: resource

Returns current container model.

Returns:



302
303
304
# File 'lib/pvectl/presenters/container.rb', line 302

def container
  @container
end

Instance Method Details

#columnsArray<String>

Returns column headers for standard table output.

Returns:

  • (Array<String>)

    column headers



30
31
32
# File 'lib/pvectl/presenters/container.rb', line 30

def columns
  %w[NAME CTID STATUS NODE CPU MEMORY]
end

#consume(*keys) ⇒ void

This method returns an undefined value.

Registers config keys as consumed by a format method.

Parameters:

  • keys (Array<Symbol>)

    config keys to mark as consumed



547
548
549
# File 'lib/pvectl/presenters/container.rb', line 547

def consume(*keys)
  @consumed_keys.merge(keys.map(&:to_sym))
end

#consume_matching(config, pattern) ⇒ void

This method returns an undefined value.

Consumes all config keys matching a pattern.

Parameters:

  • config (Hash)

    config hash

  • pattern (Regexp)

    pattern to match key names



556
557
558
# File 'lib/pvectl/presenters/container.rb', line 556

def consume_matching(config, pattern)
  config.keys.select { |k| k.to_s.match?(pattern) }.each { |k| consume(k) }
end

#cpu_percentString

Returns CPU usage as percentage string.

For running containers, shows actual usage percentage. For stopped containers, shows "-" for usage but includes core count if available.

Returns:

  • (String)

    CPU percentage (e.g., "12%/4") or "-/4" for stopped containers



182
183
184
185
186
187
188
# File 'lib/pvectl/presenters/container.rb', line 182

def cpu_percent
  return "-" if container.maxcpu.nil?
  return "-/#{container.maxcpu}" unless container.running?
  return "-/#{container.maxcpu}" if container.cpu.nil?

  "#{(container.cpu * 100).round}%/#{container.maxcpu}"
end

#disk_displayString

Returns disk formatted as "used/total GiB".

Returns:

  • (String)

    formatted disk (e.g., "15.0/50.0 GiB") or "-" if unavailable



272
273
274
275
276
# File 'lib/pvectl/presenters/container.rb', line 272

def disk_display
  return "-" if disk_used_gib.nil?

  "#{disk_used_gib}/#{disk_total_gib} GiB"
end

#disk_total_gibFloat?

Returns total disk in GiB.

Returns:

  • (Float, nil)

    total disk in GiB, or nil if unavailable



263
264
265
266
267
# File 'lib/pvectl/presenters/container.rb', line 263

def disk_total_gib
  return nil if container.maxdisk.nil?

  (container.maxdisk.to_f / 1024 / 1024 / 1024).round(1)
end

#disk_used_gibFloat?

Returns disk used in GiB.

Returns:

  • (Float, nil)

    disk used in GiB, or nil if unavailable



254
255
256
257
258
# File 'lib/pvectl/presenters/container.rb', line 254

def disk_used_gib
  return nil if container.disk.nil?

  (container.disk.to_f / 1024 / 1024 / 1024).round(1)
end

#display_nameString

Returns display name, falling back to "CT-ctid" if name is nil.

Returns:

  • (String)

    display name



172
173
174
# File 'lib/pvectl/presenters/container.rb', line 172

def display_name
  container.name || "CT-#{container.vmid}"
end

#extra_columnsArray<String>

Returns additional column headers for wide output.

Returns:

  • (Array<String>)

    extra column headers



37
38
39
# File 'lib/pvectl/presenters/container.rb', line 37

def extra_columns
  %w[UPTIME TEMPLATE TAGS SWAP DISK NETIN NETOUT POOL]
end

#extra_values(model, **_context) ⇒ Array<String>

Returns additional values for wide output.

Parameters:

Returns:

  • (Array<String>)

    extra values matching extra_columns order



63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/pvectl/presenters/container.rb', line 63

def extra_values(model, **_context)
  @container = model
  [
    uptime_human,
    template_display,
    tags_display,
    swap_display,
    disk_display,
    netin_display,
    netout_display,
    pool_display
  ]
end

#format_dns(config) ⇒ Hash, String

Formats DNS section.

Parameters:

  • config (Hash)

    container config

Returns:

  • (Hash, String)

    DNS info or "-"



466
467
468
469
470
471
472
473
# File 'lib/pvectl/presenters/container.rb', line 466

def format_dns(config)
  consume(:nameserver, :searchdomain)
  ns = config[:nameserver]
  sd = config[:searchdomain]
  return "-" if ns.nil? && sd.nil?

  { "Nameserver" => ns || "-", "Search Domain" => sd || "-" }
end

#format_features(config = {}) ⇒ String

Formats features for display.

Parameters:

  • config (Hash) (defaults to: {})

    raw config hash for key consumption

Returns:

  • (String)

    formatted features or "-"



423
424
425
426
427
428
429
430
431
432
433
# File 'lib/pvectl/presenters/container.rb', line 423

def format_features(config = {})
  consume(:features)
  features_str = container.features || config[:features]
  return "-" if features_str.nil? || features_str.empty?

  # Parse "nesting=1,keyctl=1" to "nesting, keyctl"
  features_str.split(",").map do |f|
    key, value = f.split("=")
    value == "1" ? key : nil
  end.compact.join(", ")
end

#format_haHash

Formats High Availability section.

Returns:

  • (Hash)

    HA info



533
534
535
536
537
538
539
540
541
# File 'lib/pvectl/presenters/container.rb', line 533

def format_ha
  ha = container.ha
  return { "State" => "-", "Group" => "-" } if ha.nil?

  {
    "State" => ha[:managed] == 1 ? "managed" : "-",
    "Group" => ha[:group] || "-"
  }
end

#format_mountpoints(config) ⇒ Array<Hash>, String

Formats mountpoints section (mp0-mp255).

Parameters:

  • config (Hash)

    container config

Returns:

  • (Array<Hash>, String)

    mountpoints table or "-"



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/pvectl/presenters/container.rb', line 439

def format_mountpoints(config)
  mp_keys = config.keys.select { |k| k.to_s.match?(/^mp\d+$/) }
  consume_matching(config, /^mp\d+$/)
  consume_matching(config, /^unused\d+$/)
  return "-" if mp_keys.empty?

  mp_keys.sort.map do |key|
    parts = config[key].to_s.split(",")
    storage_part = parts.first
    storage = storage_part.include?(":") ? storage_part.split(":").first : storage_part
    mp_path = nil
    size = nil
    parts[1..].each do |part|
      k, v = part.split("=", 2)
      case k
      when "mp" then mp_path = v
      when "size" then size = v
      end
    end
    { "NAME" => key.to_s, "PATH" => mp_path || "-", "STORAGE" => storage, "SIZE" => size || "-" }
  end
end

#format_network_interfaces(config = {}) ⇒ Array<Hash>, String

Formats network interfaces for table display.

Parameters:

  • config (Hash) (defaults to: {})

    raw config hash for key consumption

Returns:

  • (Array<Hash>, String)

    network interfaces or "-"



404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/pvectl/presenters/container.rb', line 404

def format_network_interfaces(config = {})
  consume_matching(config, /^net\d+$/)
  interfaces = container.network_interfaces
  return "-" if interfaces.nil? || interfaces.empty?

  interfaces.map do |iface|
    {
      "NAME" => iface[:name] || "-",
      "BRIDGE" => iface[:bridge] || "-",
      "IP" => iface[:ip] || "-",
      "MAC" => iface[:hwaddr] || "-"
    }
  end
end

#format_options(config) ⇒ Hash

Formats Options section (PVE Options tab).

Shows boot, startup, OS type, architecture, security, and other container options.

Parameters:

  • config (Hash)

    container config

Returns:

  • (Hash)

    options info



482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
# File 'lib/pvectl/presenters/container.rb', line 482

def format_options(config)
  consume(:onboot, :startup, :ostype, :arch, :unprivileged,
          :features, :cmode, :tty, :protection, :lock, :hookscript)

  on_boot = config[:onboot] == 1 ? "Yes" : "No"
  startup = config[:startup]
  startup_display = startup ? startup.to_s : "-"
  ostype = container.ostype || config[:ostype] || "-"
  arch = container.arch || config[:arch] || "-"
  unpriv = container.unprivileged? ? "Yes" : "No"
  features = format_features(config)
  cmode = config[:cmode] || "tty"
  tty_count = (config[:tty] || 2).to_s
  protection = config[:protection] == 1 ? "Yes" : "No"
  hookscript = config[:hookscript] || "-"

  {
    "Start at Boot" => on_boot,
    "Startup Order" => startup_display,
    "OS Type" => ostype,
    "Architecture" => arch,
    "Unprivileged" => unpriv,
    "Features" => features,
    "Console Mode" => cmode,
    "TTY" => tty_count,
    "Protection" => protection,
    "Hookscript" => hookscript
  }
end

#format_remaining(config) ⇒ Array<Hash>, String

Formats remaining unconsumed config keys as catch-all table.

Parameters:

  • config (Hash)

    full config hash

Returns:

  • (Array<Hash>, String)

    remaining keys table or "-"



564
565
566
567
568
569
570
# File 'lib/pvectl/presenters/container.rb', line 564

def format_remaining(config)
  excluded = %i[digest]
  remaining = config.keys.map(&:to_sym) - @consumed_keys.to_a - excluded
  return "-" if remaining.empty?

  remaining.sort.map { |k| { "KEY" => k.to_s, "VALUE" => config[k].to_s } }
end

#format_resources(config) ⇒ Hash

Formats Resources section (PVE Resources tab).

Shows configured memory, swap, cores, rootfs, and mountpoints.

Parameters:

  • config (Hash)

    raw config hash for key consumption

Returns:

  • (Hash)

    resources info



360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/pvectl/presenters/container.rb', line 360

def format_resources(config)
  consume(:memory, :swap, :cores, :rootfs)

  mem_mb = config[:memory]
  memory_str = if mem_mb
                 "#{(mem_mb.to_f / 1024).round(2)} GiB"
               else
                 container.maxmem ? format_bytes(container.maxmem) : "-"
               end

  swap_mb = config[:swap]
  swap_str = if swap_mb
               "#{swap_mb} MiB"
             else
               container.maxswap ? format_bytes(container.maxswap) : "-"
             end

  {
    "Memory" => memory_str,
    "Swap" => swap_str,
    "Cores" => (config[:cores] || container.maxcpu || "-").to_s,
    "Root Filesystem" => format_rootfs(config),
    "Mountpoints" => format_mountpoints(config)
  }
end

#format_rootfs(config = {}) ⇒ Hash

Formats rootfs section.

Parameters:

  • config (Hash) (defaults to: {})

    raw config hash for key consumption

Returns:

  • (Hash)

    rootfs info



390
391
392
393
394
395
396
397
398
# File 'lib/pvectl/presenters/container.rb', line 390

def format_rootfs(config = {})
  size_gib = disk_total_gib ? "#{disk_total_gib} GiB" : "-"
  used_gib = disk_used_gib ? "#{disk_used_gib} GiB" : "-"

  {
    "Size" => size_gib,
    "Used" => used_gib
  }
end

#format_snapshots(snapshots) ⇒ Array<Hash>, String

Formats snapshots section.

Parameters:

  • snapshots (Array<Hash>, nil)

    snapshots from API

Returns:

  • (Array<Hash>, String)

    snapshots table or "No snapshots"



516
517
518
519
520
521
522
523
524
525
526
527
528
# File 'lib/pvectl/presenters/container.rb', line 516

def format_snapshots(snapshots)
  return "No snapshots" if snapshots.nil? || snapshots.empty?

  snapshots.map do |snap|
    snaptime = snap[:snaptime]
    date = snaptime ? Time.at(snaptime).strftime("%Y-%m-%d %H:%M:%S") : "-"
    {
      "NAME" => snap[:name],
      "DATE" => date,
      "DESCRIPTION" => snap[:description] || "-"
    }
  end
end

#format_summaryHash

Formats Summary section (PVE Summary tab).

Shows resource usage and (for running containers) runtime info including uptime, PID, and network I/O statistics.

Returns:

  • (Hash)

    summary info



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/pvectl/presenters/container.rb', line 311

def format_summary
  cpu_usage = if container.running? && container.cpu
                "#{(container.cpu * 100).round(2)}% of #{container.maxcpu || '-'} core(s)"
              else
                "-"
              end

  mem_usage = if container.running? && container.mem && container.maxmem && container.maxmem > 0
                pct = ((container.mem.to_f / container.maxmem) * 100).round(2)
                "#{pct}% (#{format_bytes(container.mem)} of #{format_bytes(container.maxmem)})"
              else
                "-"
              end

  swap_usage = if container.running? && container.swap && container.maxswap && container.maxswap > 0
                 "#{format_bytes(container.swap)} / #{format_bytes(container.maxswap)}"
               else
                 "-"
               end

  rootfs_usage = if container.disk && container.maxdisk && container.maxdisk > 0
                   "#{format_bytes(container.disk)} / #{format_bytes(container.maxdisk)}"
                 else
                   "-"
                 end

  result = {
    "CPU Usage" => cpu_usage,
    "Memory Usage" => mem_usage,
    "Swap Usage" => swap_usage,
    "Root FS Usage" => rootfs_usage
  }

  if container.running?
    result["Uptime"] = uptime_human
    result["PID"] = (container.pid || "-").to_s
    result["Network In"] = format_bytes(container.netin)
    result["Network Out"] = format_bytes(container.netout)
  end

  result
end

#memory_displayString

Returns memory formatted as "used/total GiB".

For running containers, shows actual usage and total. For stopped containers, shows "-" for usage but includes total if available.

Returns:

  • (String)

    formatted memory (e.g., "2.1/4.0 GiB") or "-/4.0 GiB" for stopped



214
215
216
217
218
219
220
# File 'lib/pvectl/presenters/container.rb', line 214

def memory_display
  return "-" if memory_total_gib.nil?
  return "-/#{memory_total_gib} GiB" unless container.running?
  return "-/#{memory_total_gib} GiB" if memory_used_gib.nil?

  "#{memory_used_gib}/#{memory_total_gib} GiB"
end

#memory_total_gibFloat?

Returns total memory in GiB.

Returns:

  • (Float, nil)

    total memory in GiB, or nil if unavailable



202
203
204
205
206
# File 'lib/pvectl/presenters/container.rb', line 202

def memory_total_gib
  return nil if container.maxmem.nil?

  (container.maxmem.to_f / 1024 / 1024 / 1024).round(1)
end

#memory_used_gibFloat?

Returns memory used in GiB.

Returns:

  • (Float, nil)

    memory used in GiB, or nil if unavailable



193
194
195
196
197
# File 'lib/pvectl/presenters/container.rb', line 193

def memory_used_gib
  return nil if container.mem.nil?

  (container.mem.to_f / 1024 / 1024 / 1024).round(1)
end

#netin_displayString

Returns network in bytes formatted.

Returns:

  • (String)

    formatted network in (e.g., "117.7 MiB") or "-"



288
289
290
# File 'lib/pvectl/presenters/container.rb', line 288

def netin_display
  format_bytes(container.netin)
end

#netout_displayString

Returns network out bytes formatted.

Returns:

  • (String)

    formatted network out (e.g., "941.9 MiB") or "-"



295
296
297
# File 'lib/pvectl/presenters/container.rb', line 295

def netout_display
  format_bytes(container.netout)
end

#pool_displayString

Returns pool display string.

Returns:

  • (String)

    pool name or "-" if no pool



281
282
283
# File 'lib/pvectl/presenters/container.rb', line 281

def pool_display
  container.pool || "-"
end

#swap_displayString

Returns swap formatted as "used/total MiB".

Returns:

  • (String)

    formatted swap (e.g., "128/512 MiB") or "-" if unavailable



243
244
245
246
247
248
249
# File 'lib/pvectl/presenters/container.rb', line 243

def swap_display
  return "-" if swap_total_mib.nil? || swap_total_mib.zero?
  return "-/#{swap_total_mib.round} MiB" unless container.running?
  return "-/#{swap_total_mib.round} MiB" if swap_used_mib.nil?

  "#{swap_used_mib.round}/#{swap_total_mib.round} MiB"
end

#swap_total_mibFloat?

Returns total swap in MiB.

Returns:

  • (Float, nil)

    total swap in MiB, or nil if unavailable



234
235
236
237
238
# File 'lib/pvectl/presenters/container.rb', line 234

def swap_total_mib
  return nil if container.maxswap.nil?

  (container.maxswap.to_f / 1024 / 1024).round(1)
end

#swap_used_mibFloat?

Returns swap used in MiB.

Returns:

  • (Float, nil)

    swap used in MiB, or nil if unavailable



225
226
227
228
229
# File 'lib/pvectl/presenters/container.rb', line 225

def swap_used_mib
  return nil if container.swap.nil?

  (container.swap.to_f / 1024 / 1024).round(1)
end

#to_description(model) ⇒ Hash

Converts Container model to description format for describe command.

Returns a structured Hash organized by Proxmox VE web UI tabs: Summary, Resources, Network, DNS, Options, Task History, Snapshots, High Availability. Nested Hashes create indented subsections. Arrays of Hashes render as inline tables.

Parameters:

Returns:

  • (Hash)

    structured hash for describe formatter



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
# File 'lib/pvectl/presenters/container.rb', line 136

def to_description(model)
  @container = model
  @consumed_keys = Set.new
  data = container.describe_data || {}
  config = data[:config] || {}

  consume(:hostname, :description, :tags, :pool, :template, :lxc)

  {
    "Name" => display_name,
    "CTID" => container.vmid,
    "Status" => container.status,
    "Node" => container.node,
    "Tags" => tags_display,
    "Description" => container.description || config[:description] || "-",
    "Summary" => format_summary,
    "Resources" => format_resources(config),
    "Network" => format_network_interfaces(config),
    "DNS" => format_dns(config),
    "Options" => format_options(config),
    "Firewall" => format_firewall(data[:firewall]),
    "Firewall Rules" => format_firewall_rules(data[:firewall]),
    "Task History" => format_task_history(data[:tasks]),
    "Snapshots" => format_snapshots(data[:snapshots]),
    "High Availability" => format_ha,
    "Additional Configuration" => format_remaining(config)
  }
end

#to_hash(model) ⇒ Hash

Converts Container model to hash for JSON/YAML output.

Returns a structured hash with nested objects for complex data like CPU, memory, swap, disk, uptime, and network.

Parameters:

Returns:

  • (Hash)

    hash representation with string keys



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
# File 'lib/pvectl/presenters/container.rb', line 84

def to_hash(model)
  @container = model
  {
    "ctid" => container.vmid,
    "name" => container.name,
    "status" => container.status,
    "node" => container.node,
    "template" => container.template?,
    "pool" => container.pool,
    "cpu" => {
      "usage_percent" => container.cpu.nil? ? nil : (container.cpu * 100).round,
      "cores" => container.maxcpu
    },
    "memory" => {
      "used_gib" => memory_used_gib,
      "total_gib" => memory_total_gib,
      "used_bytes" => container.mem,
      "total_bytes" => container.maxmem
    },
    "swap" => {
      "used_mib" => swap_used_mib,
      "total_mib" => swap_total_mib,
      "used_bytes" => container.swap,
      "total_bytes" => container.maxswap
    },
    "disk" => {
      "used_gib" => disk_used_gib,
      "total_gib" => disk_total_gib,
      "used_bytes" => container.disk,
      "total_bytes" => container.maxdisk
    },
    "uptime" => {
      "seconds" => container.uptime,
      "human" => uptime_human
    },
    "network" => {
      "in_bytes" => container.netin,
      "out_bytes" => container.netout
    },
    "tags" => tags_array
  }
end

#to_row(model, **_context) ⇒ Array<String>

Converts Container model to table row values.

Parameters:

Returns:

  • (Array<String>)

    row values matching columns order



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/pvectl/presenters/container.rb', line 46

def to_row(model, **_context)
  @container = model
  [
    display_name,
    container.vmid.to_s,
    container.status,
    container.node,
    cpu_percent,
    memory_display
  ]
end