@mittwald/cli 1.4.4 → 1.5.1

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.
Files changed (83) hide show
  1. package/README.md +3 -0
  2. package/dist/commands/container/delete.d.ts +15 -0
  3. package/dist/commands/container/delete.js +39 -0
  4. package/dist/commands/container/list.d.ts +23 -0
  5. package/dist/commands/container/list.js +53 -0
  6. package/dist/commands/container/logs.d.ts +14 -0
  7. package/dist/commands/container/logs.js +47 -0
  8. package/dist/commands/container/recreate.d.ts +20 -0
  9. package/dist/commands/container/recreate.js +72 -0
  10. package/dist/commands/container/restart.d.ts +18 -0
  11. package/dist/commands/container/restart.js +40 -0
  12. package/dist/commands/container/run.d.ts +41 -0
  13. package/dist/commands/container/run.js +167 -0
  14. package/dist/commands/container/start.d.ts +18 -0
  15. package/dist/commands/container/start.js +40 -0
  16. package/dist/commands/container/stop.d.ts +18 -0
  17. package/dist/commands/container/stop.js +40 -0
  18. package/dist/commands/container/update.d.ts +36 -0
  19. package/dist/commands/container/update.js +174 -0
  20. package/dist/commands/context/set.d.ts +1 -0
  21. package/dist/commands/context/set.js +8 -0
  22. package/dist/commands/cronjob/execution/logs.js +6 -24
  23. package/dist/commands/cronjob/list.d.ts +2 -3
  24. package/dist/commands/domain/virtualhost/create.d.ts +1 -0
  25. package/dist/commands/domain/virtualhost/create.js +12 -0
  26. package/dist/commands/extension/install.js +4 -3
  27. package/dist/commands/extension/list-installed.js +4 -3
  28. package/dist/commands/mail/address/create.d.ts +2 -1
  29. package/dist/commands/mail/address/create.js +2 -1
  30. package/dist/commands/org/membership/list-own.d.ts +3 -0
  31. package/dist/commands/org/membership/list.d.ts +3 -0
  32. package/dist/commands/registry/create.d.ts +20 -0
  33. package/dist/commands/registry/create.js +77 -0
  34. package/dist/commands/registry/delete.d.ts +13 -0
  35. package/dist/commands/registry/delete.js +21 -0
  36. package/dist/commands/registry/list.d.ts +23 -0
  37. package/dist/commands/registry/list.js +33 -0
  38. package/dist/commands/registry/update.d.ts +20 -0
  39. package/dist/commands/registry/update.js +73 -0
  40. package/dist/commands/stack/delete.d.ts +16 -0
  41. package/dist/commands/stack/delete.js +54 -0
  42. package/dist/commands/stack/deploy.d.ts +18 -0
  43. package/dist/commands/stack/deploy.js +75 -0
  44. package/dist/commands/stack/list.d.ts +24 -0
  45. package/dist/commands/stack/list.js +60 -0
  46. package/dist/commands/stack/ps.d.ts +23 -0
  47. package/dist/commands/stack/ps.js +51 -0
  48. package/dist/lib/basecommands/DeleteBaseCommand.js +1 -1
  49. package/dist/lib/basecommands/ExtendedBaseCommand.d.ts +1 -0
  50. package/dist/lib/basecommands/ExtendedBaseCommand.js +4 -0
  51. package/dist/lib/context/Context.d.ts +5 -2
  52. package/dist/lib/context/Context.js +10 -1
  53. package/dist/lib/context/FlagSetBuilder.d.ts +3 -4
  54. package/dist/lib/context/FlagSetBuilder.js +22 -15
  55. package/dist/lib/resources/container/containerconfig.d.ts +43 -0
  56. package/dist/lib/resources/container/containerconfig.js +82 -0
  57. package/dist/lib/resources/container/flags.d.ts +13 -0
  58. package/dist/lib/resources/container/flags.js +34 -0
  59. package/dist/lib/resources/org/flags.js +7 -1
  60. package/dist/lib/resources/server/flags.js +7 -1
  61. package/dist/lib/resources/stack/enrich.d.ts +4 -0
  62. package/dist/lib/resources/stack/enrich.js +55 -0
  63. package/dist/lib/resources/stack/env.d.ts +1 -0
  64. package/dist/lib/resources/stack/env.js +11 -0
  65. package/dist/lib/resources/stack/flags.d.ts +5 -0
  66. package/dist/lib/resources/stack/flags.js +2 -0
  67. package/dist/lib/resources/stack/loader.d.ts +5 -0
  68. package/dist/lib/resources/stack/loader.js +47 -0
  69. package/dist/lib/resources/stack/loader.test.d.ts +1 -0
  70. package/dist/lib/resources/stack/loader.test.js +51 -0
  71. package/dist/lib/resources/stack/sanitize.d.ts +10 -0
  72. package/dist/lib/resources/stack/sanitize.js +43 -0
  73. package/dist/lib/resources/stack/types.d.ts +12 -0
  74. package/dist/lib/resources/stack/types.js +1 -0
  75. package/dist/lib/util/pager.d.ts +7 -0
  76. package/dist/lib/util/pager.js +21 -0
  77. package/dist/rendering/process/process.d.ts +1 -1
  78. package/dist/rendering/process/process_fancy.d.ts +1 -1
  79. package/dist/rendering/process/process_fancy.js +2 -1
  80. package/dist/rendering/process/process_flags.js +1 -1
  81. package/dist/rendering/process/process_quiet.d.ts +1 -1
  82. package/dist/rendering/process/process_quiet.js +3 -0
  83. package/package.json +21 -17
package/README.md CHANGED
@@ -107,6 +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) - Delete a container
110
111
  * [`mw context`](docs/context.md) - Save certain environment parameters for later use
111
112
  * [`mw conversation`](docs/conversation.md) - Manage your support cases
112
113
  * [`mw cronjob`](docs/cronjob.md) - Manage cronjobs of your projects
@@ -119,9 +120,11 @@ USAGE
119
120
  * [`mw mail`](docs/mail.md) - Manage mailboxes and mail addresses in your projects
120
121
  * [`mw org`](docs/org.md) - Manage your organizations, and also any kinds of user memberships concerning these organizations.
121
122
  * [`mw project`](docs/project.md) - Manage your projects, and also any kinds of user memberships concerning these projects.
123
+ * [`mw registry`](docs/registry.md) - Manage container registries
122
124
  * [`mw server`](docs/server.md) - Manage your servers
123
125
  * [`mw sftp-user`](docs/sftp-user.md) - Manage SFTP users of your projects
124
126
  * [`mw ssh-user`](docs/ssh-user.md) - Manage SSH users of your projects
127
+ * [`mw stack`](docs/stack.md) - Manage container stacks
125
128
  * [`mw update`](docs/update.md) - update the mw CLI
126
129
  * [`mw user`](docs/user.md) - Manage your own user account
127
130
 
@@ -0,0 +1,15 @@
1
+ import { DeleteBaseCommand } from "../../lib/basecommands/DeleteBaseCommand.js";
2
+ export default class Delete extends DeleteBaseCommand<typeof Delete> {
3
+ static description: string;
4
+ static resourceName: string;
5
+ static aliases: string[];
6
+ static flags: {
7
+ "project-id": import("@oclif/core/interfaces").OptionFlag<string>;
8
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ static args: {
12
+ "container-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
13
+ };
14
+ protected deleteResource(): Promise<void>;
15
+ }
@@ -0,0 +1,39 @@
1
+ import { Args } from "@oclif/core";
2
+ import { projectFlags } from "../../lib/resources/project/flags.js";
3
+ import { withContainerAndStackId } from "../../lib/resources/container/flags.js";
4
+ import assertSuccess from "../../lib/apiutil/assert_success.js";
5
+ import { DeleteBaseCommand } from "../../lib/basecommands/DeleteBaseCommand.js";
6
+ import { assertStatus } from "@mittwald/api-client";
7
+ export default class Delete extends DeleteBaseCommand {
8
+ static description = "Delete a container";
9
+ static resourceName = "container";
10
+ static aliases = ["container:rm"];
11
+ static flags = {
12
+ ...DeleteBaseCommand.baseFlags,
13
+ ...projectFlags,
14
+ };
15
+ static args = {
16
+ "container-id": Args.string({
17
+ description: "ID or short ID of the container to start",
18
+ required: true,
19
+ }),
20
+ };
21
+ async deleteResource() {
22
+ const [serviceId, stackId] = await withContainerAndStackId(this.apiClient, Delete, this.flags, this.args, this.config);
23
+ const stackResponse = await this.apiClient.container.getStack({ stackId });
24
+ assertStatus(stackResponse, 200);
25
+ const service = stackResponse.data.services?.find((s) => s.id === serviceId);
26
+ if (!service) {
27
+ return;
28
+ }
29
+ const response = await this.apiClient.container.updateStack({
30
+ stackId,
31
+ data: {
32
+ services: {
33
+ [service.serviceName]: {},
34
+ },
35
+ },
36
+ });
37
+ assertSuccess(response);
38
+ }
39
+ }
@@ -0,0 +1,23 @@
1
+ import { Simplify } from "@mittwald/api-client-commons";
2
+ import { MittwaldAPIV2, MittwaldAPIV2Client } from "@mittwald/api-client";
3
+ import { ListBaseCommand } from "../../lib/basecommands/ListBaseCommand.js";
4
+ import { ListColumns } from "../../rendering/formatter/Table.js";
5
+ type ResponseItem = Simplify<MittwaldAPIV2.Paths.V2ProjectsProjectIdServices.Get.Responses.$200.Content.ApplicationJson[number]>;
6
+ export type Response = Awaited<ReturnType<MittwaldAPIV2Client["container"]["listServices"]>>;
7
+ export declare class List extends ListBaseCommand<typeof List, ResponseItem, Response> {
8
+ static description: string;
9
+ static aliases: string[];
10
+ static args: {};
11
+ static flags: {
12
+ "project-id": import("@oclif/core/interfaces").OptionFlag<string>;
13
+ output: import("@oclif/core/interfaces").OptionFlag<"json" | "txt" | "yaml" | "csv" | "tsv">;
14
+ extended: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ "no-header": import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ "no-truncate": import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ "no-relative-dates": import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
+ "csv-separator": import("@oclif/core/interfaces").OptionFlag<"," | ";">;
19
+ };
20
+ getData(): Promise<Response>;
21
+ protected getColumns(data: ResponseItem[]): ListColumns<ResponseItem>;
22
+ }
23
+ export {};
@@ -0,0 +1,53 @@
1
+ import { ListBaseCommand } from "../../lib/basecommands/ListBaseCommand.js";
2
+ import { projectFlags } from "../../lib/resources/project/flags.js";
3
+ import { makeDateRendererForFormat } from "../../rendering/textformat/formatDate.js";
4
+ export class List extends ListBaseCommand {
5
+ static description = "List containers belonging to a project.";
6
+ static aliases = ["container:ls"];
7
+ static args = {};
8
+ static flags = {
9
+ ...ListBaseCommand.baseFlags,
10
+ ...projectFlags,
11
+ };
12
+ async getData() {
13
+ const projectId = await this.withProjectId(List);
14
+ return this.apiClient.container.listServices({ projectId });
15
+ }
16
+ getColumns(data) {
17
+ const { id, shortId } = super.getColumns(data);
18
+ const dateFormatter = makeDateRendererForFormat(this.flags.output, !this.flags["no-relative-dates"]);
19
+ return {
20
+ id,
21
+ stackId: {
22
+ header: "Stack ID",
23
+ extended: true,
24
+ exactWidth: 40,
25
+ },
26
+ shortId,
27
+ name: {
28
+ get: (svc) => svc.serviceName,
29
+ },
30
+ image: {
31
+ get: (svc) => svc.deployedState.image,
32
+ },
33
+ command: {
34
+ get: (svc) => svc.deployedState.command?.join(" "),
35
+ },
36
+ description: {},
37
+ ports: {
38
+ get: (svc) => {
39
+ if (svc.deployedState.ports === undefined ||
40
+ svc.deployedState.ports.length === 0) {
41
+ return "no ports";
42
+ }
43
+ return svc.deployedState.ports.join(", ");
44
+ },
45
+ },
46
+ status: {
47
+ get(svc) {
48
+ return svc.status + " (" + dateFormatter(svc.statusSetAt) + ")";
49
+ },
50
+ },
51
+ };
52
+ }
53
+ }
@@ -0,0 +1,14 @@
1
+ import { BaseCommand } from "../../lib/basecommands/BaseCommand.js";
2
+ export declare class Logs extends BaseCommand {
3
+ static summary: string;
4
+ static description: string;
5
+ static aliases: string[];
6
+ static flags: {
7
+ "no-pager": import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ "project-id": import("@oclif/core/interfaces").OptionFlag<string>;
9
+ };
10
+ static args: {
11
+ "container-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,47 @@
1
+ import { BaseCommand } from "../../lib/basecommands/BaseCommand.js";
2
+ import { GetBaseCommand } from "../../lib/basecommands/GetBaseCommand.js";
3
+ import { Args, Flags } from "@oclif/core";
4
+ import { withContainerAndStackId } from "../../lib/resources/container/flags.js";
5
+ import { projectFlags } from "../../lib/resources/project/flags.js";
6
+ import { assertStatus } from "@mittwald/api-client";
7
+ import { printToPager } from "../../lib/util/pager.js";
8
+ export class Logs extends BaseCommand {
9
+ static summary = "Display logs of a specific container.";
10
+ static description = "This command prints the log output of a specific container. " +
11
+ "" +
12
+ 'When this command is run in a terminal, the output is piped through a pager. The pager is determined by your PAGER environment variable, with defaulting to "less". You can disable this behavior with the --no-pager flag.';
13
+ static aliases = ["container:ls"];
14
+ static flags = {
15
+ ...GetBaseCommand.baseFlags,
16
+ ...projectFlags,
17
+ "no-pager": Flags.boolean({
18
+ description: "Disable pager for output.",
19
+ }),
20
+ };
21
+ static args = {
22
+ "container-id": Args.string({
23
+ description: "ID of the container for which to get logs",
24
+ required: true,
25
+ }),
26
+ };
27
+ async run() {
28
+ const { flags, args } = await this.parse(Logs);
29
+ const [serviceId, stackId] = await withContainerAndStackId(this.apiClient, Logs, flags, args, this.config);
30
+ const usePager = process.stdin.isTTY && !flags["no-pager"];
31
+ const logsResp = await this.apiClient.container.getServiceLogs({
32
+ stackId,
33
+ serviceId,
34
+ });
35
+ assertStatus(logsResp, 200);
36
+ // This is to work around a bug which causes the response to
37
+ // "getServiceLogs" to contain extra NULL bytes.
38
+ // eslint-disable-next-line no-control-regex
39
+ const logs = logsResp.data.replace(/^\x00*/, "");
40
+ if (usePager) {
41
+ printToPager(logs);
42
+ }
43
+ else {
44
+ this.log(logs);
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,20 @@
1
+ import { ReactNode } from "react";
2
+ import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js";
3
+ type Result = {
4
+ serviceId: string;
5
+ };
6
+ export declare class Recreate extends ExecRenderBaseCommand<typeof Recreate, Result> {
7
+ static summary: string;
8
+ static flags: {
9
+ pull: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ "project-id": import("@oclif/core/interfaces").OptionFlag<string>;
12
+ quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ };
14
+ static args: {
15
+ "container-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
16
+ };
17
+ protected exec(): Promise<Result>;
18
+ protected render({ serviceId }: Result): ReactNode;
19
+ }
20
+ export {};
@@ -0,0 +1,72 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Args, Flags } from "@oclif/core";
3
+ import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js";
4
+ import { makeProcessRenderer, processFlags, } from "../../rendering/process/process_flags.js";
5
+ import { projectFlags } from "../../lib/resources/project/flags.js";
6
+ import { withContainerAndStackId } from "../../lib/resources/container/flags.js";
7
+ import assertSuccess from "../../lib/apiutil/assert_success.js";
8
+ import { Success } from "../../rendering/react/components/Success.js";
9
+ import { Value } from "../../rendering/react/components/Value.js";
10
+ import { assertStatus } from "@mittwald/api-client";
11
+ export class Recreate extends ExecRenderBaseCommand {
12
+ static summary = "Recreates a container.";
13
+ static flags = {
14
+ ...processFlags,
15
+ ...projectFlags,
16
+ pull: Flags.boolean({
17
+ description: "pull the container image before recreating the container",
18
+ }),
19
+ force: Flags.boolean({
20
+ description: "also recreate the container when it is already up to date",
21
+ }),
22
+ };
23
+ static args = {
24
+ "container-id": Args.string({
25
+ description: "ID or short ID of the container to restart",
26
+ required: true,
27
+ }),
28
+ };
29
+ async exec() {
30
+ const p = makeProcessRenderer(this.flags, "Recreating a container");
31
+ const { pull, force } = this.flags;
32
+ const [serviceId, stackId] = await withContainerAndStackId(this.apiClient, Recreate, this.flags, this.args, this.config);
33
+ if (pull) {
34
+ await p.runStep("pulling image and recreating", async () => {
35
+ const r = await this.apiClient.container.pullImageForService({
36
+ serviceId,
37
+ stackId,
38
+ });
39
+ assertSuccess(r);
40
+ });
41
+ }
42
+ else {
43
+ const service = await p.runStep("getting container status", async () => {
44
+ const r = await this.apiClient.container.getService({
45
+ serviceId,
46
+ stackId,
47
+ });
48
+ assertStatus(r, 200);
49
+ return r.data;
50
+ });
51
+ if (!service.requiresRecreate && !force) {
52
+ p.addInfo("service is already up to date");
53
+ }
54
+ else {
55
+ await p.runStep("recreating container", async () => {
56
+ const r = await this.apiClient.container.restartService({
57
+ serviceId,
58
+ stackId,
59
+ });
60
+ assertSuccess(r);
61
+ });
62
+ }
63
+ }
64
+ await p.complete(_jsxs(Success, { children: ["Container ", _jsx(Value, { children: serviceId }), " was successfully recreated."] }));
65
+ return { serviceId };
66
+ }
67
+ render({ serviceId }) {
68
+ if (this.flags.quiet) {
69
+ return serviceId;
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,18 @@
1
+ import { ReactNode } from "react";
2
+ import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js";
3
+ type Result = {
4
+ serviceId: string;
5
+ };
6
+ export declare class Restart extends ExecRenderBaseCommand<typeof Restart, Result> {
7
+ static summary: string;
8
+ static flags: {
9
+ "project-id": import("@oclif/core/interfaces").OptionFlag<string>;
10
+ quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ };
12
+ static args: {
13
+ "container-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
14
+ };
15
+ protected exec(): Promise<Result>;
16
+ protected render({ serviceId }: Result): ReactNode;
17
+ }
18
+ export {};
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Args } from "@oclif/core";
3
+ import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js";
4
+ import { makeProcessRenderer, processFlags, } from "../../rendering/process/process_flags.js";
5
+ import { projectFlags } from "../../lib/resources/project/flags.js";
6
+ import { withContainerAndStackId } from "../../lib/resources/container/flags.js";
7
+ import assertSuccess from "../../lib/apiutil/assert_success.js";
8
+ import { Success } from "../../rendering/react/components/Success.js";
9
+ import { Value } from "../../rendering/react/components/Value.js";
10
+ export class Restart extends ExecRenderBaseCommand {
11
+ static summary = "Restarts a container.";
12
+ static flags = {
13
+ ...processFlags,
14
+ ...projectFlags,
15
+ };
16
+ static args = {
17
+ "container-id": Args.string({
18
+ description: "ID or short ID of the container to restart",
19
+ required: true,
20
+ }),
21
+ };
22
+ async exec() {
23
+ const p = makeProcessRenderer(this.flags, "Restarting a container");
24
+ const [serviceId, stackId] = await withContainerAndStackId(this.apiClient, Restart, this.flags, this.args, this.config);
25
+ await p.runStep("restarting container", async () => {
26
+ const r = await this.apiClient.container.restartService({
27
+ serviceId,
28
+ stackId,
29
+ });
30
+ assertSuccess(r);
31
+ });
32
+ await p.complete(_jsxs(Success, { children: ["Container ", _jsx(Value, { children: serviceId }), " was successfully restarted."] }));
33
+ return { serviceId };
34
+ }
35
+ render({ serviceId }) {
36
+ if (this.flags.quiet) {
37
+ return serviceId;
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,41 @@
1
+ import { ReactNode } from "react";
2
+ import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js";
3
+ type Result = {
4
+ serviceId: string;
5
+ };
6
+ export declare class Run extends ExecRenderBaseCommand<typeof Run, Result> {
7
+ static summary: string;
8
+ static flags: {
9
+ env: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ "env-file": import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ entrypoint: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ publish: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ "publish-all": import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ volume: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ "project-id": import("@oclif/core/interfaces").OptionFlag<string>;
18
+ quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
19
+ };
20
+ static args: {
21
+ image: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
22
+ command: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
23
+ args: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
24
+ };
25
+ protected exec(): Promise<Result>;
26
+ private addServiceToStack;
27
+ /**
28
+ * Builds a container service request from command line arguments and image
29
+ * metadata
30
+ *
31
+ * @param image The container image to use
32
+ * @param imageMeta Metadata about the container image
33
+ * @param serviceName Name of the service to create
34
+ * @returns A properly formatted container service request
35
+ */
36
+ private buildServiceRequest;
37
+ private getImageAndMeta;
38
+ private getServiceName;
39
+ protected render({ serviceId }: Result): ReactNode;
40
+ }
41
+ export {};
@@ -0,0 +1,167 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Args, Flags } from "@oclif/core";
3
+ import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js";
4
+ import { makeProcessRenderer, processFlags, } from "../../rendering/process/process_flags.js";
5
+ import { projectFlags } from "../../lib/resources/project/flags.js";
6
+ import { Success } from "../../rendering/react/components/Success.js";
7
+ 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
+ export class Run extends ExecRenderBaseCommand {
12
+ static summary = "Creates and starts a new container.";
13
+ static flags = {
14
+ ...processFlags,
15
+ ...projectFlags,
16
+ env: Flags.string({
17
+ summary: "set environment variables in the container",
18
+ description: "Format: KEY=VALUE. Multiple environment variables can be specified with multiple --env flags.",
19
+ required: false,
20
+ multiple: true,
21
+ char: "e",
22
+ }),
23
+ "env-file": Flags.string({
24
+ summary: "read environment variables from a file",
25
+ description: "The file should contain lines in the format KEY=VALUE. Multiple files can be specified with multiple --env-file flags.",
26
+ multiple: true,
27
+ required: false,
28
+ }),
29
+ description: Flags.string({
30
+ summary: "add a descriptive label to the container",
31
+ description: "This helps identify the container's purpose or contents.",
32
+ required: false,
33
+ }),
34
+ entrypoint: Flags.string({
35
+ summary: "override the default entrypoint of the container image",
36
+ description: "The entrypoint is the command that will be executed when the container starts. If omitted, the entrypoint defined in the image will be used.",
37
+ required: false,
38
+ }),
39
+ name: Flags.string({
40
+ summary: "assign a custom name to the container",
41
+ description: "This makes it easier to reference the container in subsequent commands. If omitted, a random name will be generated automatically.",
42
+ required: false,
43
+ }),
44
+ publish: Flags.string({
45
+ summary: "publish a container's port(s) to the host",
46
+ description: "Map a container's port to a port on the host system. " +
47
+ "Format: <host-port>:<container-port> or just <container-port> (in which case the host port will be automatically assigned). " +
48
+ "For example, -p 8080:80 maps port 80 in the container to port 8080 on the host. " +
49
+ "Use multiple -p flags to publish multiple ports.",
50
+ required: false,
51
+ multiple: true,
52
+ char: "p",
53
+ }),
54
+ "publish-all": Flags.boolean({
55
+ summary: "publish all ports that are defined in the image",
56
+ description: "Automatically publish all ports that are exposed by the container image to random ports on the host.",
57
+ required: false,
58
+ char: "P",
59
+ }),
60
+ volume: Flags.string({
61
+ summary: "bind mount a volume to the container",
62
+ description: "This flag can be used to add volume mounts to the container. It can be used multiple times to mount multiple volumes." +
63
+ "" +
64
+ "Needs to be in the format <host-path>:<container-path>. " +
65
+ "" +
66
+ "If you specify a file path as volume, this will mount a path from your hosting environment's file system (NOT your local file system) into the container. " +
67
+ "You can also specify a named volume, which needs to be created beforehand.",
68
+ required: false,
69
+ char: "v",
70
+ multiple: true,
71
+ }),
72
+ };
73
+ static args = {
74
+ image: Args.string({
75
+ summary: "container image to run",
76
+ description: "Can be specified as a repository/tag or repository@digest (e.g., 'ubuntu:20.04' or 'alpine@sha256:abc123...'). If no tag is provided, 'latest' is assumed.",
77
+ required: true,
78
+ }),
79
+ command: Args.string({
80
+ summary: "command to run in the container",
81
+ description: "This overrides the default command specified in the container image. If omitted, the default command from the image will be used. For example, 'bash' or 'python app.py'.",
82
+ required: false,
83
+ }),
84
+ args: Args.string({
85
+ summary: "arguments to pass to the command",
86
+ 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
+ required: false,
88
+ variadic: true,
89
+ }),
90
+ };
91
+ async exec() {
92
+ const p = makeProcessRenderer(this.flags, "Creating a container");
93
+ const projectId = await this.withProjectId(Run);
94
+ const stackId = projectId;
95
+ const serviceName = this.getServiceName();
96
+ const { image, meta: imageMeta } = await p.runStep("getting image metadata", this.getImageAndMeta(projectId));
97
+ const serviceRequest = await p.runStep("preparing service request", this.buildServiceRequest(image, imageMeta, serviceName));
98
+ const stack = await p.runStep("creating container", this.addServiceToStack(stackId, serviceName, serviceRequest));
99
+ const service = stack.services?.find(matchServiceByName(serviceName));
100
+ const serviceId = service?.id;
101
+ if (!serviceId) {
102
+ throw new Error("Service ID not found in the created stack.");
103
+ }
104
+ await p.complete(_jsxs(Success, { children: ["Container ", _jsx(Value, { children: serviceId }), " was successfully created and started."] }));
105
+ return { serviceId };
106
+ }
107
+ async addServiceToStack(stackId, serviceName, serviceRequest) {
108
+ const resp = await this.apiClient.container.updateStack({
109
+ stackId,
110
+ data: {
111
+ services: {
112
+ [serviceName]: serviceRequest,
113
+ },
114
+ },
115
+ });
116
+ assertStatus(resp, 200);
117
+ return resp.data;
118
+ }
119
+ /**
120
+ * Builds a container service request from command line arguments and image
121
+ * metadata
122
+ *
123
+ * @param image The container image to use
124
+ * @param imageMeta Metadata about the container image
125
+ * @param serviceName Name of the service to create
126
+ * @returns A properly formatted container service request
127
+ */
128
+ async buildServiceRequest(image, imageMeta, serviceName) {
129
+ const command = this.args.command ? [this.args.command] : imageMeta.command;
130
+ const entrypoint = this.flags.entrypoint
131
+ ? [this.flags.entrypoint]
132
+ : imageMeta.entrypoint;
133
+ const description = this.flags.description ?? serviceName;
134
+ const envs = await parseEnvironmentVariables(this.flags.env, this.flags["env-file"]);
135
+ const ports = getPortMappings(imageMeta, this.flags["publish-all"], this.flags.publish);
136
+ const volumes = this.flags.volume;
137
+ return {
138
+ image,
139
+ command,
140
+ entrypoint,
141
+ description,
142
+ envs,
143
+ ports,
144
+ volumes,
145
+ };
146
+ }
147
+ async getImageAndMeta(projectId) {
148
+ const { image } = this.args;
149
+ const meta = await getImageMeta(this.apiClient, image, projectId);
150
+ return { image, meta };
151
+ }
152
+ getServiceName() {
153
+ const { name } = this.flags;
154
+ if (name !== undefined) {
155
+ return name;
156
+ }
157
+ return dockerNames.getRandomName();
158
+ }
159
+ render({ serviceId }) {
160
+ if (this.flags.quiet) {
161
+ return serviceId;
162
+ }
163
+ }
164
+ }
165
+ function matchServiceByName(name) {
166
+ return (service) => service.serviceName === name;
167
+ }
@@ -0,0 +1,18 @@
1
+ import { ReactNode } from "react";
2
+ import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js";
3
+ type Result = {
4
+ serviceId: string;
5
+ };
6
+ export declare class Start extends ExecRenderBaseCommand<typeof Start, Result> {
7
+ static summary: string;
8
+ static flags: {
9
+ "project-id": import("@oclif/core/interfaces").OptionFlag<string>;
10
+ quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ };
12
+ static args: {
13
+ "container-id": import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
14
+ };
15
+ protected exec(): Promise<Result>;
16
+ protected render({ serviceId }: Result): ReactNode;
17
+ }
18
+ export {};
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Args } from "@oclif/core";
3
+ import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js";
4
+ import { makeProcessRenderer, processFlags, } from "../../rendering/process/process_flags.js";
5
+ import { projectFlags } from "../../lib/resources/project/flags.js";
6
+ import { withContainerAndStackId } from "../../lib/resources/container/flags.js";
7
+ import assertSuccess from "../../lib/apiutil/assert_success.js";
8
+ import { Success } from "../../rendering/react/components/Success.js";
9
+ import { Value } from "../../rendering/react/components/Value.js";
10
+ export class Start extends ExecRenderBaseCommand {
11
+ static summary = "Starts a stopped container.";
12
+ static flags = {
13
+ ...processFlags,
14
+ ...projectFlags,
15
+ };
16
+ static args = {
17
+ "container-id": Args.string({
18
+ description: "ID or short ID of the container to start",
19
+ required: true,
20
+ }),
21
+ };
22
+ async exec() {
23
+ const p = makeProcessRenderer(this.flags, "Starting a container");
24
+ const [serviceId, stackId] = await withContainerAndStackId(this.apiClient, Start, this.flags, this.args, this.config);
25
+ await p.runStep("starting container", async () => {
26
+ const r = await this.apiClient.container.startService({
27
+ serviceId,
28
+ stackId,
29
+ });
30
+ assertSuccess(r);
31
+ });
32
+ await p.complete(_jsxs(Success, { children: ["Container ", _jsx(Value, { children: serviceId }), " was successfully started."] }));
33
+ return { serviceId };
34
+ }
35
+ render({ serviceId }) {
36
+ if (this.flags.quiet) {
37
+ return serviceId;
38
+ }
39
+ }
40
+ }