rampway 0.5.0 → 0.5.2
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 +31 -1
- package/build/src/cli.d.ts.map +1 -1
- package/build/src/cli.js +10 -5
- package/build/src/cli.js.map +1 -1
- package/build/src/commands/context.d.ts +12 -0
- package/build/src/commands/context.d.ts.map +1 -1
- package/build/src/commands/context.js +13 -2
- package/build/src/commands/context.js.map +1 -1
- package/build/src/commands/deploy.d.ts.map +1 -1
- package/build/src/commands/deploy.js +61 -1
- package/build/src/commands/deploy.js.map +1 -1
- package/build/src/commands/plan.js +1 -1
- package/build/src/commands/plan.js.map +1 -1
- package/build/src/config/validate-config.d.ts.map +1 -1
- package/build/src/config/validate-config.js +84 -0
- package/build/src/config/validate-config.js.map +1 -1
- package/build/src/runtimes/compose-runtime.d.ts +49 -0
- package/build/src/runtimes/compose-runtime.d.ts.map +1 -0
- package/build/src/runtimes/compose-runtime.js +106 -0
- package/build/src/runtimes/compose-runtime.js.map +1 -0
- package/build/src/runtimes/index.d.ts.map +1 -1
- package/build/src/runtimes/index.js +2 -1
- package/build/src/runtimes/index.js.map +1 -1
- package/build/src/types.d.ts +49 -0
- package/build/src/types.d.ts.map +1 -1
- package/build/src/velocious/client.d.ts +146 -0
- package/build/src/velocious/client.d.ts.map +1 -0
- package/build/src/velocious/client.js +292 -0
- package/build/src/velocious/client.js.map +1 -0
- package/docs/config-reference.md +106 -0
- package/docs/velocious-deployment-api.md +37 -0
- package/package.json +1 -1
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { releaseIdFromDate } from "../core/release-id.js";
|
|
4
|
+
import { redactText } from "../core/redaction.js";
|
|
5
|
+
import { Reporter } from "../core/reporter.js";
|
|
6
|
+
import { remoteGitRepoLabel } from "../core/git-source.js";
|
|
7
|
+
const DEFAULT_POLL_INTERVAL_MS = 2000;
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
9
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
10
|
+
const FULL_SHA_PATTERN = /^[0-9a-f]{40}$/u;
|
|
11
|
+
const TERMINAL_STATUSES = ["succeeded", "failed", "interrupted", "reconciliation_required"];
|
|
12
|
+
/**
|
|
13
|
+
* @name DeploymentRun
|
|
14
|
+
* @description Public representation of a control-plane deployment run.
|
|
15
|
+
* @typedef {object} DeploymentRun
|
|
16
|
+
* @property {string} id
|
|
17
|
+
* @property {string} status
|
|
18
|
+
* @property {Record<string, any>} [result]
|
|
19
|
+
* @property {{message?: string, recovery?: any}} [error]
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Control-plane failure that must not trigger a transport fallback: an
|
|
23
|
+
* authenticated admission decision (401/403/409/422), a 5xx that already
|
|
24
|
+
* admitted a run, or an interruption after a run was admitted.
|
|
25
|
+
*/
|
|
26
|
+
export class ControlPlaneError extends Error {
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} message
|
|
29
|
+
* @param {{status?: number, cause?: unknown, runId?: string|null}} [opts]
|
|
30
|
+
*/
|
|
31
|
+
constructor(message, { status, cause, runId = null } = {}) {
|
|
32
|
+
super(message, cause === undefined ? undefined : { cause });
|
|
33
|
+
/** @type {number|null} */
|
|
34
|
+
this.status = status ?? null;
|
|
35
|
+
/** @type {string|null} */
|
|
36
|
+
this.runId = runId;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Control-plane unavailability that may fall back to the direct transport:
|
|
41
|
+
* network-level failure, a not-deployed endpoint/target (404), or a 5xx
|
|
42
|
+
* without any admitted run.
|
|
43
|
+
*/
|
|
44
|
+
export class ControlPlaneUnavailableError extends ControlPlaneError {
|
|
45
|
+
/**
|
|
46
|
+
* @param {string} message
|
|
47
|
+
* @param {{status?: number, cause?: unknown}} [opts]
|
|
48
|
+
*/
|
|
49
|
+
constructor(message, { status, cause } = {}) {
|
|
50
|
+
super(message, { status, cause });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Whether a control-plane failure is eligible for the SSH/direct fallback.
|
|
55
|
+
*
|
|
56
|
+
* The control plane is tried first only while it is unavailable or not
|
|
57
|
+
* deployed; authentication and validation decisions (401/403/409/422) and any
|
|
58
|
+
* failure after a run was admitted are always surfaced instead.
|
|
59
|
+
*
|
|
60
|
+
* @param {unknown} error
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
63
|
+
export function isFallbackEligible(error) {
|
|
64
|
+
return error instanceof ControlPlaneUnavailableError;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* @param {unknown} value
|
|
68
|
+
* @returns {value is string} Whether the value is a full 40-character commit SHA.
|
|
69
|
+
*/
|
|
70
|
+
export function isFullCommitSha(value) {
|
|
71
|
+
return typeof value === "string" && FULL_SHA_PATTERN.test(value);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Resolves the immutable revision a control-plane deploy requires. A full
|
|
75
|
+
* 40-character SHA is used as-is; otherwise the configured release branch is
|
|
76
|
+
* resolved to its current tip with `git ls-remote`.
|
|
77
|
+
*
|
|
78
|
+
* @param {{repo?: string, revision?: string, branch?: string}} args
|
|
79
|
+
* @returns {Promise<string>} Full commit SHA.
|
|
80
|
+
*/
|
|
81
|
+
export async function resolveControlPlaneRevision({ repo, revision, branch = "master" }) {
|
|
82
|
+
if (isFullCommitSha(revision))
|
|
83
|
+
return revision;
|
|
84
|
+
if (revision)
|
|
85
|
+
throw new Error(`Control-plane deploys require a full 40-character Git revision, got '${revision}'. Pass the full commit SHA or use --transport=ssh.`);
|
|
86
|
+
if (!repo)
|
|
87
|
+
throw new Error("Control-plane deploys require stage.repo to resolve the release branch to a commit SHA");
|
|
88
|
+
let output;
|
|
89
|
+
try {
|
|
90
|
+
output = (await runGit(["ls-remote", "--exit-code", repo, `refs/heads/${branch}`])).output;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
throw new Error(`Could not resolve branch '${branch}' of ${remoteGitRepoLabel(repo)} to a commit SHA`, { cause: error });
|
|
94
|
+
}
|
|
95
|
+
const sha = output.trim().split(/\s+/u)[0] ?? "";
|
|
96
|
+
if (!isFullCommitSha(sha))
|
|
97
|
+
throw new Error(`Could not resolve branch '${branch}' of ${remoteGitRepoLabel(repo)} to a commit SHA`);
|
|
98
|
+
return sha;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Creates a deployment run on the control plane and classifies the outcome.
|
|
102
|
+
*
|
|
103
|
+
* @param {{endpoint: string, accessToken: string, project: string, stage: string, revision: string, idempotencyKey: string, fetchImpl?: typeof fetch}} args
|
|
104
|
+
* @returns {Promise<{run: DeploymentRun, replayed: boolean}>}
|
|
105
|
+
*/
|
|
106
|
+
export async function createDeploymentRun({ endpoint, accessToken, project, stage, revision, idempotencyKey, fetchImpl = fetch }) {
|
|
107
|
+
const url = `${stripTrailingSlash(endpoint)}/runs`;
|
|
108
|
+
let response;
|
|
109
|
+
try {
|
|
110
|
+
response = await fetchImpl(url, {
|
|
111
|
+
method: "POST",
|
|
112
|
+
headers: {
|
|
113
|
+
Authorization: `Bearer ${accessToken}`,
|
|
114
|
+
"Content-Type": "application/json"
|
|
115
|
+
},
|
|
116
|
+
body: JSON.stringify({ project, stage, revision, idempotencyKey }),
|
|
117
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
throw new ControlPlaneUnavailableError(`Control plane unavailable at ${redactText(url, [accessToken])}: ${errorMessage(error)}`, { cause: error });
|
|
122
|
+
}
|
|
123
|
+
const body = await readJsonResponse(response).catch(() => null);
|
|
124
|
+
if (response.status === 200 || response.status === 202) {
|
|
125
|
+
const run = body?.run;
|
|
126
|
+
if (!run || typeof run.id !== "string" || run.id.length === 0) {
|
|
127
|
+
throw new ControlPlaneError(`Control plane returned HTTP ${response.status} without a run`, { status: response.status });
|
|
128
|
+
}
|
|
129
|
+
return { run: /** @type {DeploymentRun} */ (run), replayed: response.status === 200 };
|
|
130
|
+
}
|
|
131
|
+
if (response.status === 404) {
|
|
132
|
+
throw new ControlPlaneUnavailableError("Control plane endpoint or target is not deployed (HTTP 404)", { status: response.status });
|
|
133
|
+
}
|
|
134
|
+
if (response.status >= 500 && !body?.run) {
|
|
135
|
+
throw new ControlPlaneUnavailableError(`Control plane server unavailable (HTTP ${response.status})`, { status: response.status });
|
|
136
|
+
}
|
|
137
|
+
const reason = typeof body?.error === "string" ? body.error : `HTTP ${response.status}`;
|
|
138
|
+
throw new ControlPlaneError(redactText(`Control plane rejected the deployment request: ${reason}`, [accessToken]), { status: response.status });
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Polls a control-plane run until it reaches a terminal status. Any failure
|
|
142
|
+
* after admission (including lost contact) is a hard error: the run exists on
|
|
143
|
+
* the control plane, so there is nothing left to fall back to.
|
|
144
|
+
*
|
|
145
|
+
* @param {{endpoint: string, accessToken: string, runId: string, pollIntervalMs?: number, timeoutMs?: number, fetchImpl?: typeof fetch, now?: () => number, reporter?: Reporter}} args
|
|
146
|
+
* @returns {Promise<DeploymentRun>}
|
|
147
|
+
*/
|
|
148
|
+
export async function pollDeploymentRun({ endpoint, accessToken, runId, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS, timeoutMs = DEFAULT_TIMEOUT_MS, fetchImpl = fetch, now = Date.now, reporter = new Reporter({ lifecycle: false }) }) {
|
|
149
|
+
const url = `${stripTrailingSlash(endpoint)}/runs/${encodeURIComponent(runId)}`;
|
|
150
|
+
const startedAt = now();
|
|
151
|
+
/** @type {string|undefined} */
|
|
152
|
+
let lastStatus;
|
|
153
|
+
for (;;) {
|
|
154
|
+
if (now() - startedAt > timeoutMs) {
|
|
155
|
+
throw new ControlPlaneError(`Control plane run ${runId} did not reach a terminal status within ${Math.round(timeoutMs / 1000)}s (last status '${lastStatus ?? "pending"}')`);
|
|
156
|
+
}
|
|
157
|
+
let response;
|
|
158
|
+
try {
|
|
159
|
+
response = await fetchImpl(url, { headers: { Authorization: `Bearer ${accessToken}` }, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
throw new ControlPlaneError(`Lost contact with control plane while waiting for run ${runId}: ${errorMessage(error)}`, { cause: error, runId });
|
|
163
|
+
}
|
|
164
|
+
const body = await readJsonResponse(response).catch(() => null);
|
|
165
|
+
const run = body?.run;
|
|
166
|
+
if (response.status === 404 || !run) {
|
|
167
|
+
throw new ControlPlaneError(`Control plane no longer reports run ${runId} (HTTP ${response.status})`, { runId, status: response.status });
|
|
168
|
+
}
|
|
169
|
+
if (run.status !== lastStatus) {
|
|
170
|
+
lastStatus = run.status;
|
|
171
|
+
reporter.line(` * Control-plane run ${runId} status: ${run.status}`);
|
|
172
|
+
}
|
|
173
|
+
if (TERMINAL_STATUSES.includes(run.status))
|
|
174
|
+
return /** @type {DeploymentRun} */ (run);
|
|
175
|
+
await delay(pollIntervalMs);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Deploys a stage through the mounted control plane: resolves the immutable
|
|
180
|
+
* revision, creates an idempotent run, polls it to completion, and returns the
|
|
181
|
+
* control-plane deploy report annotated with the run identifier.
|
|
182
|
+
*
|
|
183
|
+
* @param {{stage: import('#types').StageConfig, httpConfig: import('#types').HttpTransportConfig, revision?: string, reporter?: Reporter, fetchImpl?: typeof fetch}} args
|
|
184
|
+
* @returns {Promise<import('#types').DeployReport>}
|
|
185
|
+
*/
|
|
186
|
+
export async function deployViaControlPlane({ stage, httpConfig, revision, reporter = new Reporter({ lifecycle: false }), fetchImpl = fetch }) {
|
|
187
|
+
const endpoint = httpConfig.endpoint;
|
|
188
|
+
const accessToken = await resolveAccessToken(httpConfig);
|
|
189
|
+
const project = httpConfig.project ?? stage.application;
|
|
190
|
+
const releaseRevision = await resolveControlPlaneRevision({ repo: stage.repo, revision, branch: stage.branch });
|
|
191
|
+
const idempotencyKey = controlPlaneIdempotencyKey(project, stage.stageName, releaseRevision);
|
|
192
|
+
reporter.line(` * Requesting control-plane deploy of ${project}/${stage.stageName} revision ${releaseRevision}`);
|
|
193
|
+
const { run, replayed } = await createDeploymentRun({ endpoint, accessToken, fetchImpl, project, stage: stage.stageName, revision: releaseRevision, idempotencyKey });
|
|
194
|
+
reporter.line(replayed
|
|
195
|
+
? ` * Control plane replayed run ${run.id} for idempotency key ${idempotencyKey}`
|
|
196
|
+
: ` * Control plane admitted run ${run.id}`);
|
|
197
|
+
const finalRun = await pollDeploymentRun({ endpoint, accessToken, fetchImpl, runId: run.id, pollIntervalMs: httpConfig.pollIntervalMs, timeoutMs: httpConfig.timeoutMs, reporter });
|
|
198
|
+
if (finalRun.status !== "succeeded") {
|
|
199
|
+
const detail = finalRun.error && typeof finalRun.error === "object" && typeof finalRun.error.message === "string"
|
|
200
|
+
? finalRun.error.message
|
|
201
|
+
: "no failure detail recorded";
|
|
202
|
+
throw new Error(redactText(`Control-plane run ${finalRun.id} ended with status '${finalRun.status}': ${detail}`, [accessToken]));
|
|
203
|
+
}
|
|
204
|
+
const result = finalRun.result && typeof finalRun.result === "object" ? finalRun.result : {};
|
|
205
|
+
reporter.line(` * Control-plane run ${finalRun.id} succeeded`);
|
|
206
|
+
return {
|
|
207
|
+
...result,
|
|
208
|
+
application: result.application ?? stage.application,
|
|
209
|
+
stage: result.stage ?? stage.stageName,
|
|
210
|
+
releaseId: result.releaseId ?? null,
|
|
211
|
+
revision: result.revision ?? releaseRevision,
|
|
212
|
+
event: result.event ?? "deploy",
|
|
213
|
+
runId: finalRun.id,
|
|
214
|
+
transport: "http"
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Builds a client-generated idempotency key that is stable per invocation, so
|
|
219
|
+
* retrying the same command replays an existing admitted run instead of
|
|
220
|
+
* creating a duplicate.
|
|
221
|
+
*
|
|
222
|
+
* @param {string} project
|
|
223
|
+
* @param {string} stage
|
|
224
|
+
* @param {string} revision
|
|
225
|
+
* @returns {string}
|
|
226
|
+
*/
|
|
227
|
+
export function controlPlaneIdempotencyKey(project, stage, revision) {
|
|
228
|
+
return `${project}-${stage}-${revision}-${releaseIdFromDate()}`;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* @param {import('#types').HttpTransportConfig} httpConfig
|
|
232
|
+
* @returns {string} Bearer token.
|
|
233
|
+
*/
|
|
234
|
+
function resolveAccessToken(httpConfig) {
|
|
235
|
+
if (httpConfig.accessToken)
|
|
236
|
+
return httpConfig.accessToken;
|
|
237
|
+
if (httpConfig.accessTokenEnv) {
|
|
238
|
+
const value = process.env[httpConfig.accessTokenEnv];
|
|
239
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
240
|
+
throw new Error(`stage.http.accessTokenEnv '${httpConfig.accessTokenEnv}' is not set in the environment`);
|
|
241
|
+
}
|
|
242
|
+
return value;
|
|
243
|
+
}
|
|
244
|
+
throw new Error("stage.http requires accessToken or accessTokenEnv");
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* @param {string[]} args
|
|
248
|
+
* @returns {Promise<{output: string}>}
|
|
249
|
+
*/
|
|
250
|
+
function runGit(args) {
|
|
251
|
+
return new Promise((resolve, reject) => {
|
|
252
|
+
execFile("git", args, { timeout: REQUEST_TIMEOUT_MS, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
|
|
253
|
+
if (error) {
|
|
254
|
+
const detail = String(stderr || error.message).trim();
|
|
255
|
+
reject(new Error(`git ${args[0]} failed: ${redactText(detail)}`));
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
resolve({ output: String(stdout) });
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* @param {string} endpoint
|
|
264
|
+
* @returns {string}
|
|
265
|
+
*/
|
|
266
|
+
function stripTrailingSlash(endpoint) {
|
|
267
|
+
return endpoint.replace(/\/+$/u, "");
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* @param {Response} response
|
|
271
|
+
* @returns {Promise<Record<string, any>|null>}
|
|
272
|
+
*/
|
|
273
|
+
async function readJsonResponse(response) {
|
|
274
|
+
const text = await response.text();
|
|
275
|
+
return text ? JSON.parse(text) : null;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* @param {unknown} error
|
|
279
|
+
* @returns {string}
|
|
280
|
+
*/
|
|
281
|
+
function errorMessage(error) {
|
|
282
|
+
const cause = error instanceof Error && error.cause instanceof Error ? error.cause.message : null;
|
|
283
|
+
return cause ?? (error instanceof Error ? error.message : String(error));
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* @param {number} ms
|
|
287
|
+
* @returns {Promise<void>}
|
|
288
|
+
*/
|
|
289
|
+
function delay(ms) {
|
|
290
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/velocious/client.js"],"names":[],"mappings":"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"}
|
package/docs/config-reference.md
CHANGED
|
@@ -34,6 +34,7 @@ Each stage under `stages` supports:
|
|
|
34
34
|
| `strategy` | string | no | Transport-dependent | Deploy strategy: `"local-copy"`, `"remote-git"`, or reserved `"archive-upload"`. Defaults to `"local-copy"` for local transport and `"remote-git"` for SSH transport. `archive-upload` is recognized but not yet deployable. |
|
|
35
35
|
| `transport` | object | no | `{type: "ssh"}` | Transport configuration. See Transport below. |
|
|
36
36
|
| `hosts` | array | no | `[]` | For SSH transport: list of hosts with `host`, `user`, and `roles`. |
|
|
37
|
+
| `http` | object | no | — | Optional control-plane transport config. Enables `rampway <stage> deploy --transport=http|auto`. See HTTP control plane below. |
|
|
37
38
|
| `keepReleases` | number | no | `5` | Number of old releases to keep after cleanup. |
|
|
38
39
|
| `linkedFiles` | array | no | `[]` | Files symlinked from `shared/` into each release. See Linked files below. |
|
|
39
40
|
| `linkedDirs` | array | no | `[]` | Directories symlinked from `shared/` into each release. |
|
|
@@ -78,6 +79,75 @@ transport: {type: "ssh"},
|
|
|
78
79
|
hosts: [{host: "server.example", user: "dev", roles: ["app", "web"]}]
|
|
79
80
|
```
|
|
80
81
|
|
|
82
|
+
### http (control plane)
|
|
83
|
+
|
|
84
|
+
Optional transport that runs the deploy through the authenticated Velocious
|
|
85
|
+
deployment API mounted by the application. When `http` is configured,
|
|
86
|
+
`rampway <stage> deploy` defaults to `--transport=auto`; see Deploy transports
|
|
87
|
+
below.
|
|
88
|
+
|
|
89
|
+
| Field | Type | Required | Default | Description |
|
|
90
|
+
|-------|------|----------|---------|-------------|
|
|
91
|
+
| `endpoint` | string | yes | — | Base URL where the API is mounted, e.g. `https://app.example/rampway/deployments`. Must be an `http(s)` URL. |
|
|
92
|
+
| `accessToken` | string | no | — | Bearer token to authenticate the client. Exactly one of `accessToken` or `accessTokenEnv` is required. |
|
|
93
|
+
| `accessTokenEnv` | string | no | — | Name of an environment variable holding the bearer token. Exactly one of `accessToken` or `accessTokenEnv` is required. The token value is never printed. |
|
|
94
|
+
| `project` | string | no | `stage.application` | Project the control plane is allowlisted for. |
|
|
95
|
+
| `pollIntervalMs` | positive integer | no | `2000` | How often to poll the run while it is pending/running. |
|
|
96
|
+
| `timeoutMs` | positive integer | no | `1800000` | How long to keep polling before failing the deploy. |
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
http: {
|
|
100
|
+
endpoint: "https://app.example/rampway/deployments",
|
|
101
|
+
accessTokenEnv: "RAMPWAY_DEPLOY_TOKEN",
|
|
102
|
+
project: "my-app",
|
|
103
|
+
pollIntervalMs: 2000,
|
|
104
|
+
timeoutMs: 1800000
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The CLI resolves the release revision as a full 40-character SHA; a branch name
|
|
109
|
+
is resolved with `git ls-remote` against `stage.repo`. The control plane only
|
|
110
|
+
accepts full SHAs reachable from the stage's approved release branch. Malformed
|
|
111
|
+
`http` config fails loudly: a missing or non-`http(s)` endpoint, an absent or
|
|
112
|
+
invalid token source, a non-string `project`, or non-positive interval/timeout
|
|
113
|
+
values are all rejected.
|
|
114
|
+
|
|
115
|
+
### Deploy transports
|
|
116
|
+
|
|
117
|
+
`rampway <stage> deploy --transport=http|ssh|auto` selects how the deploy is
|
|
118
|
+
executed:
|
|
119
|
+
|
|
120
|
+
- `http` — requires `stage.http`; runs entirely through the control plane and
|
|
121
|
+
fails if the stage has no `http` config.
|
|
122
|
+
- `ssh` — runs directly over the stage's `transport` (ssh or local),
|
|
123
|
+
bypassing the control plane.
|
|
124
|
+
- `auto` (default) — uses the control plane when `stage.http` is configured,
|
|
125
|
+
and falls back to the direct transport only when the control plane is
|
|
126
|
+
unreachable (network failure), not deployed for the project/stage (`404`),
|
|
127
|
+
or unavailable (`5xx` without an admitted run). It never falls back on
|
|
128
|
+
authentication or validation errors (`401`, `403`, `409`, `422`), on a
|
|
129
|
+
`5xx` that carries an admitted run, or once a run has been admitted and is
|
|
130
|
+
being polled.
|
|
131
|
+
|
|
132
|
+
Dry runs (`--dry-run`) always print a direct-transport plan and never contact
|
|
133
|
+
the control plane.
|
|
134
|
+
|
|
135
|
+
### Structural deploy plans
|
|
136
|
+
|
|
137
|
+
`rampway <stage> plan [revision]` renders the deploy structure after loading
|
|
138
|
+
and validating configuration. With no positional revision, selection remains
|
|
139
|
+
`--revision`, then `--branch`, then `stage.revision`, then `stage.branch`. A
|
|
140
|
+
positional revision takes precedence over all four sources and must contain 7
|
|
141
|
+
to 64 hexadecimal characters. The selected value appears in both human and
|
|
142
|
+
JSON output.
|
|
143
|
+
|
|
144
|
+
Planning is structural only: it does not prove that a SHA exists, check branch
|
|
145
|
+
or remote-git reachability, or obtain HTTP control-plane approval. It performs
|
|
146
|
+
no SSH, Git fetch/clone, local-copy, temporary checkout, control-plane request,
|
|
147
|
+
deploy lock, release-directory, runtime, filesystem deployment, or transport
|
|
148
|
+
operation. `local-copy` and `remote-git` steps are descriptions of what a later
|
|
149
|
+
deploy would do, not actions or admission checks.
|
|
150
|
+
|
|
81
151
|
## Linked files and directories
|
|
82
152
|
|
|
83
153
|
Linked files and directories are symlinked from `shared/` into every release before tasks run.
|
|
@@ -338,6 +408,42 @@ deploy without `config` retains its existing forward-deploy behavior, but
|
|
|
338
408
|
runtime-complete rollback fails closed until a successful deploy with explicit
|
|
339
409
|
stable config has recorded known-good state.
|
|
340
410
|
|
|
411
|
+
### compose
|
|
412
|
+
|
|
413
|
+
Compose runtime adapter for a pre-existing, trusted Docker Compose deployment.
|
|
414
|
+
Rampway runs only the two literal commands configured here from a fixed absolute
|
|
415
|
+
Compose root; it does not construct Compose arguments from release IDs, hooks,
|
|
416
|
+
or command-line input.
|
|
417
|
+
|
|
418
|
+
| Field | Type | Required | Description |
|
|
419
|
+
|-------|------|----------|-------------|
|
|
420
|
+
| `type` | string | yes | `"compose"` |
|
|
421
|
+
| `composeRoot` | absolute path | yes | Fixed directory used as the working directory for both commands. |
|
|
422
|
+
| `handoffCommand` | string | yes | Static trusted command that selects/starts the candidate runtime. |
|
|
423
|
+
| `healthCommand` | string | yes | Static trusted command that performs the runtime health check after handoff. |
|
|
424
|
+
|
|
425
|
+
```js
|
|
426
|
+
runtime: {
|
|
427
|
+
type: "compose",
|
|
428
|
+
composeRoot: "/srv/myapp/compose",
|
|
429
|
+
handoffCommand: "docker compose up -d --wait web",
|
|
430
|
+
healthCommand: "docker compose exec -T web node scripts/healthcheck.mjs"
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
The adapter passes the immutable candidate to both commands through the trusted
|
|
435
|
+
`RAMPWAY_RELEASE_ID` and `RAMPWAY_RELEASE_PATH` environment variables, then runs
|
|
436
|
+
the candidate handoff followed by the configured health command before Rampway runs stage health checks or publishes `current`. Its
|
|
437
|
+
snapshot stores only the fixed Compose root and a contract digest: it never
|
|
438
|
+
stores command text or secrets. Configuration is intentionally conservative:
|
|
439
|
+
all three fields are required, `composeRoot` must be absolute, and no runtime
|
|
440
|
+
`env` or extra adapter fields are accepted. Put sensitive values in the trusted
|
|
441
|
+
Compose environment/secret mechanism, not in these commands.
|
|
442
|
+
|
|
443
|
+
A failed candidate handoff or health check, and a failed explicit rollback,
|
|
444
|
+
reactivate and health-check the previous Compose runtime through the same
|
|
445
|
+
runtime deploy API before returning the original failure.
|
|
446
|
+
|
|
341
447
|
## Rollback and retained releases
|
|
342
448
|
|
|
343
449
|
`rampway <stage> rollback` selects the latest other safe 14-digit release, or
|
|
@@ -128,6 +128,43 @@ const runResponse = await fetch(`${baseUrl}/runs/${encodeURIComponent(created.ru
|
|
|
128
128
|
const {run, current} = await runResponse.json()
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
+
## Deploying from the Rampway CLI
|
|
132
|
+
|
|
133
|
+
A stage that configures `stage.http` can deploy through this API directly from
|
|
134
|
+
the CLI:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
RAMPWAY_DEPLOY_TOKEN=... rampway production deploy --transport=http
|
|
138
|
+
RAMPWAY_DEPLOY_TOKEN=... rampway production deploy --transport=auto
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`--transport=auto` (the default) tries the control plane first and falls back
|
|
142
|
+
to the stage's direct SSH/local transport only when the control plane is
|
|
143
|
+
unreachable, the project/stage is not allowlisted (`404`), or it returns `5xx`
|
|
144
|
+
without having admitted a run. It never falls back on `401`/`403`/`409`/`422`,
|
|
145
|
+
on a `5xx` that carries an admitted run, or once a run has been admitted and is
|
|
146
|
+
being polled. `--transport=ssh` bypasses the control plane entirely;
|
|
147
|
+
`--transport=http` requires `stage.http` and fails if it is missing. Dry runs
|
|
148
|
+
always print a direct-transport plan.
|
|
149
|
+
|
|
150
|
+
The separate `rampway <stage> plan [revision]` command is purely structural.
|
|
151
|
+
Even for a stage configured with `http`, it never contacts this control plane
|
|
152
|
+
and does not resolve a branch, prove that a supplied SHA exists or is reachable
|
|
153
|
+
from the approved branch, or request deployment admission. Those full-SHA and
|
|
154
|
+
reachability checks remain part of an actual HTTP deployment.
|
|
155
|
+
|
|
156
|
+
The client posts the stage's project/application name, an immutable full commit
|
|
157
|
+
SHA (resolving `stage.branch` with `git ls-remote` against `stage.repo` when no
|
|
158
|
+
revision is pinned), and an idempotency key derived from the project, stage,
|
|
159
|
+
revision, and release timestamp. It then polls the run until it reaches a
|
|
160
|
+
terminal status. A `succeeded` run is reported as a normal deploy; a
|
|
161
|
+
`failed`/`interrupted`/`reconciliation_required` run fails the deploy with the
|
|
162
|
+
control plane's sanitized error, and Rampway records whether previous live
|
|
163
|
+
state was restored. Polling is bounded by `http.pollIntervalMs` (default
|
|
164
|
+
`2000`) and `http.timeoutMs` (default 30 minutes); losing contact with the
|
|
165
|
+
control plane while a run is admitted fails the deploy rather than retrying
|
|
166
|
+
elsewhere.
|
|
167
|
+
|
|
131
168
|
Authentication is accepted only from the `Authorization` header. Never place
|
|
132
169
|
the token in a URL or command argument that is logged; clients should obtain it
|
|
133
170
|
from their own secret store and configure the HTTP library's header directly.
|