Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 42 additions & 10 deletions docs/stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,14 @@ Deploys a docker-compose compatible file to a mittwald container stack

```
USAGE
$ mw stack deploy [--token <value>] [-s <value>] [-q] [-c <value>] [--env-file <value>]
$ mw stack deploy [--token <value>] [-s <value>] [-q] [-c <value> | --from-template <value>] [--env-file <value>]

FLAGS
-c, --compose-file=<value> [default: ./docker-compose.yml] path to a compose file, or "-" to read from stdin
-q, --quiet suppress process output and only display a machine-readable summary
-s, --stack-id=<value> ID of a stack; this flag is optional if a default stack is set in the context
--env-file=<value> [default: ./.env] alternative path to file with environment variables
-c, --compose-file=<value> [default: ./docker-compose.yml] path to a compose file, or "-" to read from stdin
-q, --quiet suppress process output and only display a machine-readable summary
-s, --stack-id=<value> ID of a stack; this flag is optional if a default stack is set in the context
--env-file=<value> [default: ./.env] alternative path to file with environment variables
--from-template=<value> deploy from a GitHub template (e.g., mittwald/n8n)

AUTHENTICATION FLAGS
--token=<value> API token to use for authentication (overrides environment and config file). NOTE: watch out that
Expand All @@ -78,6 +79,21 @@ FLAG DESCRIPTIONS

May contain a ID of a stack; you can also use the "mw context set --stack-id=<VALUE>" command to persistently set a
default stack for all commands that accept this flag.

--from-template=<value> deploy from a GitHub template (e.g., mittwald/n8n)

Fetch and deploy a stack from a GitHub template repository. Template names are automatically converted to repository
names by prefixing "stack-template-" to the name part.

For example, "mittwald/n8n" resolves to the repository "mittwald/stack-template-n8n". The command fetches both
docker-compose.yml and .env files from the main branch.

Environment variable precedence (from lowest to highest):
1. Template .env file (if present in the repository)
2. System environment variables (process.env)
3. Local --env-file (if specified)

This flag is mutually exclusive with --compose-file.
```

## `mw stack list`
Expand Down Expand Up @@ -229,13 +245,14 @@ Deploys a docker-compose compatible file to a mittwald container stack

```
USAGE
$ mw stack up [--token <value>] [-s <value>] [-q] [-c <value>] [--env-file <value>]
$ mw stack up [--token <value>] [-s <value>] [-q] [-c <value> | --from-template <value>] [--env-file <value>]

FLAGS
-c, --compose-file=<value> [default: ./docker-compose.yml] path to a compose file, or "-" to read from stdin
-q, --quiet suppress process output and only display a machine-readable summary
-s, --stack-id=<value> ID of a stack; this flag is optional if a default stack is set in the context
--env-file=<value> [default: ./.env] alternative path to file with environment variables
-c, --compose-file=<value> [default: ./docker-compose.yml] path to a compose file, or "-" to read from stdin
-q, --quiet suppress process output and only display a machine-readable summary
-s, --stack-id=<value> ID of a stack; this flag is optional if a default stack is set in the context
--env-file=<value> [default: ./.env] alternative path to file with environment variables
--from-template=<value> deploy from a GitHub template (e.g., mittwald/n8n)

AUTHENTICATION FLAGS
--token=<value> API token to use for authentication (overrides environment and config file). NOTE: watch out that
Expand All @@ -257,4 +274,19 @@ FLAG DESCRIPTIONS

May contain a ID of a stack; you can also use the "mw context set --stack-id=<VALUE>" command to persistently set a
default stack for all commands that accept this flag.

--from-template=<value> deploy from a GitHub template (e.g., mittwald/n8n)

Fetch and deploy a stack from a GitHub template repository. Template names are automatically converted to repository
names by prefixing "stack-template-" to the name part.

For example, "mittwald/n8n" resolves to the repository "mittwald/stack-template-n8n". The command fetches both
docker-compose.yml and .env files from the main branch.

Environment variable precedence (from lowest to highest):
1. Template .env file (if present in the repository)
2. System environment variables (process.env)
3. Local --env-file (if specified)

This flag is mutually exclusive with --compose-file.
```
69 changes: 64 additions & 5 deletions src/commands/stack/deploy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,27 @@ import {
makeProcessRenderer,
processFlags,
} from "../../rendering/process/process_flags.js";
import { loadStackFromFile } from "../../lib/resources/stack/loader.js";
import { assertStatus } from "@mittwald/api-client";
import {
loadStackFromFile,
loadStackFromStr,
} from "../../lib/resources/stack/loader.js";
import { assertStatus, MittwaldAPIV2 } from "@mittwald/api-client";
import assertSuccess from "../../lib/apiutil/assert_success.js";
import { collectEnvironment } from "../../lib/resources/stack/env.js";
import { sanitizeStackDefinition } from "../../lib/resources/stack/sanitize.js";
import { enrichStackDefinition } from "../../lib/resources/stack/enrich.js";
import { Success } from "../../rendering/react/components/Success.js";
import { Value } from "../../rendering/react/components/Value.js";
import { loadStackFromTemplate } from "../../lib/resources/stack/template-loader.js";
import { parse } from "envfile";

interface DeployResult {
restartedServices: string[];
}

type StackRequest =
MittwaldAPIV2.Paths.V2StacksStackId.Put.Parameters.RequestBody;

export class Deploy extends ExecRenderBaseCommand<typeof Deploy, DeployResult> {
static description =
"Deploys a docker-compose compatible file to a mittwald container stack";
Expand All @@ -30,13 +38,57 @@ export class Deploy extends ExecRenderBaseCommand<typeof Deploy, DeployResult> {
summary: 'path to a compose file, or "-" to read from stdin',
default: "./docker-compose.yml",
char: "c",
exclusive: ["from-template"],
}),
"from-template": Flags.string({
summary: "deploy from a GitHub template (e.g., mittwald/n8n)",
description: `\
Fetch and deploy a stack from a GitHub template repository. Template names are automatically converted to repository names by prefixing "stack-template-" to the name part.

For example, "mittwald/n8n" resolves to the repository "mittwald/stack-template-n8n". The command fetches both docker-compose.yml and .env files from the main branch.

Environment variable precedence (from lowest to highest):
1. Template .env file (if present in the repository)
2. System environment variables (process.env)
3. Local --env-file (if specified)

This flag is mutually exclusive with --compose-file.`,
exclusive: ["compose-file"],
}),
"env-file": Flags.file({
summary: "alternative path to file with environment variables",
default: "./.env",
}),
};

private async loadStackDefinition(
source: { template: string } | { composeFile: string },
envFile: string,
renderer: ReturnType<typeof makeProcessRenderer>,
): Promise<StackRequest> {
if ("template" in source) {
// Load from GitHub template
const { composeYaml, envContent } = await renderer.runStep(
"fetching template from GitHub",
() => loadStackFromTemplate(source.template),
);

// Build environment: start with process.env, then template .env, then local --env-file
let env: Record<string, string | undefined> = { ...process.env };
if (envContent) {
const templateEnv = parse(envContent);
env = { ...env, ...templateEnv };
}
env = await collectEnvironment(env, envFile);

return loadStackFromStr(composeYaml, env);
}

// Load from local file
const env = await collectEnvironment(process.env, envFile);
return loadStackFromFile(source.composeFile, env);
}

protected async exec(): Promise<DeployResult> {
const stackId = await withStackId(
this.apiClient,
Expand All @@ -45,13 +97,20 @@ export class Deploy extends ExecRenderBaseCommand<typeof Deploy, DeployResult> {
this.args,
this.config,
);
const { "compose-file": composeFile, "env-file": envFile } = this.flags;
const {
"compose-file": composeFile,
"from-template": fromTemplate,
"env-file": envFile,
} = this.flags;
const r = makeProcessRenderer(this.flags, "Deploying container stack");

const result: DeployResult = { restartedServices: [] };

const env = await collectEnvironment(process.env, envFile);
let stackDefinition = await loadStackFromFile(composeFile, env);
let stackDefinition = await this.loadStackDefinition(
fromTemplate ? { template: fromTemplate } : { composeFile },
envFile,
r,
);

stackDefinition = sanitizeStackDefinition(stackDefinition);
stackDefinition = await r.runStep("getting image configurations", () =>
Expand Down
Loading