redkite 0.1.6
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/LICENSE +21 -0
- package/README.md +819 -0
- package/bin/redkite.mjs +12 -0
- package/dist/build.d.ts +27 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +132 -0
- package/dist/build.js.map +1 -0
- package/dist/checks.d.ts +4 -0
- package/dist/checks.d.ts.map +1 -0
- package/dist/checks.js +49 -0
- package/dist/checks.js.map +1 -0
- package/dist/cli/agent.d.ts +2 -0
- package/dist/cli/agent.d.ts.map +1 -0
- package/dist/cli/agent.js +24 -0
- package/dist/cli/agent.js.map +1 -0
- package/dist/cli/config.d.ts +5 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +217 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +438 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/log.d.ts +9 -0
- package/dist/cli/log.d.ts.map +1 -0
- package/dist/cli/log.js +128 -0
- package/dist/cli/log.js.map +1 -0
- package/dist/cli/screen.d.ts +34 -0
- package/dist/cli/screen.d.ts.map +1 -0
- package/dist/cli/screen.js +298 -0
- package/dist/cli/screen.js.map +1 -0
- package/dist/cli/viewer.d.ts +9 -0
- package/dist/cli/viewer.d.ts.map +1 -0
- package/dist/cli/viewer.js +139 -0
- package/dist/cli/viewer.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +79 -0
- package/dist/config.js.map +1 -0
- package/dist/deploy.d.ts +19 -0
- package/dist/deploy.d.ts.map +1 -0
- package/dist/deploy.js +275 -0
- package/dist/deploy.js.map +1 -0
- package/dist/docker.d.ts +96 -0
- package/dist/docker.d.ts.map +1 -0
- package/dist/docker.js +325 -0
- package/dist/docker.js.map +1 -0
- package/dist/dockerfile.d.ts +14 -0
- package/dist/dockerfile.d.ts.map +1 -0
- package/dist/dockerfile.js +160 -0
- package/dist/dockerfile.js.map +1 -0
- package/dist/health.d.ts +13 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +54 -0
- package/dist/health.js.map +1 -0
- package/dist/host.d.ts +21 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +27 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +5 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +45 -0
- package/dist/layout.js.map +1 -0
- package/dist/localHost.d.ts +7 -0
- package/dist/localHost.d.ts.map +1 -0
- package/dist/localHost.js +32 -0
- package/dist/localHost.js.map +1 -0
- package/dist/log.d.ts +14 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +15 -0
- package/dist/log.js.map +1 -0
- package/dist/nginx.d.ts +4 -0
- package/dist/nginx.d.ts.map +1 -0
- package/dist/nginx.js +53 -0
- package/dist/nginx.js.map +1 -0
- package/dist/pipeline.d.ts +103 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +142 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/presets/index.d.ts +3 -0
- package/dist/presets/index.d.ts.map +1 -0
- package/dist/presets/index.js +3 -0
- package/dist/presets/index.js.map +1 -0
- package/dist/presets/nextApp.d.ts +15 -0
- package/dist/presets/nextApp.d.ts.map +1 -0
- package/dist/presets/nextApp.js +53 -0
- package/dist/presets/nextApp.js.map +1 -0
- package/dist/presets/nodeApp.d.ts +19 -0
- package/dist/presets/nodeApp.d.ts.map +1 -0
- package/dist/presets/nodeApp.js +29 -0
- package/dist/presets/nodeApp.js.map +1 -0
- package/dist/secrets/bitwarden.d.ts +3 -0
- package/dist/secrets/bitwarden.d.ts.map +1 -0
- package/dist/secrets/bitwarden.js +6 -0
- package/dist/secrets/bitwarden.js.map +1 -0
- package/dist/secrets/refs.d.ts +9 -0
- package/dist/secrets/refs.d.ts.map +1 -0
- package/dist/secrets/refs.js +20 -0
- package/dist/secrets/refs.js.map +1 -0
- package/dist/secrets/store.d.ts +9 -0
- package/dist/secrets/store.d.ts.map +1 -0
- package/dist/secrets/store.js +104 -0
- package/dist/secrets/store.js.map +1 -0
- package/dist/services/ensure.d.ts +18 -0
- package/dist/services/ensure.d.ts.map +1 -0
- package/dist/services/ensure.js +93 -0
- package/dist/services/ensure.js.map +1 -0
- package/dist/services/index.d.ts +19 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +27 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/planned.d.ts +20 -0
- package/dist/services/planned.d.ts.map +1 -0
- package/dist/services/planned.js +77 -0
- package/dist/services/planned.js.map +1 -0
- package/dist/shell.d.ts +11 -0
- package/dist/shell.d.ts.map +1 -0
- package/dist/shell.js +65 -0
- package/dist/shell.js.map +1 -0
- package/dist/source.d.ts +16 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +108 -0
- package/dist/source.js.map +1 -0
- package/dist/sshHost.d.ts +15 -0
- package/dist/sshHost.d.ts.map +1 -0
- package/dist/sshHost.js +91 -0
- package/dist/sshHost.js.map +1 -0
- package/dist/steps.d.ts +27 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/steps.js +69 -0
- package/dist/steps.js.map +1 -0
- package/dist/topology.d.ts +40 -0
- package/dist/topology.d.ts.map +1 -0
- package/dist/topology.js +113 -0
- package/dist/topology.js.map +1 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +64 -0
- package/src/build.ts +236 -0
- package/src/checks.ts +65 -0
- package/src/cli/agent.ts +28 -0
- package/src/cli/config.ts +275 -0
- package/src/cli/index.ts +559 -0
- package/src/cli/log.ts +168 -0
- package/src/cli/screen.ts +409 -0
- package/src/cli/viewer.ts +192 -0
- package/src/config.ts +90 -0
- package/src/deploy.ts +409 -0
- package/src/docker.ts +433 -0
- package/src/dockerfile.ts +228 -0
- package/src/health.ts +83 -0
- package/src/host.ts +54 -0
- package/src/index.ts +25 -0
- package/src/layout.ts +41 -0
- package/src/localHost.ts +50 -0
- package/src/log.ts +38 -0
- package/src/nginx.ts +63 -0
- package/src/pipeline.ts +335 -0
- package/src/presets/index.ts +2 -0
- package/src/presets/nextApp.ts +70 -0
- package/src/presets/nodeApp.ts +46 -0
- package/src/secrets/bitwarden.ts +7 -0
- package/src/secrets/refs.ts +32 -0
- package/src/secrets/store.ts +147 -0
- package/src/services/ensure.ts +160 -0
- package/src/services/index.ts +48 -0
- package/src/services/planned.ts +119 -0
- package/src/shell.ts +95 -0
- package/src/source.ts +171 -0
- package/src/sshHost.ts +145 -0
- package/src/steps.ts +101 -0
- package/src/topology.ts +199 -0
- package/src/types.ts +243 -0
package/src/topology.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { AppSpec, Deployment, ServiceSpec } from "./types.js";
|
|
2
|
+
|
|
3
|
+
import { environmentOf } from "./config.js";
|
|
4
|
+
import { mountFor } from "./layout.js";
|
|
5
|
+
|
|
6
|
+
// Every name and address the deployment uses, derived from the app list. This
|
|
7
|
+
// is the file that replaces the constants block: nothing is chosen by hand, so
|
|
8
|
+
// adding an app cannot collide with an address somebody already picked.
|
|
9
|
+
|
|
10
|
+
const NGINX_OCTET = 20;
|
|
11
|
+
const APP_BLOCK_START = 21;
|
|
12
|
+
// Services sit in their own block, so adding one never moves an app
|
|
13
|
+
const SERVICE_BLOCK_START = 200;
|
|
14
|
+
|
|
15
|
+
export type AppTopology = {
|
|
16
|
+
name: string;
|
|
17
|
+
container: string;
|
|
18
|
+
retired: string;
|
|
19
|
+
failed: string;
|
|
20
|
+
currentAddress: string;
|
|
21
|
+
retiredAddress: string;
|
|
22
|
+
port: number;
|
|
23
|
+
route: string;
|
|
24
|
+
volumes: { volume: string; mountPath: string }[];
|
|
25
|
+
caches: Record<string, string>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type ServiceTopology = {
|
|
29
|
+
name: string;
|
|
30
|
+
container: string;
|
|
31
|
+
address: string;
|
|
32
|
+
alias?: string;
|
|
33
|
+
volumes: { volume: string; mountPath: string }[];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type Topology = {
|
|
37
|
+
environment: string;
|
|
38
|
+
branch: string;
|
|
39
|
+
network: string;
|
|
40
|
+
subnet: string;
|
|
41
|
+
cidr: string;
|
|
42
|
+
// Absent for an environment written for verify alone, which publishes nothing
|
|
43
|
+
publicPort?: number;
|
|
44
|
+
// Derived, never listed. Apps with routes imply exactly one proxy, and it
|
|
45
|
+
// keeps the address it was given before any of this was derived
|
|
46
|
+
router: ServiceTopology;
|
|
47
|
+
apps: AppTopology[];
|
|
48
|
+
services: ServiceTopology[];
|
|
49
|
+
// Every alias a container must resolve, name to address
|
|
50
|
+
extraHosts: Record<string, string>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export function topologyFor(config: Deployment, environment: string): Topology {
|
|
54
|
+
const env = environmentOf(config, environment);
|
|
55
|
+
|
|
56
|
+
if (!env) {
|
|
57
|
+
const known = Object.keys(config.environments ?? {});
|
|
58
|
+
|
|
59
|
+
// An environment is a file, so having none is a different mistake from
|
|
60
|
+
// asking for one that is not there, and reads as one
|
|
61
|
+
if (known.length === 0) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`No environments. Each one is a redkite.<name>.config.ts beside the ` +
|
|
64
|
+
`deployment, so ${environment} wants a redkite.${environment}.config.ts`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Unknown environment ${environment}, expected one of ${known.join(", ")}`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const prefix = `${config.project}-${environment}`;
|
|
74
|
+
const address = (octet: number) => `${env.subnet}.${octet}`;
|
|
75
|
+
|
|
76
|
+
const apps = config.apps.map((app, index) =>
|
|
77
|
+
appTopology(app, prefix, environment, address, index),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const services = config.services.map((service, index) =>
|
|
81
|
+
serviceTopology(service, prefix, address, index),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
environment,
|
|
86
|
+
branch: env.branch,
|
|
87
|
+
network: `${prefix}-network`,
|
|
88
|
+
subnet: env.subnet,
|
|
89
|
+
cidr: `${env.subnet}.0/16`,
|
|
90
|
+
publicPort: env.publicPort,
|
|
91
|
+
router: {
|
|
92
|
+
name: "nginx",
|
|
93
|
+
container: `${prefix}-nginx`,
|
|
94
|
+
address: address(NGINX_OCTET),
|
|
95
|
+
volumes: [],
|
|
96
|
+
},
|
|
97
|
+
apps,
|
|
98
|
+
services,
|
|
99
|
+
extraHosts: extraHosts(apps, services, env.extraHosts),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function appTopology(
|
|
104
|
+
app: AppSpec,
|
|
105
|
+
prefix: string,
|
|
106
|
+
environment: string,
|
|
107
|
+
address: (octet: number) => string,
|
|
108
|
+
index: number,
|
|
109
|
+
): AppTopology {
|
|
110
|
+
const container = `${prefix}-${app.name}`;
|
|
111
|
+
const base = APP_BLOCK_START + index * 2;
|
|
112
|
+
|
|
113
|
+
const volumes = Object.entries(app.volumes ?? {}).map(([name, mountPath]) => ({
|
|
114
|
+
volume: `${container}-${name}`,
|
|
115
|
+
mountPath,
|
|
116
|
+
}));
|
|
117
|
+
|
|
118
|
+
// Deduplicated by where each one mounts: an app that is the whole repository
|
|
119
|
+
// resolves the root and its own node_modules to one path, and two ids for one
|
|
120
|
+
// target would be a second cache nothing ever writes to
|
|
121
|
+
const targets = new Set<string>();
|
|
122
|
+
const caches: Record<string, string> = {};
|
|
123
|
+
|
|
124
|
+
for (const cache of app.build.caches) {
|
|
125
|
+
const target = mountFor(cache, app.dir);
|
|
126
|
+
if (targets.has(target)) continue;
|
|
127
|
+
|
|
128
|
+
targets.add(target);
|
|
129
|
+
caches[cache] = `${app.name}-${environment}-${cache}-cache`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
name: app.name,
|
|
134
|
+
container,
|
|
135
|
+
retired: `retired-${container}`,
|
|
136
|
+
failed: `failed-${container}`,
|
|
137
|
+
// Retired takes the lower slot, so a rollback never renumbers the live one
|
|
138
|
+
retiredAddress: address(base),
|
|
139
|
+
currentAddress: address(base + 1),
|
|
140
|
+
port: app.port,
|
|
141
|
+
route: app.route,
|
|
142
|
+
volumes,
|
|
143
|
+
caches,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function serviceTopology(
|
|
148
|
+
service: ServiceSpec,
|
|
149
|
+
prefix: string,
|
|
150
|
+
address: (octet: number) => string,
|
|
151
|
+
index: number,
|
|
152
|
+
): ServiceTopology {
|
|
153
|
+
const container = `${prefix}-${service.name}`;
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
name: service.name,
|
|
157
|
+
container,
|
|
158
|
+
// Pinned when a service already exists at a known address
|
|
159
|
+
address: address(service.address ?? SERVICE_BLOCK_START + index),
|
|
160
|
+
alias: service.alias,
|
|
161
|
+
volumes: Object.entries(service.volumes ?? {}).map(([name, mountPath]) => ({
|
|
162
|
+
volume: `${container}-${name}`,
|
|
163
|
+
mountPath,
|
|
164
|
+
})),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Nginx resolves upstreams by container name, apps reach services by alias
|
|
169
|
+
function extraHosts(
|
|
170
|
+
apps: AppTopology[],
|
|
171
|
+
services: ServiceTopology[],
|
|
172
|
+
declared: Record<string, string> = {},
|
|
173
|
+
) {
|
|
174
|
+
const hosts: Record<string, string> = {};
|
|
175
|
+
|
|
176
|
+
for (const app of apps) {
|
|
177
|
+
hosts[app.container] = app.currentAddress;
|
|
178
|
+
hosts[app.retired] = app.retiredAddress;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (const service of services) {
|
|
182
|
+
if (service.alias) hosts[service.alias] = service.address;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Declared last, but never over a derived one: a name that already resolves
|
|
186
|
+
// to a container in this deployment would send its traffic somewhere else,
|
|
187
|
+
// and the deploy would look like it worked
|
|
188
|
+
for (const [name, address] of Object.entries(declared)) {
|
|
189
|
+
if (name in hosts) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`extraHosts names ${name}, which this deployment already resolves to ${hosts[name]}`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
hosts[name] = address;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return hosts;
|
|
199
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import type { AnyStep } from "./pipeline.js";
|
|
2
|
+
|
|
3
|
+
// The shapes a deploy config is written against. Nothing here knows about
|
|
4
|
+
// Docker or git, so a config can be planned and asserted on without a host to
|
|
5
|
+
// deploy to.
|
|
6
|
+
|
|
7
|
+
export type Environment = {
|
|
8
|
+
// Where the images are built. "host" is the deploy host, which is where they
|
|
9
|
+
// are needed and costs nothing to move them. "local" builds on this machine
|
|
10
|
+
// and ships the result, for a host too small to compile on. Ignored when the
|
|
11
|
+
// deploy host is already this machine
|
|
12
|
+
buildOn?: "host" | "local";
|
|
13
|
+
// Git ref the apps are built from, the only per-environment source difference
|
|
14
|
+
branch: string;
|
|
15
|
+
// First three octets, the allocator owns the fourth. One /16 per environment
|
|
16
|
+
subnet: string;
|
|
17
|
+
// Port published on the host, the only port a person outside ever types.
|
|
18
|
+
// A verify environment has none: nothing serves in one, so there is no proxy
|
|
19
|
+
// to publish and no traffic to publish it for
|
|
20
|
+
publicPort?: number;
|
|
21
|
+
// The machine the containers run on, and which builds the images. Absent
|
|
22
|
+
// means this one
|
|
23
|
+
host?: DeployHost;
|
|
24
|
+
// Hostname to address, added to every container beside the ones the topology
|
|
25
|
+
// derives. For a database or a legacy service that has no DNS the apps can
|
|
26
|
+
// use, and whose address is what differs between environments
|
|
27
|
+
extraHosts?: Record<string, string>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Where a step's container is attached. "host" is the deploy host's own stack,
|
|
31
|
+
// which reaches whatever that machine already reaches. "deployment" is the
|
|
32
|
+
// network the apps and services run on, which is what resolves a service by
|
|
33
|
+
// the alias the apps know it by
|
|
34
|
+
export type StepNetwork = "host" | "deployment" | "none" | { named: string };
|
|
35
|
+
|
|
36
|
+
export type DeployHost = {
|
|
37
|
+
// SSH destination, user@address
|
|
38
|
+
bastion: string;
|
|
39
|
+
// Socket path on that machine. Local when absent, deploying to this one
|
|
40
|
+
socket?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// One item in a store, named at the point of use rather than through a lookup
|
|
44
|
+
// table. The provider tag is what routes it to a store at deploy time
|
|
45
|
+
export type SecretRef = {
|
|
46
|
+
// Which store resolves this, and the primitive that produced it
|
|
47
|
+
provider: string;
|
|
48
|
+
// The store's own identifier for the item
|
|
49
|
+
id: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// One entry, or several merged in order. A key set by a later ref wins
|
|
53
|
+
export type SecretRefs = SecretRef | SecretRef[];
|
|
54
|
+
|
|
55
|
+
export type ServiceSpec = {
|
|
56
|
+
// Identifies the service, and becomes part of its container name
|
|
57
|
+
name: string;
|
|
58
|
+
// Image pulled and re-tagged onto the host, pinned rather than floating
|
|
59
|
+
image: string;
|
|
60
|
+
// Hostname other containers reach this service by, added as an extra host
|
|
61
|
+
alias?: string;
|
|
62
|
+
// Whether the host brings it back after a reboot or a crash
|
|
63
|
+
restart?: "always" | "unless-stopped";
|
|
64
|
+
// Container path to the file contents baked into the image at build time
|
|
65
|
+
files?: Record<string, string>;
|
|
66
|
+
// Settings the image reads on start. These are baked into the container, so
|
|
67
|
+
// a credential belongs in secrets rather than here
|
|
68
|
+
environment?: Record<string, string>;
|
|
69
|
+
// Resolved at deploy time and handed over as an env file, so a password
|
|
70
|
+
// reaches the container without being written down in this repository
|
|
71
|
+
secrets?: SecretRefs;
|
|
72
|
+
// Pins the last octet, for a service already running at an address it was
|
|
73
|
+
// given by hand. Drop the pin once the container has been recreated
|
|
74
|
+
address?: number;
|
|
75
|
+
// Volume name to container path. Without one, an image that declares a
|
|
76
|
+
// VOLUME gets an anonymous volume nothing in this file can name again
|
|
77
|
+
volumes?: Record<string, string>;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// A directory carried into the runtime image. Optional means the build is
|
|
81
|
+
// allowed not to have produced it, which is a claim about that directory alone
|
|
82
|
+
export type CarryPath = string | { path: string; optional: true };
|
|
83
|
+
|
|
84
|
+
export type BuildSpec = {
|
|
85
|
+
// Which preset produced this, carried for diagnostics rather than dispatch
|
|
86
|
+
preset: string;
|
|
87
|
+
// Fat image the repository is compiled in, with caches mounted
|
|
88
|
+
builderImage: string;
|
|
89
|
+
// Slim image the compiled output is copied into, and which actually ships
|
|
90
|
+
runtimeImage: string;
|
|
91
|
+
// Copied and run before the rest of the source. A commit that does not touch
|
|
92
|
+
// these files reuses the installed dependencies instead of resolving them
|
|
93
|
+
// again, which is the difference between a deploy and a cold build
|
|
94
|
+
dependencies?: {
|
|
95
|
+
files: string[];
|
|
96
|
+
step: string;
|
|
97
|
+
// Root lifecycle scripts removed from the manifest before installing. This
|
|
98
|
+
// layer holds the manifest and the lockfile alone, so a prepare that wants
|
|
99
|
+
// the repository, its hooks or its scripts directory cannot run here
|
|
100
|
+
stripScripts?: string[];
|
|
101
|
+
};
|
|
102
|
+
// Shell commands run in order in the builder. A non-zero exit fails the deploy
|
|
103
|
+
steps: string[];
|
|
104
|
+
// Directory in the builder that becomes the root of the runtime image
|
|
105
|
+
output: string;
|
|
106
|
+
// Extra builder directories copied into the runtime image beside the output.
|
|
107
|
+
// A bare path has to exist, and a build whose output is missing one of these
|
|
108
|
+
// is a build that failed without saying so
|
|
109
|
+
carry: CarryPath[];
|
|
110
|
+
// Whether the output tree keeps the repository's own directory structure.
|
|
111
|
+
// Next's standalone build does: it traces from the workspace root, so an app
|
|
112
|
+
// in a subdirectory arrives under that subdirectory rather than at the top
|
|
113
|
+
keepsLayout?: boolean;
|
|
114
|
+
// Process the runtime image starts, as argv rather than a shell string. It
|
|
115
|
+
// runs at the app's root, so a path relative to that survives dir
|
|
116
|
+
entrypoint: string[];
|
|
117
|
+
// Cache names mounted into the builder, keyed per app and environment so two
|
|
118
|
+
// apps or two environments never share one
|
|
119
|
+
caches: string[];
|
|
120
|
+
// Whether the checkout follows the submodules named in .gitmodules. An empty
|
|
121
|
+
// submodule directory builds a working image with the contents missing
|
|
122
|
+
submodules: boolean;
|
|
123
|
+
// Packages the builder needs before any step runs, typically an SSH client
|
|
124
|
+
aptPackages: string[];
|
|
125
|
+
// Packages the runtime image needs, typically curl for the health probe
|
|
126
|
+
runtimePackages: string[];
|
|
127
|
+
// Shell commands run in the runtime image, for what a package manager cannot
|
|
128
|
+
// install. Above the output copy, so a new commit does not repeat them
|
|
129
|
+
runtimeSteps: string[];
|
|
130
|
+
// Uploads source maps during the build, then deletes them from the image
|
|
131
|
+
sourcemaps?: SourcemapSpec;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export type SourcemapSpec = {
|
|
135
|
+
// Which service receives the maps, "sentry" is the only one today
|
|
136
|
+
provider: "sentry";
|
|
137
|
+
// Env var stripped from the shipped file, it is a build-time credential and
|
|
138
|
+
// has no reason to travel inside the image
|
|
139
|
+
stripFromImage: string;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// How a build is decided to work. The commands run in the builder image, which
|
|
143
|
+
// is the one holding the test runner and the dev dependencies, so nothing has
|
|
144
|
+
// to be installed to check a release that is already compiled
|
|
145
|
+
export type VerifySpec = {
|
|
146
|
+
// Shell commands run in order. A non-zero exit fails the run, and the first
|
|
147
|
+
// is usually whatever brings the test database to the schema the tests want
|
|
148
|
+
steps: string[];
|
|
149
|
+
// Defaults to the deployment network, which is what resolves a service by
|
|
150
|
+
// the alias the app already uses: postgres answers at postgres
|
|
151
|
+
network?: StepNetwork;
|
|
152
|
+
// Settings the checks need beside the ones the image was built with, a test
|
|
153
|
+
// database url among them
|
|
154
|
+
environment?: Record<string, string>;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export type HealthSpec = {
|
|
158
|
+
// Path probed on the container itself, not through the proxy
|
|
159
|
+
path: string;
|
|
160
|
+
// Decides whether the parsed body means healthy. A body that answers but
|
|
161
|
+
// fails this is a retry, not a verdict, the container may still be starting
|
|
162
|
+
expect: (body: Record<string, unknown>) => boolean;
|
|
163
|
+
// Attempts before the deploy reverts. Defaults to 5
|
|
164
|
+
retries?: number;
|
|
165
|
+
// Wait between attempts in milliseconds. Defaults to 5000
|
|
166
|
+
intervalMs?: number;
|
|
167
|
+
// Wait before the first attempt, covering ordinary start-up. Defaults to 10000
|
|
168
|
+
delayMs?: number;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export type AppSpec = {
|
|
172
|
+
// Identifies the app. Becomes its container name, cache keys, volume names
|
|
173
|
+
// and nginx upstream, so changing it orphans everything named after it
|
|
174
|
+
name: string;
|
|
175
|
+
// Clone URL, fetched over the forwarded SSH agent rather than with a token.
|
|
176
|
+
// Exactly one of this and path: the source is either cloned or already here
|
|
177
|
+
repo?: string;
|
|
178
|
+
// A directory on this machine, built as it stands rather than cloned. What a
|
|
179
|
+
// CI job already checked out is one, and so is the copy you are editing.
|
|
180
|
+
// Relative to the deployment file. The branch an environment names is not
|
|
181
|
+
// read for one of these: what is on disk is what ships
|
|
182
|
+
path?: string;
|
|
183
|
+
// What of that directory goes into the build, relative to it. Only for a
|
|
184
|
+
// path, and only needed where git cannot answer: a work tree's .gitignore
|
|
185
|
+
// already says it. Given here it wins, which is how a repository is narrowed
|
|
186
|
+
// to the one app inside it
|
|
187
|
+
include?: string[];
|
|
188
|
+
// Nginx location this app answers. "/" is the catch-all, and a mounted route
|
|
189
|
+
// such as "/api/" has its prefix stripped before the request is proxied
|
|
190
|
+
route: string;
|
|
191
|
+
// Port the app listens on inside its container, and which nginx proxies to
|
|
192
|
+
port: number;
|
|
193
|
+
// Environment for the app. Several are merged in the order written, so a
|
|
194
|
+
// shared ref can come first and a per-app one override it
|
|
195
|
+
secrets?: SecretRefs;
|
|
196
|
+
// Where the app sits in the repository, for a monorepo whose root is not it.
|
|
197
|
+
// Build steps and the shipped command run there. The dependency install does
|
|
198
|
+
// not: a workspace lockfile is resolved at the root for every package at once
|
|
199
|
+
dir?: string;
|
|
200
|
+
// How the repository becomes a runnable image
|
|
201
|
+
build: BuildSpec;
|
|
202
|
+
// What has to be true before traffic is allowed to move to the new container
|
|
203
|
+
health: HealthSpec;
|
|
204
|
+
// What has to be true for the build to be worth deploying at all. Only
|
|
205
|
+
// `redkite verify` runs these, so a deploy never pays for them
|
|
206
|
+
verify?: VerifySpec;
|
|
207
|
+
// Volume name to container path, for state that outlives a deploy
|
|
208
|
+
volumes?: Record<string, string>;
|
|
209
|
+
// Plain environment for the running container, for what is configuration
|
|
210
|
+
// rather than a credential. Secrets come from the vault instead
|
|
211
|
+
environment?: Record<string, string>;
|
|
212
|
+
// Container path to the item whose contents land there, for credentials that
|
|
213
|
+
// have to be a file rather than an environment variable
|
|
214
|
+
files?: Record<string, SecretRef>;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
export type Deployment = {
|
|
218
|
+
// Prefixes every container, network and volume, and separates one project's
|
|
219
|
+
// objects from another's on a shared host
|
|
220
|
+
project: string;
|
|
221
|
+
// Overrides whichever one was selected, for a deployment that has only one
|
|
222
|
+
// and no reason to keep it in a file. Two of them is two files
|
|
223
|
+
environment?: Environment;
|
|
224
|
+
// Filled by the loader from the redkite.<name>.config.ts files beside this
|
|
225
|
+
// one, and from anything package.json names. A deployment does not declare
|
|
226
|
+
// them: the thing that differs between staging and production is a file, not
|
|
227
|
+
// a key several levels down a literal
|
|
228
|
+
environments?: Record<string, Environment>;
|
|
229
|
+
// Largest request body the proxy accepts before answering 413
|
|
230
|
+
maxBodySize?: string;
|
|
231
|
+
// Image the derived proxy runs, pinned rather than floating
|
|
232
|
+
proxyImage?: string;
|
|
233
|
+
// Long-lived containers shared by the apps, not rebuilt on every deploy.
|
|
234
|
+
// The proxy is not one of them: apps with routes imply exactly one, so it is
|
|
235
|
+
// derived rather than listed
|
|
236
|
+
services: ServiceSpec[];
|
|
237
|
+
// The applications. Everything derived, addresses, container names, nginx
|
|
238
|
+
// upstreams and location blocks, is a function of this list
|
|
239
|
+
apps: AppSpec[];
|
|
240
|
+
// Work injected into the run, from this file or from a plugin that answers
|
|
241
|
+
// with some. Addressed by where it runs, so nothing here has to be called
|
|
242
|
+
steps?: AnyStep[];
|
|
243
|
+
};
|