@mittwald/cli 1.5.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/commands/app/dependency/list.d.ts +1 -0
- package/dist/commands/app/dependency/versions.d.ts +1 -0
- package/dist/commands/app/get.js +1 -2
- package/dist/commands/app/list-upgrade-candidates.d.ts +1 -0
- package/dist/commands/app/list.d.ts +1 -0
- package/dist/commands/app/upgrade.d.ts +14 -0
- package/dist/commands/app/upgrade.js +35 -22
- package/dist/commands/backup/delete.d.ts +1 -0
- package/dist/commands/backup/get.js +1 -2
- package/dist/commands/backup/list.d.ts +1 -0
- package/dist/commands/backup/schedule/list.d.ts +1 -0
- package/dist/commands/container/delete.d.ts +1 -0
- package/dist/commands/container/exec.d.ts +25 -0
- package/dist/commands/container/exec.js +93 -0
- package/dist/commands/container/list.d.ts +1 -0
- package/dist/commands/container/logs.d.ts +1 -0
- package/dist/commands/container/port-forward.d.ts +20 -0
- package/dist/commands/container/port-forward.js +68 -0
- package/dist/commands/container/run.d.ts +12 -0
- package/dist/commands/container/run.js +27 -8
- package/dist/commands/container/ssh.d.ts +17 -0
- package/dist/commands/container/ssh.js +61 -0
- package/dist/commands/conversation/categories.d.ts +1 -0
- package/dist/commands/conversation/create.d.ts +1 -0
- package/dist/commands/conversation/list.d.ts +1 -0
- package/dist/commands/conversation/reply.d.ts +1 -0
- package/dist/commands/cronjob/delete.d.ts +1 -0
- package/dist/commands/cronjob/execution/get.d.ts +1 -1
- package/dist/commands/cronjob/execution/list.d.ts +1 -0
- package/dist/commands/cronjob/execution/logs.d.ts +1 -0
- package/dist/commands/cronjob/get.js +1 -2
- package/dist/commands/cronjob/list.d.ts +1 -0
- package/dist/commands/database/list.d.ts +1 -0
- package/dist/commands/database/mysql/charsets.d.ts +1 -0
- package/dist/commands/database/mysql/delete.d.ts +1 -0
- package/dist/commands/database/mysql/get.d.ts +1 -1
- package/dist/commands/database/mysql/list.d.ts +1 -0
- package/dist/commands/database/mysql/user/delete.d.ts +1 -0
- package/dist/commands/database/mysql/user/get.d.ts +1 -1
- package/dist/commands/database/mysql/user/list.d.ts +1 -0
- package/dist/commands/database/mysql/versions.d.ts +1 -0
- package/dist/commands/database/redis/get.d.ts +1 -1
- package/dist/commands/database/redis/list.d.ts +1 -0
- package/dist/commands/database/redis/versions.d.ts +1 -0
- package/dist/commands/domain/dnszone/get.js +1 -2
- package/dist/commands/domain/dnszone/list.d.ts +1 -0
- package/dist/commands/domain/dnszone/update.js +3 -2
- package/dist/commands/domain/get.js +1 -2
- package/dist/commands/domain/list.d.ts +1 -0
- package/dist/commands/domain/virtualhost/delete.d.ts +1 -0
- package/dist/commands/domain/virtualhost/get.js +1 -2
- package/dist/commands/domain/virtualhost/list.d.ts +1 -0
- package/dist/commands/extension/list-installed.d.ts +1 -0
- package/dist/commands/extension/list.d.ts +1 -0
- package/dist/commands/mail/address/delete.d.ts +1 -0
- package/dist/commands/mail/address/get.d.ts +1 -1
- package/dist/commands/mail/address/list.d.ts +1 -0
- package/dist/commands/mail/deliverybox/delete.d.ts +1 -0
- package/dist/commands/mail/deliverybox/get.d.ts +1 -1
- package/dist/commands/mail/deliverybox/list.d.ts +1 -0
- package/dist/commands/org/delete.d.ts +1 -0
- package/dist/commands/org/get.js +1 -2
- package/dist/commands/org/invite/list-own.d.ts +1 -0
- package/dist/commands/org/invite/list.d.ts +1 -0
- package/dist/commands/org/list.d.ts +1 -0
- package/dist/commands/org/membership/list-own.d.ts +1 -0
- package/dist/commands/org/membership/list.d.ts +1 -0
- package/dist/commands/project/delete.d.ts +1 -0
- package/dist/commands/project/filesystem/usage.js +1 -2
- package/dist/commands/project/get.js +1 -2
- package/dist/commands/project/invite/get.d.ts +1 -1
- package/dist/commands/project/invite/list-own.d.ts +1 -0
- package/dist/commands/project/invite/list.d.ts +1 -0
- package/dist/commands/project/list.d.ts +1 -0
- package/dist/commands/project/membership/get-own.d.ts +1 -0
- package/dist/commands/project/membership/get.d.ts +1 -1
- package/dist/commands/project/membership/list-own.d.ts +1 -0
- package/dist/commands/project/membership/list.d.ts +1 -0
- package/dist/commands/registry/delete.d.ts +1 -0
- package/dist/commands/registry/list.d.ts +1 -0
- package/dist/commands/server/get.d.ts +1 -1
- package/dist/commands/server/list.d.ts +1 -0
- package/dist/commands/sftp-user/delete.d.ts +1 -0
- package/dist/commands/sftp-user/list.d.ts +1 -0
- package/dist/commands/ssh-user/delete.d.ts +1 -0
- package/dist/commands/ssh-user/list.d.ts +1 -0
- package/dist/commands/stack/delete.d.ts +1 -0
- package/dist/commands/stack/deploy.js +1 -8
- package/dist/commands/stack/list.d.ts +1 -0
- package/dist/commands/stack/ps.d.ts +1 -0
- package/dist/commands/stack/ps.js +1 -1
- package/dist/commands/user/api-token/get.d.ts +1 -1
- package/dist/commands/user/api-token/list.d.ts +1 -0
- package/dist/commands/user/api-token/revoke.d.ts +1 -0
- package/dist/commands/user/get.d.ts +1 -1
- package/dist/commands/user/session/get.d.ts +1 -1
- package/dist/commands/user/session/list.d.ts +1 -0
- package/dist/commands/user/ssh-key/delete.d.ts +1 -0
- package/dist/commands/user/ssh-key/get.d.ts +1 -1
- package/dist/commands/user/ssh-key/list.d.ts +1 -0
- package/dist/lib/basecommands/BaseCommand.d.ts +7 -5
- package/dist/lib/basecommands/BaseCommand.js +26 -8
- package/dist/lib/basecommands/CommandFlags.d.ts +3 -3
- package/dist/lib/basecommands/CoreBaseCommand.d.ts +12 -0
- package/dist/lib/basecommands/CoreBaseCommand.js +16 -0
- package/dist/lib/basecommands/DeleteBaseCommand.d.ts +1 -0
- package/dist/lib/basecommands/DeleteBaseCommand.js +1 -0
- package/dist/lib/basecommands/ExecRenderBaseCommand.d.ts +3 -0
- package/dist/lib/basecommands/ExecRenderBaseCommand.js +3 -0
- package/dist/lib/basecommands/ExtendedBaseCommand.d.ts +3 -0
- package/dist/lib/basecommands/ExtendedBaseCommand.js +3 -0
- package/dist/lib/basecommands/GetBaseCommand.d.ts +1 -1
- package/dist/lib/basecommands/GetBaseCommand.js +1 -0
- package/dist/lib/basecommands/ListBaseCommand.d.ts +1 -0
- package/dist/lib/basecommands/ListBaseCommand.js +1 -0
- package/dist/lib/basecommands/UnauthenticatedBaseCommand.d.ts +10 -0
- package/dist/lib/basecommands/UnauthenticatedBaseCommand.js +16 -0
- package/dist/lib/error/NoTokenFoundError.d.ts +11 -0
- package/dist/lib/error/NoTokenFoundError.js +13 -0
- package/dist/lib/resources/ssh/container.d.ts +3 -0
- package/dist/lib/resources/ssh/container.js +29 -0
- package/dist/lib/resources/stack/enrich.d.ts +2 -2
- package/dist/lib/resources/stack/enrich.js +1 -31
- package/dist/lib/units/PortMapping.d.ts +11 -0
- package/dist/lib/units/PortMapping.js +38 -0
- package/dist/lib/units/PortMapping.test.d.ts +1 -0
- package/dist/lib/units/PortMapping.test.js +28 -0
- package/dist/rendering/react/components/ErrorBox.js +4 -0
- package/package.json +67 -19
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ USAGE
|
|
|
107
107
|
* [`mw app`](docs/app.md) - Manage apps, and app installations in your projects
|
|
108
108
|
* [`mw autocomplete`](docs/autocomplete.md) - Display autocomplete installation instructions.
|
|
109
109
|
* [`mw backup`](docs/backup.md) - Manage backups of your projects
|
|
110
|
-
* [`mw container`](docs/container.md) -
|
|
110
|
+
* [`mw container`](docs/container.md) - Manage containers
|
|
111
111
|
* [`mw context`](docs/context.md) - Save certain environment parameters for later use
|
|
112
112
|
* [`mw conversation`](docs/conversation.md) - Manage your support cases
|
|
113
113
|
* [`mw cronjob`](docs/cronjob.md) - Manage cronjobs of your projects
|
|
@@ -14,6 +14,7 @@ export declare class List extends ListBaseCommand<typeof List, ResponseItem, Res
|
|
|
14
14
|
"no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
15
|
"no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
16
|
"csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
|
|
17
|
+
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
18
|
};
|
|
18
19
|
protected sorter: SorterFunction<ResponseItem>;
|
|
19
20
|
getData(): Promise<Response>;
|
|
@@ -16,6 +16,7 @@ export declare class Versions extends ListBaseCommand<typeof Versions, ResponseI
|
|
|
16
16
|
"no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
17
|
"no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
18
|
"csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
|
|
19
|
+
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
20
|
};
|
|
20
21
|
sorter: SorterFunction<ResponseItem>;
|
|
21
22
|
getData(): Promise<Response>;
|
package/dist/commands/app/get.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { RenderBaseCommand } from "../../lib/basecommands/RenderBaseCommand.js";
|
|
3
3
|
import { RenderJson } from "../../rendering/react/json/RenderJson.js";
|
|
4
|
-
import { GetBaseCommand } from "../../lib/basecommands/GetBaseCommand.js";
|
|
5
4
|
import { AppInstallationDetails } from "../../rendering/react/components/AppInstallation/AppInstallationDetails.js";
|
|
6
5
|
import { useApp, useAppInstallation } from "../../lib/resources/app/hooks.js";
|
|
7
6
|
import { appInstallationArgs } from "../../lib/resources/app/flags.js";
|
|
8
7
|
export default class Get extends RenderBaseCommand {
|
|
9
8
|
static description = "Get details about an app installation";
|
|
10
|
-
static flags = { ...
|
|
9
|
+
static flags = { ...RenderBaseCommand.buildFlags() };
|
|
11
10
|
static args = { ...appInstallationArgs };
|
|
12
11
|
render() {
|
|
13
12
|
const appInstallationId = this.useAppInstallationId(Get);
|
|
@@ -16,6 +16,7 @@ export default class List extends ListBaseCommand<typeof List, ResponseItem, Res
|
|
|
16
16
|
"no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
17
|
"no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
18
|
"csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
|
|
19
|
+
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
20
|
};
|
|
20
21
|
protected getData(): Promise<Response>;
|
|
21
22
|
protected mapData(data: ResponseItem[]): Promise<ResponseItem[]>;
|
|
@@ -22,6 +22,7 @@ export default class List extends ListBaseCommand<typeof List, ResponseItem, Res
|
|
|
22
22
|
"no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
23
23
|
"no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
24
24
|
"csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
|
|
25
|
+
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
25
26
|
};
|
|
26
27
|
protected getData(): Promise<Response>;
|
|
27
28
|
protected mapData(data: SuccessfulResponse<Response, 200>["data"]): Promise<ExtendedResponseItem[]>;
|
|
@@ -14,5 +14,19 @@ export declare class UpgradeApp extends ExecRenderBaseCommand<typeof UpgradeApp,
|
|
|
14
14
|
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
15
|
};
|
|
16
16
|
protected exec(): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Determines the target application version based on the provided input and
|
|
19
|
+
* available upgrade candidates.
|
|
20
|
+
*
|
|
21
|
+
* @param currentApp The current application instance.
|
|
22
|
+
* @param currentAppVersion The current version of the application.
|
|
23
|
+
* @param targetAppVersionCandidates List of potential target application
|
|
24
|
+
* versions.
|
|
25
|
+
* @param process The process renderer to handle user interactions and display
|
|
26
|
+
* information.
|
|
27
|
+
* @returns The determined target application version, or undefined if not
|
|
28
|
+
* resolved.
|
|
29
|
+
*/
|
|
30
|
+
private determineTargetAppVersion;
|
|
17
31
|
protected render(): ReactNode;
|
|
18
32
|
}
|
|
@@ -11,7 +11,7 @@ import { Success } from "../../rendering/react/components/Success.js";
|
|
|
11
11
|
import { waitUntilAppStateHasNormalized } from "../../lib/resources/app/wait.js";
|
|
12
12
|
import { assertStatus } from "@mittwald/api-client-commons";
|
|
13
13
|
import { waitFlags } from "../../lib/wait.js";
|
|
14
|
-
import semver from "semver
|
|
14
|
+
import semver from "semver";
|
|
15
15
|
export class UpgradeApp extends ExecRenderBaseCommand {
|
|
16
16
|
static description = "Upgrade app installation to target version";
|
|
17
17
|
static args = {
|
|
@@ -19,11 +19,11 @@ export class UpgradeApp extends ExecRenderBaseCommand {
|
|
|
19
19
|
};
|
|
20
20
|
static flags = {
|
|
21
21
|
"target-version": Flags.string({
|
|
22
|
-
description: "target version to upgrade app to; if omitted, target version will be prompted interactively",
|
|
22
|
+
description: "target version to upgrade app to; if omitted, target version will be prompted interactively. May also be a semantic versioning range, e.g. ^1.0.0. If set to 'latest', the latest available version will be used.",
|
|
23
23
|
}),
|
|
24
24
|
force: Flags.boolean({
|
|
25
25
|
char: "f",
|
|
26
|
-
|
|
26
|
+
summary: "do not ask for confirmation.",
|
|
27
27
|
}),
|
|
28
28
|
...projectFlags,
|
|
29
29
|
...processFlags,
|
|
@@ -43,25 +43,7 @@ export class UpgradeApp extends ExecRenderBaseCommand {
|
|
|
43
43
|
process.complete(_jsxs(Text, { children: ["Your ", currentApp.name, " ", currentAppVersion.externalVersion, " is already Up-To-Date. \u2705"] }));
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
if (this.flags["target-version"] == "latest") {
|
|
48
|
-
targetAppVersion =
|
|
49
|
-
(await getLatestAvailableTargetAppVersionForAppVersionUpgradeCandidates(this.apiClient, currentApp.id, currentAppVersion.id));
|
|
50
|
-
}
|
|
51
|
-
else if (this.flags["target-version"]) {
|
|
52
|
-
const targetVersionMatchFromCandidates = targetAppVersionCandidates.find((targetAppVersionCandidate) => targetAppVersionCandidate.externalVersion ===
|
|
53
|
-
this.flags["target-version"]);
|
|
54
|
-
if (targetVersionMatchFromCandidates) {
|
|
55
|
-
targetAppVersion = targetVersionMatchFromCandidates;
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
process.addInfo(_jsx(Text, { children: "The given target upgrade version does not seem to be a valid upgrade candidate." }));
|
|
59
|
-
targetAppVersion = (await forceTargetVersionSelection(process, this.apiClient, targetAppVersionCandidates, currentApp, currentAppVersion));
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
targetAppVersion = (await forceTargetVersionSelection(process, this.apiClient, targetAppVersionCandidates, currentApp, currentAppVersion));
|
|
64
|
-
}
|
|
46
|
+
const targetAppVersion = await this.determineTargetAppVersion(currentApp, currentAppVersion, targetAppVersionCandidates, process);
|
|
65
47
|
if (!targetAppVersion) {
|
|
66
48
|
process.error("Target app version could not be determined properly.");
|
|
67
49
|
ux.exit(1);
|
|
@@ -121,6 +103,37 @@ export class UpgradeApp extends ExecRenderBaseCommand {
|
|
|
121
103
|
}
|
|
122
104
|
await process.complete(_jsx(Success, { children: successText }));
|
|
123
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Determines the target application version based on the provided input and
|
|
108
|
+
* available upgrade candidates.
|
|
109
|
+
*
|
|
110
|
+
* @param currentApp The current application instance.
|
|
111
|
+
* @param currentAppVersion The current version of the application.
|
|
112
|
+
* @param targetAppVersionCandidates List of potential target application
|
|
113
|
+
* versions.
|
|
114
|
+
* @param process The process renderer to handle user interactions and display
|
|
115
|
+
* information.
|
|
116
|
+
* @returns The determined target application version, or undefined if not
|
|
117
|
+
* resolved.
|
|
118
|
+
*/
|
|
119
|
+
async determineTargetAppVersion(currentApp, currentAppVersion, targetAppVersionCandidates, process) {
|
|
120
|
+
const targetAppVersionString = this.flags["target-version"];
|
|
121
|
+
if (targetAppVersionString == "latest") {
|
|
122
|
+
return await getLatestAvailableTargetAppVersionForAppVersionUpgradeCandidates(this.apiClient, currentApp.id, currentAppVersion.id);
|
|
123
|
+
}
|
|
124
|
+
if (targetAppVersionString) {
|
|
125
|
+
const exactVersionMatch = targetAppVersionCandidates.find((v) => v.externalVersion === targetAppVersionString);
|
|
126
|
+
if (exactVersionMatch) {
|
|
127
|
+
return exactVersionMatch;
|
|
128
|
+
}
|
|
129
|
+
const semverMatch = targetAppVersionCandidates.findLast((v) => semver.satisfies(v.externalVersion, targetAppVersionString));
|
|
130
|
+
if (semverMatch) {
|
|
131
|
+
return semverMatch;
|
|
132
|
+
}
|
|
133
|
+
process.addInfo(_jsx(Text, { children: "The given target upgrade version does not seem to be a valid upgrade candidate." }));
|
|
134
|
+
}
|
|
135
|
+
return await forceTargetVersionSelection(process, this.apiClient, targetAppVersionCandidates, currentApp, currentAppVersion);
|
|
136
|
+
}
|
|
124
137
|
render() {
|
|
125
138
|
return true;
|
|
126
139
|
}
|
|
@@ -5,6 +5,7 @@ export declare class Delete extends DeleteBaseCommand<typeof Delete> {
|
|
|
5
5
|
static flags: {
|
|
6
6
|
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
7
|
quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
9
|
};
|
|
9
10
|
static args: {
|
|
10
11
|
"backup-id": import("@oclif/core/interfaces").Arg<string>;
|
|
@@ -3,7 +3,6 @@ import { RenderBaseCommand } from "../../lib/basecommands/RenderBaseCommand.js";
|
|
|
3
3
|
import { useProjectBackup } from "../../lib/resources/projectbackup/hooks.js";
|
|
4
4
|
import { ProjectBackupDetails } from "../../rendering/react/components/ProjectBackup/ProjectBackupDetails.js";
|
|
5
5
|
import { RenderJson } from "../../rendering/react/json/RenderJson.js";
|
|
6
|
-
import { GetBaseCommand } from "../../lib/basecommands/GetBaseCommand.js";
|
|
7
6
|
import { Box } from "ink";
|
|
8
7
|
import { backupArgs, withBackupId } from "../../lib/resources/backup/flags.js";
|
|
9
8
|
import { usePromise } from "@mittwald/react-use-promise";
|
|
@@ -11,7 +10,7 @@ export default class Get extends RenderBaseCommand {
|
|
|
11
10
|
static description = "Show details of a backup.";
|
|
12
11
|
static args = { ...backupArgs };
|
|
13
12
|
static flags = {
|
|
14
|
-
...
|
|
13
|
+
...RenderBaseCommand.buildFlags(),
|
|
15
14
|
};
|
|
16
15
|
static aliases = ["project:backup:get"];
|
|
17
16
|
static deprecateAliases = true;
|
|
@@ -16,6 +16,7 @@ export declare class List extends ListBaseCommand<typeof List, ListItem, ListRes
|
|
|
16
16
|
"no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
17
|
"no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
18
|
"csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
|
|
19
|
+
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
20
|
};
|
|
20
21
|
static aliases: string[];
|
|
21
22
|
static deprecateAliases: boolean;
|
|
@@ -16,6 +16,7 @@ export declare class List extends ListBaseCommand<typeof List, ResponseItem, Res
|
|
|
16
16
|
"no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
17
|
"no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
18
|
"csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
|
|
19
|
+
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
20
|
};
|
|
20
21
|
static aliases: string[];
|
|
21
22
|
static deprecateAliases: boolean;
|
|
@@ -7,6 +7,7 @@ export default class Delete extends DeleteBaseCommand<typeof Delete> {
|
|
|
7
7
|
"project-id": import("@oclif/core/interfaces").OptionFlag<string>;
|
|
8
8
|
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
9
|
quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
};
|
|
11
12
|
static args: {
|
|
12
13
|
"container-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ExtendedBaseCommand } from "../../lib/basecommands/ExtendedBaseCommand.js";
|
|
2
|
+
export default class Exec extends ExtendedBaseCommand<typeof Exec> {
|
|
3
|
+
static summary: string;
|
|
4
|
+
static description: string;
|
|
5
|
+
static args: {
|
|
6
|
+
"container-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
command: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
workdir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
env: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
shell: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
"project-id": import("@oclif/core/interfaces").OptionFlag<string>;
|
|
14
|
+
"ssh-user": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
"ssh-identity-file": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Prepare environment variables for the SSH command
|
|
19
|
+
*
|
|
20
|
+
* @param envVars Array of environment variables in KEY=VALUE format
|
|
21
|
+
* @returns Formatted string with export commands
|
|
22
|
+
*/
|
|
23
|
+
private prepareEnvironmentVariables;
|
|
24
|
+
run(): Promise<void>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as child_process from "child_process";
|
|
2
|
+
import { Args, Flags } from "@oclif/core";
|
|
3
|
+
import { ExtendedBaseCommand } from "../../lib/basecommands/ExtendedBaseCommand.js";
|
|
4
|
+
import { getSSHConnectionForContainer } from "../../lib/resources/ssh/container.js";
|
|
5
|
+
import { sshConnectionFlags } from "../../lib/resources/ssh/flags.js";
|
|
6
|
+
import { sshUsageDocumentation } from "../../lib/resources/ssh/doc.js";
|
|
7
|
+
import { buildSSHClientFlags } from "../../lib/resources/ssh/connection.js";
|
|
8
|
+
import { withContainerAndStackId } from "../../lib/resources/container/flags.js";
|
|
9
|
+
import { projectFlags } from "../../lib/resources/project/flags.js";
|
|
10
|
+
import shellEscape from "shell-escape";
|
|
11
|
+
export default class Exec extends ExtendedBaseCommand {
|
|
12
|
+
static summary = "Execute a command in a container via SSH non-interactively.";
|
|
13
|
+
static description = sshUsageDocumentation;
|
|
14
|
+
static args = {
|
|
15
|
+
"container-id": Args.string({
|
|
16
|
+
description: "ID or short ID of the container to connect to",
|
|
17
|
+
required: true,
|
|
18
|
+
}),
|
|
19
|
+
command: Args.string({
|
|
20
|
+
description: "Command to execute in the container",
|
|
21
|
+
required: true,
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
static flags = {
|
|
25
|
+
...sshConnectionFlags,
|
|
26
|
+
...projectFlags,
|
|
27
|
+
workdir: Flags.string({
|
|
28
|
+
char: "w",
|
|
29
|
+
summary: "working directory where the command will be executed",
|
|
30
|
+
default: undefined,
|
|
31
|
+
}),
|
|
32
|
+
env: Flags.string({
|
|
33
|
+
char: "e",
|
|
34
|
+
summary: "environment variables to set for the command (format: KEY=VALUE)",
|
|
35
|
+
multiple: true,
|
|
36
|
+
}),
|
|
37
|
+
shell: Flags.string({
|
|
38
|
+
summary: "shell to use for the SSH connection",
|
|
39
|
+
default: "/bin/sh",
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Prepare environment variables for the SSH command
|
|
44
|
+
*
|
|
45
|
+
* @param envVars Array of environment variables in KEY=VALUE format
|
|
46
|
+
* @returns Formatted string with export commands
|
|
47
|
+
*/
|
|
48
|
+
prepareEnvironmentVariables(envVars) {
|
|
49
|
+
return (envVars
|
|
50
|
+
.map((env) => {
|
|
51
|
+
const eqIdx = env.indexOf("=");
|
|
52
|
+
if (eqIdx === -1) {
|
|
53
|
+
// If no '=', treat the whole string as key with empty value
|
|
54
|
+
return `export ${shellEscape([env])}=`;
|
|
55
|
+
}
|
|
56
|
+
const key = env.slice(0, eqIdx);
|
|
57
|
+
const value = env.slice(eqIdx + 1);
|
|
58
|
+
return `export ${shellEscape([key])}=${shellEscape([value])}`;
|
|
59
|
+
})
|
|
60
|
+
.join("; ") + "; ");
|
|
61
|
+
}
|
|
62
|
+
async run() {
|
|
63
|
+
const { args, flags } = await this.parse(Exec);
|
|
64
|
+
const [containerId, stackId] = await withContainerAndStackId(this.apiClient, Exec, flags, this.args, this.config);
|
|
65
|
+
const { host, user } = await getSSHConnectionForContainer(this.apiClient, containerId, stackId, flags["ssh-user"]);
|
|
66
|
+
this.log("executing command on %s as %s", host, user);
|
|
67
|
+
const command = args.command;
|
|
68
|
+
const workdir = flags.workdir;
|
|
69
|
+
// Build the command to execute
|
|
70
|
+
let execCommand = "";
|
|
71
|
+
// Add environment variables if provided
|
|
72
|
+
if (flags.env && flags.env.length > 0) {
|
|
73
|
+
execCommand += this.prepareEnvironmentVariables(flags.env);
|
|
74
|
+
}
|
|
75
|
+
// Change to working directory if specified
|
|
76
|
+
if (workdir !== undefined) {
|
|
77
|
+
execCommand += `cd ${shellEscape([workdir])} && `;
|
|
78
|
+
}
|
|
79
|
+
// Add the actual command
|
|
80
|
+
execCommand += command;
|
|
81
|
+
const sshArgs = buildSSHClientFlags(user, host, flags, {
|
|
82
|
+
interactive: false,
|
|
83
|
+
});
|
|
84
|
+
const wrappedExecCommand = shellEscape([flags.shell, "-c", execCommand]);
|
|
85
|
+
this.debug("running ssh %o, with command %o", sshArgs, wrappedExecCommand);
|
|
86
|
+
const result = child_process.spawnSync("/usr/bin/ssh", [...sshArgs, wrappedExecCommand], {
|
|
87
|
+
stdio: "inherit",
|
|
88
|
+
});
|
|
89
|
+
if (result.status !== 0) {
|
|
90
|
+
this.error(`Command failed with exit code ${result.status}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -16,6 +16,7 @@ export declare class List extends ListBaseCommand<typeof List, ResponseItem, Res
|
|
|
16
16
|
"no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
17
|
"no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
18
|
"csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
|
|
19
|
+
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
20
|
};
|
|
20
21
|
getData(): Promise<Response>;
|
|
21
22
|
protected getColumns(data: ResponseItem[]): ListColumns<ResponseItem>;
|
|
@@ -6,6 +6,7 @@ export declare class Logs extends BaseCommand {
|
|
|
6
6
|
static flags: {
|
|
7
7
|
"no-pager": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
8
|
"project-id": import("@oclif/core/interfaces").OptionFlag<string>;
|
|
9
|
+
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
10
|
};
|
|
10
11
|
static args: {
|
|
11
12
|
"container-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
import PortMapping from "../../lib/units/PortMapping.js";
|
|
4
|
+
export declare class PortForward extends ExecRenderBaseCommand<typeof PortForward, Record<string, never>> {
|
|
5
|
+
static summary: string;
|
|
6
|
+
static description: string;
|
|
7
|
+
static flags: {
|
|
8
|
+
"project-id": import("@oclif/core/interfaces").OptionFlag<string>;
|
|
9
|
+
"ssh-user": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
"ssh-identity-file": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
static args: {
|
|
14
|
+
"container-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
15
|
+
port: import("@oclif/core/interfaces").Arg<PortMapping | undefined, Record<string, unknown>>;
|
|
16
|
+
};
|
|
17
|
+
protected exec(): Promise<Record<string, never>>;
|
|
18
|
+
private getPortMappings;
|
|
19
|
+
protected render(): ReactNode;
|
|
20
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js";
|
|
3
|
+
import { makeProcessRenderer, processFlags, } from "../../rendering/process/process_flags.js";
|
|
4
|
+
import * as cp from "child_process";
|
|
5
|
+
import { Box, Text } from "ink";
|
|
6
|
+
import { Value } from "../../rendering/react/components/Value.js";
|
|
7
|
+
import { Args } from "@oclif/core";
|
|
8
|
+
import { sshConnectionFlags } from "../../lib/resources/ssh/flags.js";
|
|
9
|
+
import { sshUsageDocumentation } from "../../lib/resources/ssh/doc.js";
|
|
10
|
+
import { buildSSHClientFlags } from "../../lib/resources/ssh/connection.js";
|
|
11
|
+
import { withContainerAndStackId } from "../../lib/resources/container/flags.js";
|
|
12
|
+
import { getSSHConnectionForContainer } from "../../lib/resources/ssh/container.js";
|
|
13
|
+
import { projectFlags } from "../../lib/resources/project/flags.js";
|
|
14
|
+
import PortMapping from "../../lib/units/PortMapping.js";
|
|
15
|
+
import { assertStatus } from "@mittwald/api-client";
|
|
16
|
+
export class PortForward extends ExecRenderBaseCommand {
|
|
17
|
+
static summary = "Forward a container port to a local port";
|
|
18
|
+
static description = "This command forwards a TCP port from a container to a local port on your machine. This allows you to connect to services running in the container as if they were running on your local machine.\n\n" +
|
|
19
|
+
sshUsageDocumentation;
|
|
20
|
+
static flags = {
|
|
21
|
+
...processFlags,
|
|
22
|
+
...sshConnectionFlags,
|
|
23
|
+
...projectFlags,
|
|
24
|
+
};
|
|
25
|
+
static args = {
|
|
26
|
+
"container-id": Args.string({
|
|
27
|
+
description: "ID or short ID of the container to connect to",
|
|
28
|
+
required: true,
|
|
29
|
+
}),
|
|
30
|
+
port: PortMapping.arg({
|
|
31
|
+
summary: "Port mapping in the format 'local-port:container-port'",
|
|
32
|
+
description: "Specifies the port mapping between your local machine and the container. Format: 'local-port:container-port'. If not specified, available ports will be detected automatically.",
|
|
33
|
+
required: false,
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
async exec() {
|
|
37
|
+
const [serviceId, stackId] = await withContainerAndStackId(this.apiClient, PortForward, this.flags, this.args, this.config);
|
|
38
|
+
const p = makeProcessRenderer(this.flags, "Port-forwarding a container");
|
|
39
|
+
const { host, user } = await getSSHConnectionForContainer(this.apiClient, serviceId, stackId, this.flags["ssh-user"]);
|
|
40
|
+
const portMappings = await this.getPortMappings(stackId, serviceId);
|
|
41
|
+
await p.complete(_jsxs(Box, { flexDirection: "column", children: [portMappings.map((p, idx) => (_jsxs(Text, { children: ["Forwarding container port ", _jsx(Value, { children: p.remotePort }), " to local port ", _jsx(Value, { children: p.localPort }), "."] }, idx))), _jsx(Text, { children: "Use CTRL+C to cancel." })] }));
|
|
42
|
+
const sshArgs = buildSSHClientFlags(user, host, this.flags, {
|
|
43
|
+
interactive: false,
|
|
44
|
+
additionalFlags: portMappings
|
|
45
|
+
.map((p) => ["-L", `${p.localPort}:localhost:${p.remotePort}`])
|
|
46
|
+
.flat(),
|
|
47
|
+
});
|
|
48
|
+
cp.spawnSync("ssh", [...sshArgs, "cat", "/dev/zero"], {
|
|
49
|
+
stdio: ["ignore", process.stdout, process.stderr],
|
|
50
|
+
});
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
async getPortMappings(stackId, serviceId) {
|
|
54
|
+
if (this.args.port) {
|
|
55
|
+
return [this.args.port];
|
|
56
|
+
}
|
|
57
|
+
const containerResponse = await this.apiClient.container.getService({
|
|
58
|
+
stackId,
|
|
59
|
+
serviceId,
|
|
60
|
+
});
|
|
61
|
+
assertStatus(containerResponse, 200);
|
|
62
|
+
const ports = containerResponse.data.deployedState.ports ?? [];
|
|
63
|
+
return ports.map((p) => PortMapping.fromPortAndProtocol(p));
|
|
64
|
+
}
|
|
65
|
+
render() {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -5,6 +5,8 @@ type Result = {
|
|
|
5
5
|
};
|
|
6
6
|
export declare class Run extends ExecRenderBaseCommand<typeof Run, Result> {
|
|
7
7
|
static summary: string;
|
|
8
|
+
static strict: boolean;
|
|
9
|
+
static usage: string;
|
|
8
10
|
static flags: {
|
|
9
11
|
env: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
12
|
"env-file": import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -24,6 +26,16 @@ export declare class Run extends ExecRenderBaseCommand<typeof Run, Result> {
|
|
|
24
26
|
};
|
|
25
27
|
protected exec(): Promise<Result>;
|
|
26
28
|
private addServiceToStack;
|
|
29
|
+
/**
|
|
30
|
+
* Builds and returns the container command based on the provided image
|
|
31
|
+
* metadata and arguments.
|
|
32
|
+
*
|
|
33
|
+
* @param imageMeta The configuration object containing the metadata of the
|
|
34
|
+
* container image, including the default command.
|
|
35
|
+
* @returns An array of strings representing the container command to execute,
|
|
36
|
+
* or undefined if no specific command is set.
|
|
37
|
+
*/
|
|
38
|
+
private buildContainerCommand;
|
|
27
39
|
/**
|
|
28
40
|
* Builds a container service request from command line arguments and image
|
|
29
41
|
* metadata
|
|
@@ -3,13 +3,16 @@ import { Args, Flags } from "@oclif/core";
|
|
|
3
3
|
import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js";
|
|
4
4
|
import { makeProcessRenderer, processFlags, } from "../../rendering/process/process_flags.js";
|
|
5
5
|
import { projectFlags } from "../../lib/resources/project/flags.js";
|
|
6
|
+
import dockerNames from "docker-names";
|
|
7
|
+
import { assertStatus } from "@mittwald/api-client";
|
|
8
|
+
import { getImageMeta, getPortMappings, parseEnvironmentVariables, } from "../../lib/resources/container/containerconfig.js";
|
|
6
9
|
import { Success } from "../../rendering/react/components/Success.js";
|
|
7
10
|
import { Value } from "../../rendering/react/components/Value.js";
|
|
8
|
-
import * as dockerNames from "docker-names";
|
|
9
|
-
import { assertStatus } from "@mittwald/api-client";
|
|
10
|
-
import { parseEnvironmentVariables, getPortMappings, getImageMeta, } from "../../lib/resources/container/containerconfig.js";
|
|
11
11
|
export class Run extends ExecRenderBaseCommand {
|
|
12
12
|
static summary = "Creates and starts a new container.";
|
|
13
|
+
static strict = false;
|
|
14
|
+
// Usage needs to be overwritten because the autogenerated one is incorrect due to the variadic arguments.
|
|
15
|
+
static usage = "container run [--token <value>] [-q] [-p <value>] [-e <value>...] [--env-file <value>...] [--description <value>] [--entrypoint <value>] [--name <value>] [-p <value>...] [-P] [-v <value>...] IMAGE [COMMAND] [ARGS...]";
|
|
13
16
|
static flags = {
|
|
14
17
|
...processFlags,
|
|
15
18
|
...projectFlags,
|
|
@@ -45,11 +48,11 @@ export class Run extends ExecRenderBaseCommand {
|
|
|
45
48
|
summary: "publish a container's port(s) to the host",
|
|
46
49
|
description: "Map a container's port to a port on the host system. " +
|
|
47
50
|
"Format: <host-port>:<container-port> or just <container-port> (in which case the host port will be automatically assigned). " +
|
|
48
|
-
"For example,
|
|
49
|
-
"Use multiple
|
|
51
|
+
"For example, --publish 8080:80 maps port 80 in the container to port 8080 on the host. " +
|
|
52
|
+
"Use multiple --publish flags to publish multiple ports.\n\n" +
|
|
53
|
+
"NOTE: Please note that the usual shorthand -p is not supported for this flag, as it would conflict with the --project flag.",
|
|
50
54
|
required: false,
|
|
51
55
|
multiple: true,
|
|
52
|
-
char: "p",
|
|
53
56
|
}),
|
|
54
57
|
"publish-all": Flags.boolean({
|
|
55
58
|
summary: "publish all ports that are defined in the image",
|
|
@@ -85,7 +88,6 @@ export class Run extends ExecRenderBaseCommand {
|
|
|
85
88
|
summary: "arguments to pass to the command",
|
|
86
89
|
description: "These are the runtime arguments passed to the command specified by the command parameter or the container's default command, not to the container itself. For example, if the command is 'echo', the args might be 'hello world'.",
|
|
87
90
|
required: false,
|
|
88
|
-
variadic: true,
|
|
89
91
|
}),
|
|
90
92
|
};
|
|
91
93
|
async exec() {
|
|
@@ -116,6 +118,23 @@ export class Run extends ExecRenderBaseCommand {
|
|
|
116
118
|
assertStatus(resp, 200);
|
|
117
119
|
return resp.data;
|
|
118
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Builds and returns the container command based on the provided image
|
|
123
|
+
* metadata and arguments.
|
|
124
|
+
*
|
|
125
|
+
* @param imageMeta The configuration object containing the metadata of the
|
|
126
|
+
* container image, including the default command.
|
|
127
|
+
* @returns An array of strings representing the container command to execute,
|
|
128
|
+
* or undefined if no specific command is set.
|
|
129
|
+
*/
|
|
130
|
+
buildContainerCommand(imageMeta) {
|
|
131
|
+
if (!this.args.command) {
|
|
132
|
+
return imageMeta.command;
|
|
133
|
+
}
|
|
134
|
+
const firstArg = (this.argv.lastIndexOf(this.args.command) ?? Infinity) + 1;
|
|
135
|
+
const command = [this.args.command, ...this.argv.slice(firstArg)];
|
|
136
|
+
return command;
|
|
137
|
+
}
|
|
119
138
|
/**
|
|
120
139
|
* Builds a container service request from command line arguments and image
|
|
121
140
|
* metadata
|
|
@@ -126,7 +145,7 @@ export class Run extends ExecRenderBaseCommand {
|
|
|
126
145
|
* @returns A properly formatted container service request
|
|
127
146
|
*/
|
|
128
147
|
async buildServiceRequest(image, imageMeta, serviceName) {
|
|
129
|
-
const command = this.
|
|
148
|
+
const command = this.buildContainerCommand(imageMeta);
|
|
130
149
|
const entrypoint = this.flags.entrypoint
|
|
131
150
|
? [this.flags.entrypoint]
|
|
132
151
|
: imageMeta.entrypoint;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ExtendedBaseCommand } from "../../lib/basecommands/ExtendedBaseCommand.js";
|
|
2
|
+
export default class Ssh extends ExtendedBaseCommand<typeof Ssh> {
|
|
3
|
+
static summary: string;
|
|
4
|
+
static description: string;
|
|
5
|
+
static args: {
|
|
6
|
+
"container-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
info: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
test: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
shell: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
"project-id": import("@oclif/core/interfaces").OptionFlag<string>;
|
|
13
|
+
"ssh-user": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
"ssh-identity-file": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as child_process from "child_process";
|
|
2
|
+
import { Args, Flags } from "@oclif/core";
|
|
3
|
+
import { ExtendedBaseCommand } from "../../lib/basecommands/ExtendedBaseCommand.js";
|
|
4
|
+
import { getSSHConnectionForContainer } from "../../lib/resources/ssh/container.js";
|
|
5
|
+
import { sshConnectionFlags, } from "../../lib/resources/ssh/flags.js";
|
|
6
|
+
import { sshWrapperDocumentation } from "../../lib/resources/ssh/doc.js";
|
|
7
|
+
import { buildSSHClientFlags } from "../../lib/resources/ssh/connection.js";
|
|
8
|
+
import { withContainerAndStackId } from "../../lib/resources/container/flags.js";
|
|
9
|
+
import { projectFlags } from "../../lib/resources/project/flags.js";
|
|
10
|
+
export default class Ssh extends ExtendedBaseCommand {
|
|
11
|
+
static summary = "Connect to a container via SSH";
|
|
12
|
+
static description = "Establishes an interactive SSH connection to a container.\n\n" +
|
|
13
|
+
sshWrapperDocumentation;
|
|
14
|
+
static args = {
|
|
15
|
+
"container-id": Args.string({
|
|
16
|
+
description: "ID or short ID of the container to connect to",
|
|
17
|
+
required: true,
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
static flags = {
|
|
21
|
+
...sshConnectionFlags,
|
|
22
|
+
...projectFlags,
|
|
23
|
+
info: Flags.boolean({
|
|
24
|
+
summary: "only print connection information, without actually connecting",
|
|
25
|
+
}),
|
|
26
|
+
test: Flags.boolean({
|
|
27
|
+
summary: "test connection and exit",
|
|
28
|
+
}),
|
|
29
|
+
shell: Flags.string({
|
|
30
|
+
summary: "shell to use for the SSH connection",
|
|
31
|
+
default: "/bin/sh",
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
async run() {
|
|
35
|
+
const { flags } = await this.parse(Ssh);
|
|
36
|
+
const [containerId, stackId] = await withContainerAndStackId(this.apiClient, Ssh, flags, this.args, this.config);
|
|
37
|
+
const { host, user, directory } = await getSSHConnectionForContainer(this.apiClient, containerId, stackId, flags["ssh-user"]);
|
|
38
|
+
if (flags.info) {
|
|
39
|
+
this.log("hostname: %o", host);
|
|
40
|
+
this.log("username: %o", user);
|
|
41
|
+
this.log("directory: %o", directory);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.log("connecting to %o as %o", host, user);
|
|
45
|
+
const [cmd, args] = buildSSHCmdAndFlags(user, host, flags);
|
|
46
|
+
this.debug("running ssh %o, with command %o", args, cmd);
|
|
47
|
+
child_process.spawnSync("/usr/bin/ssh", [...args, cmd], {
|
|
48
|
+
stdio: "inherit",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function buildSSHCmdAndFlags(user, host, flags) {
|
|
53
|
+
const args = buildSSHClientFlags(user, host, flags, {
|
|
54
|
+
interactive: true,
|
|
55
|
+
additionalFlags: flags.test ? ["-q"] : [],
|
|
56
|
+
});
|
|
57
|
+
if (flags.test) {
|
|
58
|
+
return ["/bin/true", args];
|
|
59
|
+
}
|
|
60
|
+
return [flags.shell, args];
|
|
61
|
+
}
|