exec

package
v1.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ExecCmd = &cobra.Command{
	Use:   "exec [flags] [USER@]SERVER [--] COMMAND...",
	Short: "Execute a command on a remote server",
	Long: `Execute a command on a remote server.

This command executes a specified command on a remote server and returns the output.
It supports SSH-like syntax for specifying the user and server.

Use -- to separate alpacon flags from the remote command, ensuring that flags
intended for the remote command (e.g., -U, -d) are not interpreted as alpacon flags.

All flags must be placed before the server name.

Shell metacharacters (;, |, &, $) pass through unquoted to the remote shell.
To send a literal metacharacter, wrap the argument in quotes:
  alpacon exec server 'echo hello;world'

Flags:
  -u, --username [USER_NAME]    Specify the username for command execution.
  -g, --groupname [GROUP_NAME]  Specify the group name for command execution.
  --env="KEY"                   Pass an environment variable to the remote command,
                                reading its value from the current shell. This keeps
                                the value off the alpacon command line and out of the
                                audit log—use it for secrets such as passwords or tokens.
  --env="KEY=VALUE"             Set 'KEY' to a literal value. Discouraged: the value
                                is written on the command line and recorded verbatim
                                in the audit log. Never pass credentials this way.
  --work-session [UUID]         Attach this command to a work-session.
                                Overrides the workspace's active session set via
                                'alpacon work-session use'.
  --detach                      Submit the command and return immediately without
                                waiting for completion. Prints the job ID to stdout.
                                Use 'alpacon exec logs JOB_ID' to retrieve the result.
  --wait                        When a sudo command needs human approval, block and
                                re-attempt until a reviewer approves it in the Alpacon
                                console (web), or the wait times out.

Exit code 3 indicates a WorkSession gate denial; run with --output json to
parse a machine-readable diagnostic on stderr.
Exit code 4 indicates the sudo command is pending human approval (approve it in
the Alpacon console, then re-run, or pass --wait to block); --output json emits
{"status":"pending_approval", ...} on stdout.
Requires an active WorkSession when using Browser login (Auth0); Token auth (API token or Service token) bypasses this requirement.`,
	Example: `  # Simple command execution
  alpacon exec prod-docker docker ps
  alpacon exec root@prod-docker docker ps

  # Use -- to pass flags to the remote command
  alpacon exec root@db-server -- docker exec postgres psql -U myproject -d myproject
  alpacon exec my-server -- grep -r "pattern" /var/log

  # Specify user and group with flags
  alpacon exec -u root prod-docker systemctl status nginx
  alpacon exec -g docker user@server docker images

  # Pass a secret via the shell env; the value stays off the alpacon command line
  # and out of the audit log. psql reads PGPASSWORD from the environment, so it
  # never lands in the remote process's argv either.
  export PGPASSWORD=hunter2
  alpacon exec --env="PGPASSWORD" db-server -- psql -h localhost -U app -c 'SELECT 1'

  # Submit a command asynchronously and retrieve the result later
  alpacon exec --detach web-server -- apt-get update
  alpacon exec logs <JOB_ID>`,

	DisableFlagParsing: true,
	Run: func(cmd *cobra.Command, args []string) {
		parsed := ParseRemoteExecArgs(args)

		if parsed.ShowHelp {
			_ = cmd.Help()
			return
		}

		if parsed.Err != "" {
			utils.CliErrorWithExit("%s", parsed.Err)
			return
		}

		if parsed.Server == "" {
			_ = cmd.Help()
			utils.CliErrorWithExit("server name is required.")
			return
		}

		if parsed.Command == "" {
			utils.CliErrorWithExit("You must specify a command to execute.")
			return
		}

		RunRemoteExec(parsed)
	},
}

Functions

func HandleCommandResult added in v1.4.8

func HandleCommandResult(err error)

HandleCommandResult exits appropriately on error. Output is streamed to stdout during execution; on a remote failure the error carries that output, used here only to surface the sudo-denial hint (not re-printed).

func HandlePendingApproval added in v1.7.2

func HandlePendingApproval(err error, reRunHint utils.NextAction) bool

HandlePendingApproval emits the structured pending-approval feedback for an exec sudo command that was denied SUDO_APPROVAL_REQUIRED and not waited on, then exits with ExitCodePendingApproval. It reports true when it handled the err; the caller skips its normal result handling on true. The exec denial line carries no approval request id, so the machine signal omits it. reRunHint is the exact command the caller invoked (with any --env caveat in its Description), so a human can copy-paste it once the request is approved.

func ParseEnvArg added in v1.9.0

func ParseEnvArg(arg string, env map[string]string) string

ParseEnvArg parses a --env token into env. A bare KEY reads the shell value, warning and skipping if unset; malformed input returns an error message.

func RunCommandWithRetry added in v1.3.2

func RunCommandWithRetry(ac *client.AlpaconClient, serverName, command, username, groupname string, env map[string]string, workSessionID string, out io.Writer) error

RunCommandWithRetry executes a remote command with MFA/username-required error handling and retry logic, streaming output to out. workSessionID is forwarded as the work_session field; pass "" to omit it.

func RunExecWithApprovalWait added in v1.7.2

func RunExecWithApprovalWait(ac *client.AlpaconClient, serverName, command, username, groupname string, env map[string]string, workSessionID string, wait bool, out io.Writer) error

RunExecWithApprovalWait runs a command via RunExecWithPresenceStepUp and, when it is denied pending human approval (SUDO_APPROVAL_REQUIRED) and wait is set, blocks and re-attempts the command on a fixed interval until a reviewer approves it out of band (the re-run then succeeds or hits a different, terminal denial), or the bounded timeout elapses. When wait is false, or the denial is anything other than SUDO_APPROVAL_REQUIRED, it returns the first err unchanged so the caller's pending/denial handling runs.

Re-attempting the command is the only poll available here: the plugin's denial line carries the denial code but no approval request id, and this credential channel has no approval-status endpoint to query (ADR 0015 moves approval out of band). Re-running is side-effect-safe: a sudo command pending approval is denied by the server and never executes, so each poll tick is a no-op denial until a reviewer approves, at which point the command runs exactly once. The poll mirrors the MFA step-up structure (api/mfa/mfa.go): a spinner, a fixed-interval ticker, and a precise deadline.

func RunExecWithPresenceStepUp added in v1.7.2

func RunExecWithPresenceStepUp(ac *client.AlpaconClient, serverName, command, username, groupname string, env map[string]string, workSessionID string, out io.Writer) error

RunExecWithPresenceStepUp runs a command via RunCommandWithRetry and, when it is denied for a missing recent MFA (SUDO_PRESENCE_REQUIRED) on an interactive terminal, offers an MFA step-up and retries once. Non-interactive callers (scripts, CI, AI agents) fall through unchanged so HandleCommandResult prints the static denial hint; non-interactive humans additionally get the verification link they can complete out of band. Reached via RunRemoteExec by exec and websh command mode; interactive websh keeps its own sudo MFA flow.

func RunRemoteExec added in v1.9.0

func RunRemoteExec(parsed RemoteExecArgs)

RunRemoteExec is the shared post-parse execution path for exec and websh command mode. Requires Server and Command to be non-empty.

func ShellJoin added in v1.4.8

func ShellJoin(parts []string) string

ShellJoin reassembles tokenized command parts into a single string. Parts containing whitespace or single quotes are re-quoted to preserve argument boundaries; metacharacters pass through for the remote shell. A single-element slice is returned as-is (already a formed command line).

Types

type RemoteExecArgs added in v1.3.2

type RemoteExecArgs struct {
	Username      string
	Groupname     string
	WorkSessionID string
	OutputFormat  string
	Server        string
	Command       string
	Env           map[string]string
	Detach        bool
	Wait          bool
	ShowHelp      bool
	Err           string
}

RemoteExecArgs holds parsed arguments for remote command execution.

func ParseRemoteExecArgs added in v1.3.2

func ParseRemoteExecArgs(args []string) RemoteExecArgs

ParseRemoteExecArgs parses raw CLI arguments with manual flag handling. It recognizes -u/--username, -g/--groupname, and -h/--help flags before the server name.

The -- separator has two roles depending on position:

  • After the server name: acts as a command separator. Everything after it is the remote command, with no further flag parsing.
  • Before the server name (e.g., after flags): acts as an end-of-flags marker. The first arg after -- becomes the server, the rest is the command.

Without --, everything after the server name is the remote command.

Layout: [flags] [USER@]SERVER [--] COMMAND...

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL