redkite 0.1.7 → 0.1.9

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 (123) hide show
  1. package/README.md +343 -26
  2. package/dist/build.d.ts +2 -0
  3. package/dist/build.d.ts.map +1 -1
  4. package/dist/build.js +11 -1
  5. package/dist/build.js.map +1 -1
  6. package/dist/checks.d.ts.map +1 -1
  7. package/dist/checks.js +4 -0
  8. package/dist/checks.js.map +1 -1
  9. package/dist/cli/agent.d.ts +2 -0
  10. package/dist/cli/agent.d.ts.map +1 -1
  11. package/dist/cli/agent.js +23 -1
  12. package/dist/cli/agent.js.map +1 -1
  13. package/dist/cli/config.d.ts.map +1 -1
  14. package/dist/cli/config.js +15 -2
  15. package/dist/cli/config.js.map +1 -1
  16. package/dist/cli/index.d.ts.map +1 -1
  17. package/dist/cli/index.js +98 -30
  18. package/dist/cli/index.js.map +1 -1
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +20 -1
  21. package/dist/config.js.map +1 -1
  22. package/dist/deploy.d.ts +4 -1
  23. package/dist/deploy.d.ts.map +1 -1
  24. package/dist/deploy.js +77 -23
  25. package/dist/deploy.js.map +1 -1
  26. package/dist/dockerfile.js +6 -7
  27. package/dist/dockerfile.js.map +1 -1
  28. package/dist/environment.d.ts +5 -0
  29. package/dist/environment.d.ts.map +1 -0
  30. package/dist/environment.js +20 -0
  31. package/dist/environment.js.map +1 -0
  32. package/dist/host.d.ts +2 -0
  33. package/dist/host.d.ts.map +1 -1
  34. package/dist/host.js +6 -0
  35. package/dist/host.js.map +1 -1
  36. package/dist/index.d.ts +4 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +4 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/localHost.d.ts.map +1 -1
  41. package/dist/localHost.js +2 -0
  42. package/dist/localHost.js.map +1 -1
  43. package/dist/plugin.d.ts +15 -0
  44. package/dist/plugin.d.ts.map +1 -0
  45. package/dist/plugin.js +21 -0
  46. package/dist/plugin.js.map +1 -0
  47. package/dist/plugins/bitwarden.d.ts +11 -0
  48. package/dist/plugins/bitwarden.d.ts.map +1 -0
  49. package/dist/plugins/bitwarden.js +40 -0
  50. package/dist/plugins/bitwarden.js.map +1 -0
  51. package/dist/plugins/digitalOcean.d.ts +19 -0
  52. package/dist/plugins/digitalOcean.d.ts.map +1 -0
  53. package/dist/plugins/digitalOcean.js +71 -0
  54. package/dist/plugins/digitalOcean.js.map +1 -0
  55. package/dist/plugins/index.d.ts +5 -0
  56. package/dist/plugins/index.d.ts.map +1 -0
  57. package/dist/plugins/index.js +5 -0
  58. package/dist/plugins/index.js.map +1 -0
  59. package/dist/plugins/rds.d.ts +14 -0
  60. package/dist/plugins/rds.d.ts.map +1 -0
  61. package/dist/plugins/rds.js +86 -0
  62. package/dist/plugins/rds.js.map +1 -0
  63. package/dist/plugins/snapshot.d.ts +7 -0
  64. package/dist/plugins/snapshot.d.ts.map +1 -0
  65. package/dist/plugins/snapshot.js +29 -0
  66. package/dist/plugins/snapshot.js.map +1 -0
  67. package/dist/presets/nodeApp.d.ts +1 -2
  68. package/dist/presets/nodeApp.d.ts.map +1 -1
  69. package/dist/presets/nodeApp.js +0 -1
  70. package/dist/presets/nodeApp.js.map +1 -1
  71. package/dist/recover.d.ts +19 -0
  72. package/dist/recover.d.ts.map +1 -0
  73. package/dist/recover.js +58 -0
  74. package/dist/recover.js.map +1 -0
  75. package/dist/secrets/store.d.ts +5 -2
  76. package/dist/secrets/store.d.ts.map +1 -1
  77. package/dist/secrets/store.js +25 -14
  78. package/dist/secrets/store.js.map +1 -1
  79. package/dist/source.d.ts +6 -1
  80. package/dist/source.d.ts.map +1 -1
  81. package/dist/source.js +12 -4
  82. package/dist/source.js.map +1 -1
  83. package/dist/sshHost.d.ts +2 -0
  84. package/dist/sshHost.d.ts.map +1 -1
  85. package/dist/sshHost.js +29 -11
  86. package/dist/sshHost.js.map +1 -1
  87. package/dist/steps.d.ts +0 -7
  88. package/dist/steps.d.ts.map +1 -1
  89. package/dist/steps.js +6 -3
  90. package/dist/steps.js.map +1 -1
  91. package/dist/types.d.ts +7 -5
  92. package/dist/types.d.ts.map +1 -1
  93. package/package.json +1 -1
  94. package/src/build.ts +12 -1
  95. package/src/checks.ts +4 -0
  96. package/src/cli/agent.ts +28 -1
  97. package/src/cli/config.ts +19 -2
  98. package/src/cli/index.ts +118 -29
  99. package/src/config.ts +27 -1
  100. package/src/deploy.ts +101 -29
  101. package/src/dockerfile.ts +7 -11
  102. package/src/environment.ts +24 -0
  103. package/src/host.ts +11 -0
  104. package/src/index.ts +4 -1
  105. package/src/localHost.ts +3 -0
  106. package/src/plugin.ts +50 -0
  107. package/src/plugins/bitwarden.ts +57 -0
  108. package/src/plugins/digitalOcean.ts +111 -0
  109. package/src/plugins/index.ts +4 -0
  110. package/src/plugins/rds.ts +126 -0
  111. package/src/plugins/snapshot.ts +44 -0
  112. package/src/presets/nodeApp.ts +1 -3
  113. package/src/recover.ts +90 -0
  114. package/src/secrets/store.ts +39 -20
  115. package/src/source.ts +20 -5
  116. package/src/sshHost.ts +35 -11
  117. package/src/steps.ts +7 -6
  118. package/src/types.ts +29 -12
  119. package/dist/secrets/bitwarden.d.ts +0 -3
  120. package/dist/secrets/bitwarden.d.ts.map +0 -1
  121. package/dist/secrets/bitwarden.js +0 -6
  122. package/dist/secrets/bitwarden.js.map +0 -1
  123. package/src/secrets/bitwarden.ts +0 -7
package/dist/steps.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"steps.js","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAK5C,6EAA6E;AAC7E,+EAA+E;AAC/E,wEAAwE;AACxE,MAAM,UAAU,UAAU,CAAC,OAAoB,EAAE,QAAkB;IACjE,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAClD,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAClD,IAAI,OAAO,KAAK,YAAY;QAAE,OAAO,CAAC,aAAa,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAEpE,OAAO;QACL,0EAA0E;QAC1E,2EAA2E;QAC3E,6CAA6C;QAC7C,aAAa,QAAQ,CAAC,OAAO,EAAE;QAC/B,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,cAAc,IAAI,IAAI,EAAE,EAAE,CAAC;KACvF,CAAC;AACJ,CAAC;AAiBD,6EAA6E;AAC7E,oEAAoE;AACpE,MAAM,UAAU,OAAO,CAAC,OAAuB;IAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAE3C,OAAO;QACL,KAAK,EAAE,uBAAuB,OAAO,CAAC,GAAG,EAAE;QAC3C,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC;QAE/C,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YAC5B,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;YAC5C,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;YAE1D,MAAM,OAAO,CAAC,MAAM,CAAC,UAAU,CAC7B;gBACE,UAAU;gBACV,GAAG,UAAU,CAAC,OAAO,CAAC,OAAO,IAAI,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC;gBAC1D,gBAAgB;gBAChB,KAAK;gBACL,GAAG,OAAO;aACX,CAAC,IAAI,CAAC,GAAG,CAAC,EACX,GAAG,OAAO,CAAC,GAAG,oBAAoB,CACnC,CAAC;YAEF,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,gDAAgD;AAChD,SAAS,eAAe,CAAC,IAAU,EAAE,OAAuB;IAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IACvE,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,OAAO,CAAC,GAAG,kCAAkC,CAAC,CAAC;IAE5E,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC;IAC5E,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC9B,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,OAAO;QAAE,OAAO;IAElD,MAAM,IAAI,KAAK,CACb,GAAG,OAAO,CAAC,GAAG,kCAAkC,MAAM,CAAC,OAAO,IAAI;QAChE,OAAO,IAAI,CAAC,WAAW,eAAe,OAAO,IAAI,cAAc,IAAI;QACnE,+DAA+D,CAClE,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,qDAAqD;AACrD,MAAM,UAAU,SAAS,CAAC,KAAY,EAAE,IAAY;IAClD,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAC1D,IAAI,GAAG,EAAE,UAAU;QAAE,OAAO,GAAG,CAAC,UAAU,CAAC;IAE3C,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,oDAAoD,CAAC,CAAC;AAC/E,CAAC;AAID,MAAM,UAAU,MAAM,CAAC,OAAsB;IAC3C,OAAO,EAAE,QAAQ,EAAE,QAAiB,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,GAAY;IAClC,OAAO,GAAG,CAAC,KAAK,CAAC;AACnB,CAAC"}
1
+ {"version":3,"file":"steps.js","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { AnyStep } from "./pipeline.js";
2
+ import type { Plugin } from "./plugin.js";
2
3
  export type Environment = {
3
4
  buildOn?: "host" | "local";
4
5
  branch: string;
@@ -10,9 +11,11 @@ export type Environment = {
10
11
  export type StepNetwork = "host" | "deployment" | "none" | {
11
12
  named: string;
12
13
  };
14
+ export type HostKeys = "accept-new" | "strict" | "off";
13
15
  export type DeployHost = {
14
16
  bastion: string;
15
17
  socket?: string;
18
+ hostKeys?: HostKeys;
16
19
  };
17
20
  export type SecretRef = {
18
21
  provider: string;
@@ -53,11 +56,6 @@ export type BuildSpec = {
53
56
  aptPackages: string[];
54
57
  runtimePackages: string[];
55
58
  runtimeSteps: string[];
56
- sourcemaps?: SourcemapSpec;
57
- };
58
- export type SourcemapSpec = {
59
- provider: "sentry";
60
- stripFromImage: string;
61
59
  };
62
60
  export type VerifySpec = {
63
61
  steps: string[];
@@ -79,6 +77,9 @@ export type AppSpec = {
79
77
  route: string;
80
78
  port: number;
81
79
  secrets?: SecretRefs;
80
+ branch?: string;
81
+ tag?: string;
82
+ commit?: string;
82
83
  dir?: string;
83
84
  build: BuildSpec;
84
85
  health: HealthSpec;
@@ -96,5 +97,6 @@ export type Deployment = {
96
97
  services: ServiceSpec[];
97
98
  apps: AppSpec[];
98
99
  steps?: AnyStep[];
100
+ plugins?: Plugin[];
99
101
  };
100
102
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redkite",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Blue-green Docker deployment from one config file, over ssh. No engine, no agent, no dependencies.",
5
5
  "keywords": [
6
6
  "deploy",
package/src/build.ts CHANGED
@@ -3,6 +3,7 @@ import { resolve } from "node:path";
3
3
 
4
4
  import type { Docker } from "./docker.js";
5
5
  import { BUILDER_STAGE, renderDockerfile, renderDockerignore } from "./dockerfile.js";
6
+ import type { Ref } from "./source.js";
6
7
  import type { Host } from "./host.js";
7
8
  import { prepareSource } from "./source.js";
8
9
  import type { AppTopology } from "./topology.js";
@@ -48,6 +49,16 @@ export type BuildResult = {
48
49
  cached: boolean;
49
50
  };
50
51
 
52
+ // The app's own, or the environment's branch when it names none. A branch is
53
+ // the only thing an environment can say, because a tag or a commit is a claim
54
+ // about one repository and an environment spans every app
55
+ export function refOf(app: AppSpec, branch: string): Ref {
56
+ if (app.tag) return { kind: "tag", name: app.tag };
57
+ if (app.commit) return { kind: "commit", name: app.commit };
58
+
59
+ return { kind: "branch", name: app.branch ?? branch };
60
+ }
61
+
51
62
  export async function build(
52
63
  app: AppSpec,
53
64
  topology: AppTopology,
@@ -64,7 +75,7 @@ export async function build(
64
75
  // against the deployment file rather than wherever this was run
65
76
  path: app.path && resolve(app.path),
66
77
  include: app.include,
67
- branch: context.branch,
78
+ ref: refOf(app, context.branch),
68
79
  submodules: spec.submodules,
69
80
  detail,
70
81
  });
package/src/checks.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { envFlags } from "./environment.js";
1
2
  import { appRoot } from "./layout.js";
2
3
  import type { Built, Context, Plan, Released } from "./pipeline.js";
3
4
  import { quote } from "./shell.js";
@@ -31,6 +32,9 @@ async function checkApp(app: AppSpec, spec: VerifySpec, input: Built, context: C
31
32
  const flags = [
32
33
  "run --rm",
33
34
  ...attachment(spec.network ?? "deployment", context.topology),
35
+ // Before the named settings, so a check can override what the vault says
36
+ // without editing the vault
37
+ ...(await envFlags(app, context)),
34
38
  `--workdir ${appRoot(app.dir)}`,
35
39
  ...settings({ ...app.environment, ...spec.environment }),
36
40
  builderOf(input, app.name),
package/src/cli/agent.ts CHANGED
@@ -1,8 +1,26 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
 
3
+ import { environmentOf } from "../config.js";
4
+ import type { Deployment } from "../types.js";
5
+
3
6
  // Cloning private repositories and opening the tunnel both go through the
4
7
  // agent, so a deploy without one fails halfway rather than at the start
5
8
 
9
+ // Asked before one is demanded. A runner has no keys and no way to be prompted
10
+ // for one, and a deployment that reaches no other machine and clones nothing
11
+ // has no use for an agent: insisting on one there is a green run turned red
12
+ export function needsAgent(config: Deployment, environment: string) {
13
+ if (environmentOf(config, environment)?.host?.bastion) return true;
14
+
15
+ return config.apps.some((app) => app.repo !== undefined && overSsh(app.repo));
16
+ }
17
+
18
+ // A clone URL that names a transport of its own carries its own credentials.
19
+ // Anything else is the scp-like form, which is ssh and wants a key
20
+ function overSsh(repo: string) {
21
+ return !/^(https?|git|file):\/\//.test(repo);
22
+ }
23
+
6
24
  // A plain writer rather than a Log, because this runs before the view opens:
7
25
  // ssh-add may ask for a passphrase, and it cannot ask through a screen
8
26
  // something else is drawing
@@ -22,7 +40,16 @@ export function requireAgent(warn: (message: string) => void) {
22
40
  if (pid) process.env["SSH_AGENT_PID"] = pid;
23
41
 
24
42
  // Inherits stdio so a passphrase prompt reaches the person running this
25
- execFileSync("ssh-add", [], { stdio: "inherit" });
43
+ try {
44
+ execFileSync("ssh-add", [], { stdio: "inherit" });
45
+ } catch {
46
+ throw new Error(
47
+ "ssh-add found no key to load. This deployment reaches another machine " +
48
+ "or clones over ssh, so it needs an agent holding a key that can. On a " +
49
+ "runner, set one up before this step and leave SSH_AUTH_SOCK in the " +
50
+ "environment",
51
+ );
52
+ }
26
53
 
27
54
  return socket;
28
55
  }
package/src/cli/config.ts CHANGED
@@ -16,8 +16,16 @@ const EXTENSIONS = ["ts", "mts", "js", "mjs"] as const;
16
16
 
17
17
  const CANDIDATES = EXTENSIONS.map((extension) => `redkite.config.${extension}`);
18
18
 
19
- // redkite.<environment>.config.<extension>, beside the deployment it belongs to
20
- const PER_ENVIRONMENT = /^redkite\.([a-z0-9-]+)\.config\.(ts|mts|js|mjs)$/;
19
+ // Anything beside the deployment that starts with redkite and is a module it
20
+ // could read. The .config in the middle is what people write and what the docs
21
+ // say, and it is optional here because a file that was meant to be an
22
+ // environment and is named slightly differently should be read and refused
23
+ // rather than passed over as though it were not there
24
+ const PER_ENVIRONMENT = /^redkite[.\-_](.+?)(?:\.config)?\.(?:ts|mts|cts|js|mjs|cjs)$/;
25
+
26
+ // What an environment may be called. It becomes part of an image tag, and an
27
+ // image tag cannot hold a capital, so this is docker's limit rather than ours
28
+ const ENVIRONMENT_NAME = /^[a-z0-9][a-z0-9._-]*$/;
21
29
 
22
30
  // Node strips types itself from 22.18 on, which is what lets redkite ship with
23
31
  // no dependencies. These are the ways that can fail on a config it cannot read.
@@ -121,9 +129,18 @@ export async function loadEnvironments(directory: string) {
121
129
  const seen: Record<string, string> = {};
122
130
 
123
131
  for (const entry of readdirSync(directory).sort()) {
132
+ if (CANDIDATES.includes(entry)) continue;
133
+
124
134
  const name = PER_ENVIRONMENT.exec(entry)?.[1];
125
135
  if (!name) continue;
126
136
 
137
+ if (!ENVIRONMENT_NAME.test(name)) {
138
+ throw new Error(
139
+ `${join(directory, entry)} reads as the environment ${name}, which cannot ` +
140
+ "name one: it becomes part of an image tag, and a tag is lower case",
141
+ );
142
+ }
143
+
127
144
  const first = seen[name];
128
145
  if (first) {
129
146
  throw new Error(`${name} is defined by both ${first} and ${entry}`);
package/src/cli/index.ts CHANGED
@@ -7,13 +7,14 @@ import { silent, type Log } from "../log.js";
7
7
  import { renderNginx } from "../nginx.js";
8
8
  import { addressOf, RUNS, SLOTS, type Run } from "../pipeline.js";
9
9
  import { listRefs, type SecretStores } from "../secrets/refs.js";
10
- import { bitwardenStore } from "../secrets/store.js";
10
+ import { pluginSteps, storeFor } from "../plugin.js";
11
+ import { down, rollback } from "../recover.js";
11
12
  import { driftOf, plannedServices, type Drift } from "../services/planned.js";
12
13
  import { sshHost } from "../sshHost.js";
13
14
  import { topologyFor, type Topology } from "../topology.js";
14
15
  import type { Deployment, DeployHost } from "../types.js";
15
16
 
16
- import { requireAgent } from "./agent.js";
17
+ import { needsAgent, requireAgent } from "./agent.js";
17
18
  import { loadConfig } from "./config.js";
18
19
  import { createLog, describeFailure } from "./log.js";
19
20
 
@@ -25,6 +26,8 @@ const USAGE = `redkite <command> [environment]
25
26
  plan [environment] Print the derived topology, the pipeline and the nginx
26
27
  deploy [environment] Build, swap, health check, and revert on failure
27
28
  verify [environment] Bring the services up, build, and run each app's checks
29
+ rollback [environment] Put back any app a run moved and did not finish
30
+ down [environment] Stop everything this environment named
28
31
 
29
32
  --config <path> Defaults to redkite.config.ts at the root of the project
30
33
  --local Build the images here and ship them to the host
@@ -46,6 +49,7 @@ function measured(host: Host, log?: Log) {
46
49
  cache: host.cache,
47
50
  pipe: host.pipe.bind(host),
48
51
  stop: host.stop.bind(host),
52
+ final: host.final.bind(host),
49
53
 
50
54
  sh: async (command, onLine) => {
51
55
  const started = Date.now();
@@ -143,6 +147,9 @@ async function dispatch(argv: string[]) {
143
147
  const configPath = flag(argv, "--config");
144
148
 
145
149
  if (command === "plan") return await plan(environment, configPath);
150
+ if (command === "rollback" || command === "down") {
151
+ return await recover(command, environment, configPath);
152
+ }
146
153
  if (command === "deploy" || command === "verify") {
147
154
  return await run(command, environment, configPath, {
148
155
  verbose: argv.includes("--verbose"),
@@ -155,6 +162,49 @@ async function dispatch(argv: string[]) {
155
162
  process.exitCode = command ? 1 : 0;
156
163
  }
157
164
 
165
+ // Neither runs a pipeline. Both exist for the run that was killed rather than
166
+ // asked to stop, so they read what the host is in rather than what a deploy
167
+ // remembered, and both are safe where there is nothing to do
168
+ async function recover(
169
+ command: "rollback" | "down",
170
+ environment: string,
171
+ configPath?: string,
172
+ ) {
173
+ const config = await loadConfig(configPath);
174
+ const topology = topologyFor(config, environment);
175
+ const deployHost = environmentOf(config, environment)?.host;
176
+
177
+ const say = (message = "") => process.stdout.write(`${message}\n`);
178
+ if (needsAgent(config, environment)) requireAgent(say);
179
+
180
+ const host = await hostFor(deployHost, silent);
181
+
182
+ try {
183
+ if (command === "down") {
184
+ const { stopped } = await down({ config, environment, host, log: asLog(say) });
185
+
186
+ say(stopped.length === 0 ? "Nothing was running" : `Stopped ${stopped.join(", ")}`);
187
+ return;
188
+ }
189
+
190
+ const { restored } = await rollback({ config, environment, host, log: asLog(say) });
191
+
192
+ if (restored.length === 0) {
193
+ return say(`Nothing to put back in ${topology.environment}`);
194
+ }
195
+
196
+ say(`Put back ${restored.join(", ")}`);
197
+ } finally {
198
+ await host.close?.();
199
+ }
200
+ }
201
+
202
+ // These print rather than draw: a recovery runs after the view has gone, and
203
+ // often in a job that is already being torn down
204
+ function asLog(say: (message: string) => void): Log {
205
+ return Object.assign(say, { warn: say, fail: say, done: say, step: () => silent.step("") });
206
+ }
207
+
158
208
  async function plan(environment: string, configPath?: string) {
159
209
  const config = await loadConfig(configPath);
160
210
  const topology = topologyFor(config, environment);
@@ -203,6 +253,7 @@ async function plan(environment: string, configPath?: string) {
203
253
  // run it supports. Nothing declares that: the missing port is what says it
204
254
  const serves = Boolean(topology.publicPort);
205
255
 
256
+ sayPlugins(config, say);
206
257
  sayPipeline(config, serves, say);
207
258
  await sayDrift(config, topology, serves ? "deploy" : "verify", say);
208
259
 
@@ -250,6 +301,29 @@ const DRIFT: Record<Drift["reason"], string> = {
250
301
  unrecognised: "not created by redkite, will be recreated",
251
302
  };
252
303
 
304
+ // Everything this deployment opted into. A vault that is missing from here is
305
+ // the reason its refs will not resolve, and that is worth seeing before a run
306
+ function sayPlugins(config: Deployment, say: (message?: string) => void) {
307
+ const plugins = config.plugins ?? [];
308
+ if (plugins.length === 0) return;
309
+
310
+ say("\nplugins");
311
+
312
+ for (const plugin of plugins) {
313
+ const stores = Object.keys(plugin.stores ?? {});
314
+ const resolves = stores.length > 0 ? ` resolves ${stores.join(", ")}` : "";
315
+ const count = plugin.steps?.length ?? 0;
316
+
317
+ say(` ${plugin.name}${gap(plugin.name.length)}${count} ${count === 1 ? "step" : "steps"}${resolves}`);
318
+ }
319
+ }
320
+
321
+ const COLUMN = 26;
322
+
323
+ function gap(width: number) {
324
+ return " ".repeat(Math.max(2, COLUMN - width));
325
+ }
326
+
253
327
  // What each run will actually do, in the order it will do it. A step is
254
328
  // addressed rather than called, so this is the only place a sequence is visible
255
329
  function sayPipeline(
@@ -257,9 +331,19 @@ function sayPipeline(
257
331
  serves: boolean,
258
332
  say: (message?: string) => void,
259
333
  ) {
260
- const steps = config.steps ?? [];
334
+ // What the run will walk, which is the plugins' steps and then the
335
+ // deployment's own. A plan that showed only one of them would be a plan of a
336
+ // different deploy
337
+ const steps = [...pluginSteps(config.plugins), ...(config.steps ?? [])];
261
338
  const added = new Set(steps.map((step) => step.point));
262
339
 
340
+ // Says which plugin a step came from, because a point on its own does not
341
+ const from = new Map(
342
+ (config.plugins ?? []).flatMap((plugin) =>
343
+ (plugin.steps ?? []).map((step) => [step.point, plugin.name] as const),
344
+ ),
345
+ );
346
+
263
347
  const at = (phase: string, slot: string) =>
264
348
  steps.filter((step) => {
265
349
  const address = addressOf(step.point);
@@ -294,7 +378,12 @@ function sayPipeline(
294
378
  say(` ${phase.padEnd(26)}${who}${what}`);
295
379
  }
296
380
 
297
- for (const step of at(phase, slot)) say(` ${step.point}`);
381
+ for (const step of at(phase, slot)) {
382
+ const owner = from.get(step.point);
383
+ if (!owner) say(` ${step.point}`);
384
+ // A point long enough to fill the column still gets its two spaces
385
+ else say(` ${step.point}${gap(step.point.length)}${owner}`);
386
+ }
298
387
  }
299
388
  }
300
389
  }
@@ -395,7 +484,7 @@ async function hostFor(host: DeployHost | undefined, log: Log, signal?: AbortSig
395
484
  const task = log.step(`Opening a connection to ${host.bastion}`);
396
485
 
397
486
  try {
398
- const opened = await sshHost(host.bastion, { signal });
487
+ const opened = await sshHost(host.bastion, { signal, hostKeys: host.hostKeys });
399
488
  task.done();
400
489
  return opened;
401
490
  } catch (error) {
@@ -406,6 +495,9 @@ async function hostFor(host: DeployHost | undefined, log: Log, signal?: AbortSig
406
495
 
407
496
  // Only the stores the config actually names. A deployment that keeps its
408
497
  // environment somewhere else, or nowhere, needs no credentials to deploy
498
+ // One store per provider a ref names, opened from the plugins the deployment
499
+ // registered. Nothing is opened for a deployment that names no refs, and a ref
500
+ // no plugin answers for is refused rather than reaching for a vault by name
409
501
  async function openStores(config: Deployment, log: Log): Promise<SecretStores> {
410
502
  // Services name refs too, and a deployment whose only secret is a database
411
503
  // password opened no store at all until this counted them
@@ -417,35 +509,30 @@ async function openStores(config: Deployment, log: Log): Promise<SecretStores> {
417
509
  ...config.services.flatMap((service) => listRefs(service.secrets)),
418
510
  ];
419
511
 
420
- const providers = new Set(refs.map((ref) => ref.provider));
512
+ const opened: SecretStores = {};
421
513
 
422
- if (!providers.has("bitwarden")) return {};
514
+ for (const provider of new Set(refs.map((ref) => ref.provider))) {
515
+ const open = storeFor(config.plugins, provider);
423
516
 
424
- const vault = log.step("Reading the vault");
517
+ if (!open) {
518
+ throw new Error(
519
+ `Nothing resolves ${provider} secrets. Add the plugin that does to ` +
520
+ "the plugins this deployment registers",
521
+ );
522
+ }
425
523
 
426
- try {
427
- const bitwarden = await bitwardenStore({
428
- detail: vault.detail,
429
- clientId: required("BW_CLIENT_ID"),
430
- clientSecret: required("BW_CLIENT_SECRET"),
431
- password: required("BW_PASSWORD"),
432
- });
524
+ const task = log.step(`Reading the ${provider} vault`);
433
525
 
434
- vault.done();
435
- return { bitwarden };
436
- } catch (error) {
437
- vault.fail("Could not read the vault");
438
- throw error;
526
+ try {
527
+ opened[provider] = await open({ detail: task.detail });
528
+ task.done();
529
+ } catch (error) {
530
+ task.fail(`Could not read the ${provider} vault`);
531
+ throw error;
532
+ }
439
533
  }
440
- }
441
-
442
- function required(name: string) {
443
- const value = process.env[name];
444
- if (value) return value;
445
534
 
446
- throw new Error(
447
- `${name} is not set, and this deployment reads its environment from Bitwarden`,
448
- );
535
+ return opened;
449
536
  }
450
537
 
451
538
  type RunOptions = { verbose: boolean; full: boolean; local: boolean };
@@ -465,7 +552,9 @@ async function run(
465
552
  // The host clones the repositories over this, so it has to exist before the
466
553
  // connection that forwards it is opened. Before the view too: ssh-add asks
467
554
  // for a passphrase on the terminal, and by then the view owns it
468
- requireAgent((message) => process.stderr.write(`${message}\n`));
555
+ if (needsAgent(config, environment)) {
556
+ requireAgent((message) => process.stderr.write(`${message}\n`));
557
+ }
469
558
 
470
559
  // Whatever is in flight is killed, the pipeline unwinds through its own
471
560
  // failure path, and the finally below removes the scratch directory
package/src/config.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { assertSteps } from "./pipeline.js";
2
+ import { pluginSteps } from "./plugin.js";
2
3
  import type { Deployment, Environment } from "./types.js";
3
4
 
4
5
  // The one selected, from the one place worth looking. A deployment that carries
@@ -19,7 +20,10 @@ export function defineDeployment<const T extends Deployment & { environments?: n
19
20
  assertOneSource(config);
20
21
  assertRoutesResolvable(config);
21
22
  assertDirsRelative(config);
22
- assertSteps(config.steps ?? []);
23
+ assertUniquePlugins(config);
24
+ // Together, because a plugin's step and the deployment's own share the same
25
+ // points and two of them at one point is the collision worth catching
26
+ assertSteps([...pluginSteps(config.plugins), ...(config.steps ?? [])]);
23
27
  return config;
24
28
  }
25
29
 
@@ -29,6 +33,20 @@ export function defineEnvironment<const T extends Environment>(environment: T):
29
33
  return environment;
30
34
  }
31
35
 
36
+ // Registering one twice is either a mistake or two configurations of the same
37
+ // thing, and neither is something to pick a winner for
38
+ function assertUniquePlugins(config: Deployment) {
39
+ const seen = new Set<string>();
40
+
41
+ for (const plugin of config.plugins ?? []) {
42
+ if (seen.has(plugin.name)) {
43
+ throw new Error(`The plugin ${plugin.name} is registered twice`);
44
+ }
45
+
46
+ seen.add(plugin.name);
47
+ }
48
+ }
49
+
32
50
  // Cloned or already here, and the two are built differently enough that
33
51
  // guessing between them is worse than being told
34
52
  function assertOneSource(config: Deployment) {
@@ -47,6 +65,14 @@ function assertOneSource(config: Deployment) {
47
65
  throw new Error(`${app.name} says what to include, but is cloned rather than built from a path`);
48
66
  }
49
67
 
68
+ const refs = ["branch", "tag", "commit"].filter((kind) => app[kind as "branch"]);
69
+
70
+ if (refs.length > 1) {
71
+ throw new Error(
72
+ `${app.name} names ${refs.join(" and ")} to build from, and they are different commits`,
73
+ );
74
+ }
75
+
50
76
  if (app.include?.length === 0) {
51
77
  throw new Error(`${app.name} includes nothing, so there would be no build context`);
52
78
  }