mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-26 09:30:28 +00:00
api rework
This commit is contained in:
parent
86a205289c
commit
3e91e8df01
12 changed files with 968 additions and 353 deletions
|
@ -150,6 +150,13 @@ processResources {
|
||||||
into resourcesDir
|
into resourcesDir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
copy {
|
||||||
|
from file("$rootDir/openapi.yaml")
|
||||||
|
into file("${sourceSets.main.output.resourcesDir}/io/xpipe/app/resources/misc");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
distTar {
|
distTar {
|
||||||
|
|
|
@ -119,6 +119,7 @@ public class AppBeaconServer {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
var resourceMap = Map.of(
|
var resourceMap = Map.of(
|
||||||
|
"openapi.yaml", "misc/openapi.yaml",
|
||||||
"markdown.css", "misc/github-markdown-dark.css",
|
"markdown.css", "misc/github-markdown-dark.css",
|
||||||
"highlight.min.js", "misc/highlight.min.js",
|
"highlight.min.js", "misc/highlight.min.js",
|
||||||
"github-dark.min.css", "misc/github-dark.min.css"
|
"github-dark.min.css", "misc/github-dark.min.css"
|
||||||
|
|
|
@ -6,6 +6,7 @@ import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.beacon.BeaconClientException;
|
import io.xpipe.beacon.BeaconClientException;
|
||||||
import io.xpipe.beacon.BeaconServerException;
|
import io.xpipe.beacon.BeaconServerException;
|
||||||
import io.xpipe.beacon.api.ConnectionQueryExchange;
|
import io.xpipe.beacon.api.ConnectionQueryExchange;
|
||||||
|
import io.xpipe.core.store.StorePath;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -16,7 +17,7 @@ public class ConnectionQueryExchangeImpl extends ConnectionQueryExchange {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
|
public Object handle(HttpExchange exchange, Request msg) throws IOException, BeaconClientException, BeaconServerException {
|
||||||
var catMatcher = Pattern.compile(toRegex(msg.getCategoryFilter()));
|
var catMatcher = Pattern.compile(toRegex("all connections/" + msg.getCategoryFilter()));
|
||||||
var conMatcher = Pattern.compile(toRegex(msg.getConnectionFilter()));
|
var conMatcher = Pattern.compile(toRegex(msg.getConnectionFilter()));
|
||||||
|
|
||||||
List<DataStoreEntry> found = new ArrayList<>();
|
List<DataStoreEntry> found = new ArrayList<>();
|
||||||
|
@ -45,7 +46,8 @@ public class ConnectionQueryExchangeImpl extends ConnectionQueryExchange {
|
||||||
|
|
||||||
var mapped = new ArrayList<QueryResponse>();
|
var mapped = new ArrayList<QueryResponse>();
|
||||||
for (DataStoreEntry e : found) {
|
for (DataStoreEntry e : found) {
|
||||||
var cat = DataStorage.get().getStorePath(DataStorage.get().getStoreCategoryIfPresent(e.getCategoryUuid()).orElseThrow());
|
var names = DataStorage.get().getStorePath(DataStorage.get().getStoreCategoryIfPresent(e.getCategoryUuid()).orElseThrow()).getNames();
|
||||||
|
var cat = new StorePath(names.subList(1, names.size()));
|
||||||
var obj = ConnectionQueryExchange.QueryResponse.builder()
|
var obj = ConnectionQueryExchange.QueryResponse.builder()
|
||||||
.uuid(e.getUuid()).category(cat).connection(DataStorage.get()
|
.uuid(e.getUuid()).category(cat).connection(DataStorage.get()
|
||||||
.getStorePath(e)).type(e.getProvider().getId()).build();
|
.getStorePath(e)).type(e.getProvider().getId()).build();
|
||||||
|
@ -55,6 +57,92 @@ public class ConnectionQueryExchangeImpl extends ConnectionQueryExchange {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String toRegex(String pattern) {
|
private String toRegex(String pattern) {
|
||||||
return pattern.replaceAll("\\*\\*", ".*?").replaceAll("\\*","[^\\\\]*?");
|
// https://stackoverflow.com/a/17369948/6477761
|
||||||
|
StringBuilder sb = new StringBuilder(pattern.length());
|
||||||
|
int inGroup = 0;
|
||||||
|
int inClass = 0;
|
||||||
|
int firstIndexInClass = -1;
|
||||||
|
char[] arr = pattern.toCharArray();
|
||||||
|
for (int i = 0; i < arr.length; i++) {
|
||||||
|
char ch = arr[i];
|
||||||
|
switch (ch) {
|
||||||
|
case '\\':
|
||||||
|
if (++i >= arr.length) {
|
||||||
|
sb.append('\\');
|
||||||
|
} else {
|
||||||
|
char next = arr[i];
|
||||||
|
switch (next) {
|
||||||
|
case ',':
|
||||||
|
// escape not needed
|
||||||
|
break;
|
||||||
|
case 'Q':
|
||||||
|
case 'E':
|
||||||
|
// extra escape needed
|
||||||
|
sb.append('\\');
|
||||||
|
default:
|
||||||
|
sb.append('\\');
|
||||||
|
}
|
||||||
|
sb.append(next);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '*':
|
||||||
|
if (inClass == 0)
|
||||||
|
sb.append(".*");
|
||||||
|
else
|
||||||
|
sb.append('*');
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
if (inClass == 0)
|
||||||
|
sb.append('.');
|
||||||
|
else
|
||||||
|
sb.append('?');
|
||||||
|
break;
|
||||||
|
case '[':
|
||||||
|
inClass++;
|
||||||
|
firstIndexInClass = i+1;
|
||||||
|
sb.append('[');
|
||||||
|
break;
|
||||||
|
case ']':
|
||||||
|
inClass--;
|
||||||
|
sb.append(']');
|
||||||
|
break;
|
||||||
|
case '.':
|
||||||
|
case '(':
|
||||||
|
case ')':
|
||||||
|
case '+':
|
||||||
|
case '|':
|
||||||
|
case '^':
|
||||||
|
case '$':
|
||||||
|
case '@':
|
||||||
|
case '%':
|
||||||
|
if (inClass == 0 || (firstIndexInClass == i && ch == '^'))
|
||||||
|
sb.append('\\');
|
||||||
|
sb.append(ch);
|
||||||
|
break;
|
||||||
|
case '!':
|
||||||
|
if (firstIndexInClass == i)
|
||||||
|
sb.append('^');
|
||||||
|
else
|
||||||
|
sb.append('!');
|
||||||
|
break;
|
||||||
|
case '{':
|
||||||
|
inGroup++;
|
||||||
|
sb.append('(');
|
||||||
|
break;
|
||||||
|
case '}':
|
||||||
|
inGroup--;
|
||||||
|
sb.append(')');
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
if (inGroup > 0)
|
||||||
|
sb.append('|');
|
||||||
|
else
|
||||||
|
sb.append(',');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ public class HandshakeExchangeImpl extends HandshakeExchange {
|
||||||
|
|
||||||
var session = new BeaconSession(body.getClient(), UUID.randomUUID().toString());
|
var session = new BeaconSession(body.getClient(), UUID.randomUUID().toString());
|
||||||
AppBeaconServer.get().addSession(session);
|
AppBeaconServer.get().addSession(session);
|
||||||
return Response.builder().token(session.getToken()).build();
|
return Response.builder().sessionToken(session.getToken()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkAuth(BeaconAuthMethod authMethod) {
|
private boolean checkAuth(BeaconAuthMethod authMethod) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.launcher.LauncherInput;
|
import io.xpipe.app.launcher.LauncherInput;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.util.PlatformState;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
|
|
||||||
|
@ -16,6 +17,10 @@ import java.util.List;
|
||||||
public class AppDesktopIntegration {
|
public class AppDesktopIntegration {
|
||||||
|
|
||||||
public static void setupDesktopIntegrations() {
|
public static void setupDesktopIntegrations() {
|
||||||
|
if (PlatformState.getCurrent() != PlatformState.RUNNING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (Desktop.isDesktopSupported()) {
|
if (Desktop.isDesktopSupported()) {
|
||||||
Desktop.getDesktop().addAppEventListener(new SystemSleepListener() {
|
Desktop.getDesktop().addAppEventListener(new SystemSleepListener() {
|
||||||
|
|
|
@ -763,13 +763,13 @@ public abstract class DataStorage {
|
||||||
public StorePath getStorePath(DataStoreEntry entry) {
|
public StorePath getStorePath(DataStoreEntry entry) {
|
||||||
return StorePath.create(getStoreParentHierarchy(entry).stream()
|
return StorePath.create(getStoreParentHierarchy(entry).stream()
|
||||||
.filter(e -> !(e.getStore() instanceof LocalStore))
|
.filter(e -> !(e.getStore() instanceof LocalStore))
|
||||||
.map(e -> e.getName().replaceAll("/", "_"))
|
.map(e -> e.getName().toLowerCase().replaceAll("/", "_"))
|
||||||
.toArray(String[]::new));
|
.toArray(String[]::new));
|
||||||
}
|
}
|
||||||
|
|
||||||
public StorePath getStorePath(DataStoreCategory entry) {
|
public StorePath getStorePath(DataStoreCategory entry) {
|
||||||
return StorePath.create(getCategoryParentHierarchy(entry).stream()
|
return StorePath.create(getCategoryParentHierarchy(entry).stream()
|
||||||
.map(e -> e.getName().replaceAll("/", "_"))
|
.map(e -> e.getName().toLowerCase().replaceAll("/", "_"))
|
||||||
.toArray(String[]::new));
|
.toArray(String[]::new));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -70,6 +70,12 @@ html {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown-body summary {
|
||||||
|
font-weight: 600;
|
||||||
|
padding-bottom: .3em;
|
||||||
|
font-size: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
.markdown-body h1 {
|
.markdown-body h1 {
|
||||||
margin: .67em 0;
|
margin: .67em 0;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
@ -416,7 +422,7 @@ html {
|
||||||
.markdown-body table,
|
.markdown-body table,
|
||||||
.markdown-body pre,
|
.markdown-body pre,
|
||||||
.markdown-body details {
|
.markdown-body details {
|
||||||
margin-top: 0;
|
margin-top: 16px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -542,10 +542,16 @@ html {
|
||||||
.markdown-body table,
|
.markdown-body table,
|
||||||
.markdown-body pre,
|
.markdown-body pre,
|
||||||
.markdown-body details {
|
.markdown-body details {
|
||||||
margin-top: 0;
|
margin-top: 16px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown-body summary {
|
||||||
|
font-weight: 600;
|
||||||
|
padding-bottom: .3em;
|
||||||
|
font-size: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
.markdown-body blockquote > :first-child {
|
.markdown-body blockquote > :first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ public class BeaconClient {
|
||||||
HandshakeExchange.Response response = client.performRequest(HandshakeExchange.Request.builder()
|
HandshakeExchange.Response response = client.performRequest(HandshakeExchange.Request.builder()
|
||||||
.client(information)
|
.client(information)
|
||||||
.auth(BeaconAuthMethod.Local.builder().authFileContent(auth).build()).build());
|
.auth(BeaconAuthMethod.Local.builder().authFileContent(auth).build()).build());
|
||||||
client.token = response.getToken();
|
client.token = response.getSessionToken();
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,6 @@ public class HandshakeExchange extends BeaconInterface<HandshakeExchange.Request
|
||||||
@Value
|
@Value
|
||||||
public static class Response {
|
public static class Response {
|
||||||
@NonNull
|
@NonNull
|
||||||
String token;
|
String sessionToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
233
openapi.yaml
233
openapi.yaml
|
@ -1,7 +1,22 @@
|
||||||
openapi: 3.0.1
|
openapi: 3.0.1
|
||||||
info:
|
info:
|
||||||
title: XPipe API Documentation
|
title: XPipe API Documentation
|
||||||
description: The XPipe API provides programmatic access to XPipe’s features.
|
description: |
|
||||||
|
The XPipe API provides programmatic access to XPipe’s features.
|
||||||
|
|
||||||
|
The XPipe application will start up an HTTP server that can be used to send requests.
|
||||||
|
You can change the port of it in the settings menu.
|
||||||
|
Note that this server is HTTP-only for now as it runs only on localhost. HTTPS requests are not accepted.
|
||||||
|
|
||||||
|
The main use case for the API right now is programmatically managing remote systems.
|
||||||
|
To start off, you can query connections based on various filters.
|
||||||
|
With the matched connections, you can start remote shell sessions for each one and run arbitrary commands in them.
|
||||||
|
You get the command exit code and output as a response, allowing you to adapt your control flow based on command outputs.
|
||||||
|
Any kind of passwords another secret are automatically provided by XPipe when establishing a shell connection.
|
||||||
|
If required password is not stored and is set to be dynamically prompted, the running XPipe application will ask you to enter any required passwords.
|
||||||
|
|
||||||
|
You can quickly get started by either using this page as an API reference or alternatively import the [OpenAPI definition file](/openapi.yaml) into your API client of choice.
|
||||||
|
See the authentication handshake below on how to authenticate prior to sending requests.
|
||||||
termsOfService: https://docs.xpipe.io/terms-of-service
|
termsOfService: https://docs.xpipe.io/terms-of-service
|
||||||
contact:
|
contact:
|
||||||
name: XPipe - Contact us
|
name: XPipe - Contact us
|
||||||
|
@ -11,16 +26,21 @@ externalDocs:
|
||||||
description: XPipe - Plans and pricing
|
description: XPipe - Plans and pricing
|
||||||
url: https://xpipe.io/pricing
|
url: https://xpipe.io/pricing
|
||||||
servers:
|
servers:
|
||||||
- url: https://localhost:21721
|
- url: http://localhost:21721
|
||||||
description: XPipe Daemon API
|
description: XPipe Daemon API
|
||||||
- url: https://localhost:21722
|
|
||||||
description: XPipe PTB Daemon API
|
|
||||||
paths:
|
paths:
|
||||||
/handshake:
|
/handshake:
|
||||||
post:
|
post:
|
||||||
summary: Create new session
|
summary: Establish a new API session
|
||||||
description: Creates a new API session, allowing you to send requests to the daemon once it is established.
|
description: |
|
||||||
|
Prior to sending requests to the API, you first have to establish a new API session via the handshake endpoint.
|
||||||
|
In the response you will receive a session token that you can use to authenticate during this session.
|
||||||
|
|
||||||
|
This is done so that the daemon knows what kind of clients are connected and can manage individual capabilities for clients.
|
||||||
|
|
||||||
|
Note that for development you can also turn off the required authentication in the XPipe settings menu, allowing you to send unauthenticated requests.
|
||||||
operationId: handshake
|
operationId: handshake
|
||||||
|
security: []
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
|
@ -54,7 +74,11 @@ paths:
|
||||||
/connection/query:
|
/connection/query:
|
||||||
post:
|
post:
|
||||||
summary: Query connections
|
summary: Query connections
|
||||||
description: Queries all connections using various filters
|
description: |
|
||||||
|
Queries all connections using various filters.
|
||||||
|
|
||||||
|
The filters support globs and can match the category names and connection names.
|
||||||
|
All matching is case insensitive.
|
||||||
operationId: connectionQuery
|
operationId: connectionQuery
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
|
@ -65,13 +89,13 @@ paths:
|
||||||
examples:
|
examples:
|
||||||
all:
|
all:
|
||||||
summary: All
|
summary: All
|
||||||
value: {"categoryFilter": "**", "connectionFilter": "**", "typeFilter": "*"}
|
value: { "categoryFilter": "*", "connectionFilter": "*", "typeFilter": "*" }
|
||||||
simple:
|
simple:
|
||||||
summary: Simple filter
|
summary: Simple filter
|
||||||
value: { "categoryFilter": "default", "connectionFilter": "local machine", "typeFilter": "*" }
|
value: { "categoryFilter": "default", "connectionFilter": "local machine", "typeFilter": "*" }
|
||||||
globs:
|
globs:
|
||||||
summary: Globs
|
summary: Globs
|
||||||
value: {"categoryFilter": "**", "connectionFilter": "**/podman/*", "typeFilter": "*"}
|
value: { "categoryFilter": "*", "connectionFilter": "*/podman/*", "typeFilter": "*" }
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: The query was successful. The body contains all matched connections.
|
description: The query was successful. The body contains all matched connections.
|
||||||
|
@ -79,6 +103,13 @@ paths:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ConnectionQueryResponse'
|
$ref: '#/components/schemas/ConnectionQueryResponse'
|
||||||
|
examples:
|
||||||
|
standard:
|
||||||
|
summary: Matched connections
|
||||||
|
value: { "found": [ { "uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b", "category": ["default"] ,
|
||||||
|
"connection": ["local machine"], "type": "local" },
|
||||||
|
{ "uuid": "e1462ddc-9beb-484c-bd91-bb666027e300", "category": ["default", "category 1"],
|
||||||
|
"connection": ["ssh system", "shell environments", "bash"], "type": "shellEnvironment" } ] }
|
||||||
400:
|
400:
|
||||||
$ref: '#/components/responses/BadRequest'
|
$ref: '#/components/responses/BadRequest'
|
||||||
401:
|
401:
|
||||||
|
@ -89,29 +120,107 @@ paths:
|
||||||
$ref: '#/components/responses/NotFound'
|
$ref: '#/components/responses/NotFound'
|
||||||
500:
|
500:
|
||||||
$ref: '#/components/responses/InternalServerError'
|
$ref: '#/components/responses/InternalServerError'
|
||||||
/daemon/open:
|
/shell/start:
|
||||||
post:
|
post:
|
||||||
summary: Open URLs
|
summary: Start shell connection
|
||||||
description: Opens main window or executes given actions.
|
description: |
|
||||||
operationId: daemonOpen
|
Starts a new shell session for a connection. If an existing shell session is already running for that connection, this operation will do nothing.
|
||||||
|
|
||||||
|
Note that there are a variety of possible errors that can occur here when establishing the shell connection.
|
||||||
|
These errors will be returned with the HTTP return code 500.
|
||||||
|
operationId: shellStart
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: object
|
$ref: '#/components/schemas/ShellStartRequest'
|
||||||
properties:
|
examples:
|
||||||
arguments:
|
local:
|
||||||
description: |-
|
summary: Start local shell
|
||||||
Arguments to open. These can be URLs of various different types to perform certain actions.
|
value: { "uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b" }
|
||||||
type: array
|
|
||||||
minItems: 0
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
example: file:///home/user/.ssh/
|
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: '#/components/responses/Success'
|
description: The operation was successful. The shell session was started.
|
||||||
|
400:
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
401:
|
||||||
|
$ref: '#/components/responses/Unauthorized'
|
||||||
|
403:
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
404:
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
500:
|
||||||
|
$ref: '#/components/responses/InternalServerError'
|
||||||
|
/shell/stop:
|
||||||
|
post:
|
||||||
|
summary: Stop shell connection
|
||||||
|
description: |
|
||||||
|
Stops an existing shell session for a connection.
|
||||||
|
|
||||||
|
This operation will return once the shell has exited.
|
||||||
|
If the shell is busy or stuck, you might have to work with timeouts to account for these cases.
|
||||||
|
operationId: shellStop
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ShellStopRequest'
|
||||||
|
examples:
|
||||||
|
local:
|
||||||
|
summary: Stop local shell
|
||||||
|
value: { "uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b" }
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: The operation was successful. The shell session was stopped.
|
||||||
|
400:
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
401:
|
||||||
|
$ref: '#/components/responses/Unauthorized'
|
||||||
|
403:
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
404:
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
500:
|
||||||
|
$ref: '#/components/responses/InternalServerError'
|
||||||
|
/shell/exec:
|
||||||
|
post:
|
||||||
|
summary: Execute command in a shell session
|
||||||
|
description: |
|
||||||
|
Runs a command in an active shell session and waits for it to finish. The exit code and output will be returned in the response.
|
||||||
|
|
||||||
|
Note that a variety of different errors can occur when executing the command.
|
||||||
|
If the command finishes, even with an error code, a normal HTTP 200 response will be returned.
|
||||||
|
However, if any other error occurs like the shell not responding or exiting unexpectedly, an HTTP 500 response will be returned.
|
||||||
|
operationId: shellExec
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ShellExecRequest'
|
||||||
|
examples:
|
||||||
|
user:
|
||||||
|
summary: echo $USER
|
||||||
|
value: { "uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b", "command": "echo $USER" }
|
||||||
|
invalid:
|
||||||
|
summary: invalid
|
||||||
|
value: { "uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b", "command": "invalid" }
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: The operation was successful. The shell command finished.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ShellExecResponse'
|
||||||
|
examples:
|
||||||
|
user:
|
||||||
|
summary: echo $USER
|
||||||
|
value: { "exitCode": 0, "stdout": "root", "stderr": "" }
|
||||||
|
fail:
|
||||||
|
summary: invalid
|
||||||
|
value: { "exitCode": 127, "stdout": "", "stderr": "invalid: command not found" }
|
||||||
400:
|
400:
|
||||||
$ref: '#/components/responses/BadRequest'
|
$ref: '#/components/responses/BadRequest'
|
||||||
401:
|
401:
|
||||||
|
@ -124,18 +233,62 @@ paths:
|
||||||
$ref: '#/components/responses/InternalServerError'
|
$ref: '#/components/responses/InternalServerError'
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
|
ShellStartRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
connection:
|
||||||
|
type: string
|
||||||
|
description: The connection uuid
|
||||||
|
required:
|
||||||
|
- connection
|
||||||
|
ShellStopRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
connection:
|
||||||
|
type: string
|
||||||
|
description: The connection uuid
|
||||||
|
required:
|
||||||
|
- connection
|
||||||
|
ShellExecRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
connection:
|
||||||
|
type: string
|
||||||
|
description: The connection uuid
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
description: The command to execute
|
||||||
|
required:
|
||||||
|
- connection
|
||||||
|
- command
|
||||||
|
ShellExecResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
exitCode:
|
||||||
|
type: integer
|
||||||
|
description: The exit code of the command
|
||||||
|
stdout:
|
||||||
|
type: string
|
||||||
|
description: The stdout output of the command
|
||||||
|
stderr:
|
||||||
|
type: string
|
||||||
|
description: The stderr output of the command
|
||||||
|
required:
|
||||||
|
- exitCode
|
||||||
|
- stdout
|
||||||
|
- stderr
|
||||||
ConnectionQueryRequest:
|
ConnectionQueryRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
categoryFilter:
|
categoryFilter:
|
||||||
type: string
|
type: string
|
||||||
description: The filter string to match categories. Categories are delimited by / if they are hierarchical. The filter supports globs with * and **.
|
description: The filter string to match categories. Categories are delimited by / if they are hierarchical. The filter supports globs.
|
||||||
connectionFilter:
|
connectionFilter:
|
||||||
type: string
|
type: string
|
||||||
description: The filter string to match connection names. Connection names are delimited by / if they are hierarchical. The filter supports globs with * and **.
|
description: The filter string to match connection names. Connection names are delimited by / if they are hierarchical. The filter supports globs.
|
||||||
typeFilter:
|
typeFilter:
|
||||||
type: string
|
type: string
|
||||||
description: The filter string to connection types. Every unique type of connection like SSH or docker has its own type identifier that you can match. The filter supports globs with *.
|
description: The filter string to connection types. Every unique type of connection like SSH or docker has its own type identifier that you can match. The filter supports globs.
|
||||||
required:
|
required:
|
||||||
- categoryFilter
|
- categoryFilter
|
||||||
- connectionFilter
|
- connectionFilter
|
||||||
|
@ -153,11 +306,17 @@ components:
|
||||||
type: string
|
type: string
|
||||||
description: The unique id of the connection
|
description: The unique id of the connection
|
||||||
category:
|
category:
|
||||||
|
type: array
|
||||||
|
description: The full category path as an array
|
||||||
|
items:
|
||||||
type: string
|
type: string
|
||||||
description: The full category path
|
description: Individual category name
|
||||||
connection:
|
connection:
|
||||||
|
type: array
|
||||||
|
description: The full connection name path as an array
|
||||||
|
items:
|
||||||
type: string
|
type: string
|
||||||
description: The full connection name path
|
description: Individual connection name
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
description: The type identifier of the connection
|
description: The type identifier of the connection
|
||||||
|
@ -181,11 +340,11 @@ components:
|
||||||
HandshakeResponse:
|
HandshakeResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
token:
|
sessionToken:
|
||||||
type: string
|
type: string
|
||||||
description: The generated bearer token that can be used for authentication in this session
|
description: The generated bearer token that can be used for authentication in this session
|
||||||
required:
|
required:
|
||||||
- token
|
- sessionToken
|
||||||
AuthMethod:
|
AuthMethod:
|
||||||
type: object
|
type: object
|
||||||
discriminator:
|
discriminator:
|
||||||
|
@ -256,9 +415,9 @@ components:
|
||||||
InternalServerError:
|
InternalServerError:
|
||||||
description: Internal error.
|
description: Internal error.
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
auth_header:
|
bearerAuth:
|
||||||
type: apiKey
|
type: http
|
||||||
description: Authentication with `Authorization` header and `Bearer`
|
scheme: bearer
|
||||||
authentication scheme
|
description: The bearer token used is the session token that you receive from the handshake exchange.
|
||||||
name: Authorization
|
security:
|
||||||
in: header
|
- bearerAuth: []
|
||||||
|
|
Loading…
Reference in a new issue