From d104432cd3ad032baa0283f74c1ae389de672050 Mon Sep 17 00:00:00 2001 From: Phil Renaud Date: Thu, 30 Nov 2023 14:13:37 -0500 Subject: [PATCH] Actions: API, command, and jobspec docs (#19166) * API command and jobspec docs * PR comments addressed * API docs for job/jobid/action socket * Removing a perhaps incorrect origin of job_id across the jobs api doc * PR comments addressed --- command/action.go | 2 - ui/app/adapters/job.js | 4 +- website/content/api-docs/jobs.mdx | 243 +++++++++++++++--- website/content/docs/commands/job/action.mdx | 107 ++++++++ .../content/docs/job-specification/action.mdx | 128 +++++++++ website/data/docs-nav-data.json | 8 + 6 files changed, 452 insertions(+), 40 deletions(-) create mode 100644 website/content/docs/commands/job/action.mdx create mode 100644 website/content/docs/job-specification/action.mdx diff --git a/command/action.go b/command/action.go index 1a4179911..6d4ff6ede 100644 --- a/command/action.go +++ b/command/action.go @@ -10,8 +10,6 @@ import ( "github.com/posener/complete" ) -const defaultEscapeChar = "~" - type ActionCommand struct { Meta diff --git a/ui/app/adapters/job.js b/ui/app/adapters/job.js index 413a40e55..6441d5739 100644 --- a/ui/app/adapters/job.js +++ b/ui/app/adapters/job.js @@ -195,8 +195,8 @@ export default class JobAdapter extends WatchableNamespaceIDs { )}/action` + `?namespace=${job.get('namespace.id')}&action=${ action.name - }&allocID=${allocID}&task=${action.task.name}&group=${ - action.task.taskGroup.name + }&allocID=${allocID}&task=${ + action.task.name }&tty=true&ws_handshake=true` + (region ? `®ion=${region}` : ''); diff --git a/website/content/api-docs/jobs.mdx b/website/content/api-docs/jobs.mdx index 0de05ee2a..a8bea7c5a 100644 --- a/website/content/api-docs/jobs.mdx +++ b/website/content/api-docs/jobs.mdx @@ -329,8 +329,8 @@ The table below shows this endpoint's support for ### Parameters -- `:job_id` `(string: )` - Specifies the ID of the job (as specified in - the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `namespace` `(string: "default")` - Specifies the namespace of the job. If not specified, defaults to "default". This is specified as a URL query parameter. @@ -639,8 +639,8 @@ The table below shows this endpoint's support for - `diffs` `(bool: false)` - Specifies if the Diffs field should be populated, containing the structured diff between the current and last job version. -- `:job_id` `(string: )` - Specifies the ID of the job (as specified in - the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `namespace` `(string: "default")` - Specifies the target namespace. If ACL is enabled, this value must match a namespace that the token is allowed to @@ -1235,8 +1235,8 @@ The table below shows this endpoint's support for ### Parameters -- `:job_id` `(string: )` - Specifies the ID of the job (as specified in - the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `all` `(bool: false)` - Specifies whether the list of allocations should include allocations from a previously registered job with the same ID. This is @@ -1408,8 +1408,8 @@ The table below shows this endpoint's support for ### Parameters -- `:job_id` `(string: )` - Specifies the ID of the job (as specified in - the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `namespace` `(string: "default")` - Specifies the target namespace. If ACL is enabled, this value must match a namespace that the token is allowed to @@ -1473,8 +1473,8 @@ The table below shows this endpoint's support for ### Parameters -- `:job_id` `(string: )` - Specifies the ID of the job (as specified in - the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `all` `(bool: false)` - Specifies whether the list of deployments should include deployments from a previously registered job with the same ID. This is @@ -1565,8 +1565,8 @@ The table below shows this endpoint's support for ### Parameters -- `:job_id` `(string: )` - Specifies the ID of the job (as specified in - the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `namespace` `(string: "default")` - Specifies the target namespace. If ACL is enabled, this value must match a namespace that the token is allowed to @@ -1628,8 +1628,8 @@ The table below shows this endpoint's support for ### Parameters -- `:job_id` `(string: )` - Specifies the ID of the job (as specified in - the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `namespace` `(string: "default")` - Specifies the target namespace. If ACL is enabled, this value must match a namespace that the token is allowed to @@ -1685,8 +1685,8 @@ The table below shows this endpoint's support for ### Parameters -- `:job_id` `(string: )` - Specifies the ID of the job (as specified in - the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `Job` `(Job: )` - Specifies the JSON definition of the job. @@ -1759,8 +1759,8 @@ The table below shows this endpoint's support for ### Parameters -- `:job_id` `(string: )` - Specifies the ID of the job (as specified - in the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `IdempotencyToken` `(string: "")` - Optional identifier used to prevent more than one instance of the job from being dispatched. This is specified as a @@ -1829,8 +1829,8 @@ The table below shows this endpoint's support for ### Parameters -- `JobID` `(string: )` - Specifies the ID of the job (as specified - in the job file during submission). This is specified as part of the path. +- `JobID` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `JobVersion` `(integer: 0)` - Specifies the job version to revert to. @@ -1894,8 +1894,8 @@ The table below shows this endpoint's support for ### Parameters -- `JobID` `(string: )` - Specifies the ID of the job (as specified - in the job file during submission). This is specified as part of the path. +- `JobID` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `JobVersion` `(integer: 0)` - Specifies the job version to set the stability on. @@ -1954,8 +1954,8 @@ The table below shows this endpoint's support for ### Parameters -- `:job_id` `(string: )` - Specifies the ID of the job (as specified in - the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `JobID` `(string: )` - Specify the ID of the job in the JSON payload @@ -2016,8 +2016,8 @@ The table below shows this endpoint's support for ### Parameters -- `:job_id` `(string: )` - Specifies the ID of the job (as specified in -- the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `Job` `(string: )` - Specifies the JSON definition of the job. @@ -2227,8 +2227,8 @@ The table below shows this endpoint's support for ### Parameters -- `:job_id` `(string: )` - Specifies the ID of the job (as specified in - the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. ### Sample Request @@ -2265,8 +2265,8 @@ The table below shows this endpoint's support for ### Parameters -- `:job_id` `(string: )` - Specifies the ID of the job (as specified in - the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `eval_priority` `(int: 0)` - Override the priority of the evaluations produced as a result of this job deregistration. By default, this is set to the priority @@ -2324,8 +2324,8 @@ The table below shows this endpoint's support for ### Parameters -- `:job_id` `(string: )` - Specifies the ID of the job (as specified in - the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `namespace` `(string: "default")` - Specifies the target namespace. If ACL is enabled, this value must match a namespace that the token is allowed to @@ -2380,8 +2380,8 @@ The table below shows this endpoint's support for ### Parameters -- `:job_id` `(string: )` - Specifies the ID of the job (as specified in - the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `Count` `(int: )` - Specifies the new task group count. @@ -2466,8 +2466,8 @@ The table below shows this endpoint's support for ### Parameters -- `:job_id` `(string: )` - Specifies the ID of the job (as specified in -the job file during submission). This is specified as part of the path. +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. - `namespace` `(string: "default")` - Specifies the target namespace. @@ -2501,3 +2501,174 @@ $ curl \ } ] ``` + +## List Job Actions + +This endpoint lists the actions available to tasks within a job. + +| Method | Path | Produces | +| ------ | ------------------------- | ------------------ | +| `GET` | `/v1/job/:job_id/actions` | `application/json` | + +The table below shows this endpoint's support for +[blocking queries](/nomad/api-docs#blocking-queries) and +[required ACLs](/nomad/api-docs#acls). + +| Blocking Queries | ACL Required | +| ---------------- | -------------------- | +| `NO` | `namespace:read-job` | + +### Parameters + +- `:job_id` `(string: )` - Specifies the ID of the job. This is + specified as part of the path. + +### Sample Request + +```shell-session +$ nomad operator api '/v1/job/my-job/actions' +``` + +### Sample Response + +```json +[ + { + "Args": [ + "-s", + "wttr.in/Toronto?format=3" + ], + "Command": "/usr/bin/curl", + "Name": "weather", + "TaskGroupName": "group1", + "TaskName": "task" + }, + { + "Args": [ + "-c", + "nomad alloc status ${NOMAD_ALLOC_ID}" + ], + "Command": "/bin/sh", + "Name": "get-alloc-info", + "TaskGroupName": "group2", + "TaskName": "task" + }, +] +``` + +## Run Action + +This endpoint executes a predefined action from a job in the +context of a specific task within an allocation. It opens a WebSocket to +transmit output from the running action. + + +| Method | Path | Produces | +| ----------- | ------------------------ | ---------------------- | +| `WebSocket` | `/v1/job/:job_id/action` | WebSocket JSON streams | + +The table below shows this endpoint's support for +[blocking queries](/nomad/api-docs#blocking-queries) and +[required ACLs](/nomad/api-docs#acls). + +| Blocking Queries | ACL Required | +| ---------------- | -------------------------------------------------------------------------------------------- | +| `NO` | `namespace:alloc-exec` (and `namespace:alloc-node-exec` if task driver does not support [file system isolation](/nomad/docs/concepts/plugins/task-drivers#capabilities-capabilities-error)) | + +### Parameters + +- `:job_id` `(string: )` - Specifies the ID of the job containing the + action. This is specified as part of the path. +- `action` `(string: )` - Specifies the name of the action to be + executed, as defined within the job's task. Specified as a query parameter. +- `alloc_id` `(string: )` - Specifies the UUID of the target + allocation. Specified as a query parameter. +- `task` `(string: )` - Specifies the task where the action is + defined. Specified as a query parameter. +- `tty` `(bool: false)` - Specifies whether a TTY is allocated for this action. + Specified as a query parameter. +- `ws_handshake` `(bool: false)` - Specifies whether to expect the + authentication token in the first frame, as a query parameter. + +### WebSocket Upgrade Request + +Actions are executed with a WebSocket upgrade request. The request headers +must include the standard WebSocket upgrade parameters. Example: + +```shell-session +$ curl \ + -X GET \ + -H 'Connection: Upgrade' \ + -H 'Upgrade: websocket' \ + -H 'Sec-Websocket-Version: 13' \ + -H 'Sec-Websocket-Key: x3JJHMbDL1EzLkh9GBhXDw==' \ + 'http://127.0.0.1:4646/v1/job/actions-demo/action?action=weather&allocID=8614ed07-425f-889f-170d-dd2716e0e01f&task=task&tty=true' +``` +or +```shell-session +$ nomad operator api \ +-H "Connection: Upgrade" \ +-H "Upgrade: websocket" \ +-H "Sec-WebSocket-Version: 13" \ +-H "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==" \ +'/v1/job/actions-demo/action?action=weather&allocID=8614ed07-425f-889f-170d-dd2716e0e01f&task=task&tty=true' +``` + +### Request Frames + +Unlike [alloc exec](/nomad/api-docs/allocations#exec-allocation), Actions do +not take input in the form of request frames using `stdin`. Instead, the name +of the action is passed as a query parameter and the associated input is handled +on the allocation apart from the stream. + +When `?ws_handshake=true`, the first request frame must contain the +authentication token. Similarly, when `?tty=true`, a `tty_size` frame may be +provided to indicate a resize. + +The following are valid input frames: + +``` +# sending authentication token +{"version":1,"auth_token":"fc3c1968-8d31-5c50-9617-3db2e19ef32e"} + +# indicating that TTY was resized +{"tty_size": {"height": , "width": }} +``` + +### Response Frames + +Response frames represent `stdout` and `stderr` output from the command as well +as exit codes. Response frames encompass the full range of terminal emulator outputs, including +the control characters necessary to render interactive applications. + +``` +# transferring stdout data +{"stdout": {"data": "...base64 encoded string of bytes ..."}} + +# signaling that host closed stdout +{"stdout": {"close": true}} +{"exited":true,"result":{}} +``` + +The stdout data returned in the response frames from the job action endpoint is +base64 encoded. This encoding is necessary to ensure that the data can be +transmitted over WebSocket connections without issues, regardless of its content. + +### Sample Request and Response + +This example response includes instances of the ANSI "control sequence +introducer" (CSI), which is ASCII code 27 followed by `[`. The second stdout +frame includes the desired output from the Action in question, which prints +the local weather. + +``` +# Sample WebSocket request to execute an action +WebSocket Request: /v1/job/actions-demo/action?action=weather&allocID=8614ed07-425f-889f-170d-dd2716e0e01f&task=task&tty=true + +# "\x1b[H\x1b[2J$ ": +# CSI-H (move cursor to top left corner), CSI-2J (clear entire screen), print "$ " +{"stdout":{"data":"G1tIG1sySiQg"}} + +# Toronto: ⛅️ -1°C +{"stdout":{"data":"VG9yb250bzog4puF77iPICAtMcKwQw0K"}} +``` diff --git a/website/content/docs/commands/job/action.mdx b/website/content/docs/commands/job/action.mdx new file mode 100644 index 000000000..1d16217c0 --- /dev/null +++ b/website/content/docs/commands/job/action.mdx @@ -0,0 +1,107 @@ +--- +layout: docs +page_title: 'Commands: job action' +description: | + The job action command is used to execute predefined actions from a job + specification in a running task context +--- + +# Command: job action + +**Alias: `nomad action`** + +The `job action` command allows operators to execute predefined actions declared +in Nomad job specifications. These actions can be defined at task level and are +intended for specific operational tasks, such as clearing a cache, or migrating +a database. + +An action may self-terminate upon completion (for example, echoing a string), +or run for an indeterminate amount of time (for example, watching a blocking +query). In the latter case, an action can be terminated via escape character +(such as cmd+c / ctrl+c) + +When ACLs are enabled, this command requires a token with the `alloc-exec`, +`read-job`, and `list-jobs` capabilities for the allocation's namespace. If +the task driver does not have file system isolation (as with `raw_exec`), +this command requires the `alloc-node-exec`, `alloc-exec`, `read-job`, and +`list-jobs` capabilities for the allocation's namespace. + +## Usage + +```plaintext +nomad job action [options] +``` + +The `job action` command requires an action name and accepts two ways of +specifying where it should run: +- by passing the job name and known allocation ID along with the name of the action +- by passing the job, task group, and task name along with the name of the action +(when the allocation ID is not known). A random allocation will be selected if +multiple are available. + +The action name provided must be defined within a task in the [job specification] +provided. With sufficient privileges, an execution context will be opened and +the defined action command will be run. No further input is possible, save for +the escape character to terminate execution, so interactive commands are not +supported. + +## General Options + +@include 'general_options.mdx' + +## Action Options + +- `-job`: (Required) Specifies the job containing the predefined action. + +- `-alloc`: Specifies the allocation within which the action is to be executed. + If omitted, `-group` and `-task` must be provided, and a random + allocation for the group will be selected. + +- `-task`: Specifies the task within the job where the action is defined. This + is required either if `-alloc` provided and your group has multiple tasks, + or if you specify a `-group`. + +- `-group`: Specifies the task group within the job. If present, a random + allocation is selected. If omitted, `-alloc` must be provided. + +- `-i`: Pass stdin to the action, defaults to `true`. Pass `-i=false` to + disable explicitly. + +- `-t`: Allocate a pseudo-tty, defaults to `true` if stdin is detected to be a tty + session. Pass `-t=false` to disable explicitly. + +- `-e` : Sets the escape character for sessions with a pty, + defaults to '~'. The escape character is only recognized at the beginning of a + line. The escape character followed by a dot (`.`) closes the connection. + Setting the character to `none` disables any escapes and makes the session + fully transparent. + +## Examples + +Execute an action within a specific task in a job: + +```shell-session +$ nomad action \ + -group=my-group \ + -task=my-task \ + -job=my-job \ + weather + +Toronto: ☁️ +3°C +``` + +Execute an action within a specific allocation: + +```shell-session +$ nomad action \ + -alloc=f200a789-6da4-504c-d131-6181764f101e \ + -job=actions-demo \ + echo-time + +Running for 0 seconds +Running for 1 seconds +Running for 2 seconds +Running for 3 seconds +``` + +[job specification]: /nomad/docs/job-specification diff --git a/website/content/docs/job-specification/action.mdx b/website/content/docs/job-specification/action.mdx new file mode 100644 index 000000000..50c283466 --- /dev/null +++ b/website/content/docs/job-specification/action.mdx @@ -0,0 +1,128 @@ +--- +layout: docs +page_title: action Block - Job Specification +description: The "action" block allows for executable commands that job authors can predefine for operators to subsequently run against their job. +--- + +# `action` Block + + + +The `action` block allows job authors to define custom commands. These commands +can be executed by operators with the necessary permissions on a running +allocation, offering a controlled way to interact with tasks. + +## `action` Parameters + +- `command` `(string: )` - Specifies the command to be executed. +- `args` `(array: [])` - Provides a list of arguments to pass to the command. + +```hcl +job "my-job" { + group "my-group" { + task "my-task" { + action "get-changelog" { + command = "/usr/bin/curl" + args = [ + "-s", + "https://raw.githubusercontent.com/hashicorp/nomad/main/CHANGELOG.md" + ] + } + # ... + } + } +} +``` + +## `action` Examples + +### Basic Action +This example demonstrates a simple action that prints the current date and time: + +```hcl +job "example" { + # ... + group "demo" { + # ... + task "show-date" { + # ... + action "current-date" { + command = "/bin/date" + } + } + } +} +``` + +### Action with Arguments +Here, an action uses arguments to perform a specific task: + +```hcl +job "example" { + # ... + group "demo" { + # ... + task "list-files" { + # ... + action "list-tmp" { + command = "/bin/ls" + args = ["-l", "/tmp"] + } + } + } +} +``` + +### Action with Template +This advanced example demonstrates an action that fetches and formats the latest +changelog from the Nomad GitHub repository using a shell script. It showcases +the use of templating with embedded environment variables and a multi-line script. + +```hcl +action "fetch-latest-nomad-changelog" { + command = "/bin/sh" + args = ["-c", + < 1){ + # Splitting the version line into components + split($1, versionInfo, /[()]/); + version=versionInfo[1]; + gsub(" ", "", version); # Removing spaces from version + releaseDate=versionInfo[2]; + # Formatting URL components + urlVersion=version; gsub("\\.", "", urlVersion); # Remove dots from version + urlDate=releaseDate; gsub(" ", "-", urlDate); gsub(",", "", urlDate); # Replace spaces with hyphens and remove comma + # Counting items under each section + for(i=1; i<=NF; i++){ + if($i ~ /^[A-Z ]+:$/){ + gsub(":", "", $i); + section=$i; + itemCount[section]=0; + } + if(section && $i ~ /^\*/){ + itemCount[section]++; + } + } + # Printing the extracted information + printf "Version: %s\nRelease Date: %s\n", version, releaseDate; + for (s in itemCount) { + printf "%d %s, ", itemCount[s], s; + } + printf "\nLink: https://github.com/hashicorp/nomad/blob/main/CHANGELOG.md#%s-%s\n\n", urlVersion, tolower(urlDate); + delete itemCount; # Clear the itemCount array for the next version + count++; + } +}' + EOT + ] +} +``` diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 9a748d857..82a63a072 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -514,6 +514,10 @@ "title": "Overview", "path": "commands/job" }, + { + "title": "action", + "path": "commands/job/action" + }, { "title": "allocs", "path": "commands/job/allocs" @@ -1596,6 +1600,10 @@ } ] }, + { + "title": "action", + "path": "job-specification/action" + }, { "title": "artifact", "path": "job-specification/artifact"