Class: Pvectl::Commands::Service

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

Overview

Top-level ‘pvectl service` command for managing systemd services on Proxmox nodes.

Exposes four lifecycle sub-commands:

  • ‘service start <name>` start a service

  • ‘service stop <name>` stop a service (irreversible — requires –yes or prompt)

  • ‘service restart <name>` hard restart a service (irreversible — requires –yes or prompt)

  • ‘service reload <name>` reload (graceful where supported)

Each sub-command requires ‘–node NODE` (falls back to default-node from the active context configuration when not provided).

Examples:

Register with the CLI

Commands::Service.register(cli)

Constant Summary collapse

CONFIRMABLE_OPERATIONS =

All confirmable operations (stop and restart can disrupt running workloads).

%i[stop restart].freeze
OPERATIONS =

All supported operations.

%i[start stop restart reload].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(operation, args, options, global_options, prompt: $stdin, output: $stdout) ⇒ Service

Initializes a service lifecycle command.

Parameters:

  • operation (Symbol)

    operation

  • args (Array<String>)

    positional CLI arguments

  • options (Hash)

    command options

  • global_options (Hash)

    global CLI options

  • prompt (IO) (defaults to: $stdin)

    IO for confirmation prompts (default: $stdin)

  • output (IO) (defaults to: $stdout)

    IO for output (default: $stdout)



163
164
165
166
167
168
169
170
# File 'lib/pvectl/commands/service.rb', line 163

def initialize(operation, args, options, global_options, prompt: $stdin, output: $stdout)
  @operation = operation
  @args = Array(args).compact
  @options = options
  @global_options = global_options
  @prompt = prompt
  @output = output
end

Class Method Details

.execute(operation, args, options, global_options) ⇒ Integer

Executes the command.

Parameters:

  • operation (Symbol)

    operation (:start, :stop, :restart, :reload)

  • args (Array<String>)

    positional CLI arguments

  • options (Hash)

    command options

  • global_options (Hash)

    global CLI options

Returns:

  • (Integer)

    exit code



151
152
153
# File 'lib/pvectl/commands/service.rb', line 151

def self.execute(operation, args, options, global_options)
  new(operation, args, options, global_options).execute
end

.register(cli) ⇒ void

This method returns an undefined value.

Registers the service command and all sub-commands with the CLI.

Parameters:

  • cli (GLI::App)

    the CLI application object



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

def self.register(cli)
  cli.desc "Manage systemd services on Proxmox nodes"
  cli.long_desc <<~HELP
    Manage systemd services running on Proxmox VE nodes. Wraps the Proxmox
    /nodes/{node}/services API to start, stop, restart, or reload daemons
    such as pveproxy, pvedaemon, corosync, and others.

    SUB-COMMANDS
      service start NAME      Start a stopped service
      service stop NAME       Stop a running service (irreversible)
      service restart NAME    Hard restart a service (irreversible)
      service reload NAME     Reload a service (graceful where supported)

    EXAMPLES
      Restart the API proxy on a single node (with confirmation skipped):
        $ pvectl service restart pveproxy --node pve1 --yes

      Start a stopped service on the default node:
        $ pvectl service start cron

      Stop a service after interactive confirmation:
        $ pvectl service stop pve-firewall --node pve1

      Reload the syslog daemon (no interruption to running workloads):
        $ pvectl service reload syslog --node pve1

    NOTES
      --node defaults to the context's default-node if configured.

      stop and restart are irreversible and require either an interactive
      "yes" confirmation or the --yes flag to skip the prompt.

      Restarting pveproxy or corosync can momentarily disconnect the
      current API session and break cluster membership respectively. Use
      --yes only when you understand the impact.

      Operations are asynchronous — the result includes the Proxmox task
      UPID which can be inspected with `pvectl get tasks` and
      `pvectl logs task UPID`.

    SEE ALSO
      pvectl help get             List resources (try `get services`)
      pvectl help logs            Inspect task output
  HELP
  cli.command :service do |c|
    # Shared flags declared on the parent so all subcommands inherit them.
    # Avoids GLI flag-redefinition errors when the same flag is needed by
    # multiple sibling subcommands (e.g. start/stop/restart/reload all
    # need --node and --yes).
    c.desc "Node name (defaults to context default-node)"
    c.flag [:node], arg_name: "NODE"

    c.desc "Skip interactive confirmation prompt"
    c.switch [:yes, :y], negatable: false

    OPERATIONS.each do |op|
      register_subcommand(c, op)
    end
  end
end

.register_subcommand(parent, operation) ⇒ void

This method returns an undefined value.

Registers a single lifecycle sub-command.

Parameters:

  • parent (GLI::Command)

    parent service command

  • operation (Symbol)

    one of :start, :stop, :restart, :reload



98
99
100
101
102
103
104
105
106
107
108
# File 'lib/pvectl/commands/service.rb', line 98

def self.register_subcommand(parent, operation)
  parent.desc "#{operation.capitalize} a systemd service on a Proxmox node"
  parent.long_desc subcommand_long_desc(operation)
  parent.arg_name "SERVICE_NAME"
  parent.command operation do |sub|
    sub.action do |global_options, options, args|
      exit_code = execute(operation, args, options, global_options)
      exit exit_code if exit_code != 0
    end
  end
end

.subcommand_long_desc(operation) ⇒ String

Builds the man-page-style long_desc for a sub-command.

Parameters:

  • operation (Symbol)

    operation

Returns:

  • (String)

    long help text



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

def self.subcommand_long_desc(operation)
  action = operation.to_s
  confirm_note =
    if CONFIRMABLE_OPERATIONS.include?(operation)
      "This operation is irreversible. Without --yes, pvectl will\n  prompt for interactive confirmation before contacting the API."
    else
      "No confirmation is required for this operation."
    end

  <<~HELP
    #{action.capitalize} a systemd service on a Proxmox node.

    EXAMPLES
      $ pvectl service #{action} pveproxy --node pve1
      $ pvectl service #{action} pveproxy --node pve1 --yes

    NOTES
      #{confirm_note}

      Restarting pveproxy or corosync can momentarily disconnect the
      current API session and break cluster membership respectively.

      --node defaults to the context's default-node.

    SEE ALSO
      pvectl help service     Parent command
      pvectl help get         List resources (try `get services`)
  HELP
end

Instance Method Details

#executeInteger

Executes the command.

Returns:

  • (Integer)

    exit code



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/pvectl/commands/service.rb', line 175

def execute
  return usage_error("service name is required") if @args.empty?

  service_name = @args.first
  load_config
  node = resolve_node
  return config_error("node is required (provide --node or configure default-node)") unless node

  return ExitCodes::SUCCESS unless confirm!(service_name, node)

  result = perform(service_name, node)
  output_result(result)
  result.failed? ? ExitCodes::GENERAL_ERROR : ExitCodes::SUCCESS
rescue Pvectl::Config::ConfigNotFoundError,
       Pvectl::Config::InvalidConfigError,
       Pvectl::Config::ContextNotFoundError,
       Pvectl::Config::ClusterNotFoundError,
       Pvectl::Config::UserNotFoundError => e
  $stderr.puts "Error: #{e.message}"
  ExitCodes::CONFIG_ERROR
rescue StandardError => e
  $stderr.puts "Error: #{e.message}"
  ExitCodes::GENERAL_ERROR
end