-
Notifications
You must be signed in to change notification settings - Fork 273
/
Copy pathdeploy.ts
327 lines (283 loc) · 12.4 KB
/
deploy.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
/*
* Copyright (C) 2018-2023 Garden Technologies, Inc. <[email protected]>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import deline = require("deline")
import dedent = require("dedent")
import chalk = require("chalk")
import {
Command,
CommandParams,
CommandResult,
handleProcessResults,
PrepareParams,
processCommandResultSchema,
ProcessCommandResult,
} from "./base"
import { printEmoji, printHeader } from "../logger/util"
import { watchParameter, watchRemovedWarning } from "./helpers"
import { DeployTask } from "../tasks/deploy"
import { naturalList } from "../util/string"
import { StringsParameter, BooleanParameter } from "../cli/params"
import { Garden } from "../garden"
import { ActionModeMap } from "../actions/types"
import { SyncMonitor } from "../monitors/sync"
import { warnOnLinkedActions } from "../actions/helpers"
import { PluginEventBroker } from "../plugin-context"
import { HandlerMonitor } from "../monitors/handler"
import { PortForwardMonitor } from "../monitors/port-forward"
import { LogMonitor } from "../monitors/logs"
import { LoggerType, parseLogLevel } from "../logger/logger"
import { serveOpts } from "./serve"
import { DevCommand } from "./dev"
import { gardenEnv } from "../constants"
export const deployArgs = {
names: new StringsParameter({
help: deline`The name(s) of the Deploy(s) (or services if using modules) to deploy (skip to deploy everything).
You may specify multiple names, separated by spaces.`,
spread: true,
getSuggestions: ({ configDump }) => {
return Object.keys(configDump.actionConfigs.Deploy)
},
}),
}
export const deployOpts = {
"force": new BooleanParameter({ help: "Force re-deploy." }),
"force-build": new BooleanParameter({ help: "Force re-build of build dependencies." }),
"watch": watchParameter,
"sync": new StringsParameter({
help: dedent`
The name(s) of the Deploy(s) to deploy with sync enabled.
You may specify multiple names by setting this flag multiple times.
Use * to deploy all supported deployments with sync enabled.
Important: The syncs stay active after the command exits. To stop the syncs, use the \`sync stop\` command.
`,
aliases: ["dev", "dev-mode"],
getSuggestions: ({ configDump }) => {
return Object.keys(configDump.actionConfigs.Deploy)
},
}),
"local-mode": new StringsParameter({
help: dedent`
[EXPERIMENTAL] The name(s) of Deploy(s) to be started locally with local mode enabled.
You may specify multiple Deploys by setting this flag multiple times. Use * to deploy all Deploys with local mode enabled. When this option is used,
the command stays running until explicitly aborted.
This always takes the precedence over sync mode if there are any conflicts, i.e. if the same Deploys are matched with both \`--sync\` and \`--local\` options.
`,
aliases: ["local"],
getSuggestions: ({ configDump }) => {
return Object.keys(configDump.actionConfigs.Deploy)
},
}),
"skip": new StringsParameter({
help: "The name(s) of Deploys you'd like to skip.",
getSuggestions: ({ configDump }) => {
return Object.keys(configDump.actionConfigs.Deploy)
},
}),
"skip-dependencies": new BooleanParameter({
help: deline`
Deploy the specified actions, but don't build, deploy or run any dependencies. This option can only be used when a list of Deploy names is passed as CLI arguments.
This can be useful e.g. when your stack has already been deployed, and you want to run specific Deploys in sync mode without building, deploying or running dependencies that may have changed since you last deployed.
`,
aliases: ["nodeps"],
}),
"disable-port-forwards": new BooleanParameter({
help: "Disable automatic port forwarding when running persistently. Note that you can also set GARDEN_DISABLE_PORT_FORWARDS=true in your environment.",
}),
"forward": new BooleanParameter({
help: `Create port forwards and leave process running after deploying. This is implied if any of --sync / --local or --logs are set.`,
}),
"logs": new BooleanParameter({
help: `Stream logs from the requested Deploy(s) (or services if using modules) during deployment, and leave the log streaming process running after deploying. Note: This option implies the --forward option.`,
}),
"timestamps": new BooleanParameter({
help: "Show timestamps with log output. Should be used with the `--logs` option (has no effect if that option is not used).",
}),
...serveOpts,
}
type Args = typeof deployArgs
type Opts = typeof deployOpts
export class DeployCommand extends Command<Args, Opts> {
name = "deploy"
help = "Deploy actions to your environment."
protected = true
streamEvents = true
description = dedent`
Deploys all or specified Deploy actions, taking into account dependency order.
Also performs builds and other dependencies if needed.
Optionally stays running and automatically re-builds and re-deploys if sources
(or dependencies' sources) change.
Examples:
garden deploy # deploy everything in the project
garden deploy my-deploy # only deploy my-deploy
garden deploy deploy-a,deploy-b # only deploy deploy-a and deploy-b
garden deploy --force # force re-deploy, even for deploys already deployed and up-to-date
garden deploy --sync=my-deploy # deploys all Deploys, with sync enabled for my-deploy
garden deploy --sync # deploys all compatible Deploys with sync enabled
garden deploy --local=my-deploy # deploys all Deploys, with local mode enabled for my-deploy
garden deploy --local # deploys all compatible Deploys with local mode enabled
garden deploy --env stage # deploy your Deploys to an environment called stage
garden deploy --skip deploy-b # deploy everything except deploy-b
garden deploy --forward # deploy everything and start port forwards without sync or local mode
`
arguments = deployArgs
options = deployOpts
private garden?: Garden
outputsSchema = () => processCommandResultSchema()
maybePersistent({ opts }: PrepareParams<Args, Opts>) {
return !!opts["sync"] || !!opts["local-mode"] || !!opts.forward || !!opts.logs
}
printHeader({ log }) {
printHeader(log, "Deploy", "🚀")
}
getTerminalWriterType(params): LoggerType {
return this.maybePersistent(params) ? "ink" : "default"
}
terminate() {
super.terminate()
this.garden?.events.emit("_exit", {})
}
async action(params: CommandParams<Args, Opts>): Promise<CommandResult<ProcessCommandResult>> {
const { garden, log, args, opts } = params
this.garden = garden
if (opts.watch) {
await watchRemovedWarning(garden, log)
}
const monitor = this.maybePersistent(params)
if (monitor && !params.parentCommand) {
// Then we're not in the dev command yet, so we call that instead with the appropriate initial command.
// TODO: Abstract this delegation process into a helper if we write more commands that do this sort of thing.
params.opts.cmd = ["deploy " + params.args.$all!.join(" ")]
const devCmd = new DevCommand()
devCmd.printHeader(params)
await devCmd.prepare(params)
return devCmd.action(params)
}
const disablePortForwards = gardenEnv.GARDEN_DISABLE_PORT_FORWARDS || opts["disable-port-forwards"] || false
// TODO-0.13.0: make these both explicit options
let forward = monitor && !disablePortForwards
const streamLogs = opts.logs
const actionModes: ActionModeMap = {
// Support a single empty value (which comes across as an empty list) as equivalent to '*'
local: opts["local-mode"]?.length === 0 ? ["*"] : opts["local-mode"]?.map((s) => "deploy." + s),
sync: opts.sync?.length === 0 ? ["*"] : opts.sync?.map((s) => "deploy." + s),
}
const graph = await garden.getConfigGraph({ log, emit: true, actionModes })
let deployActions = graph.getDeploys({ names: args.names, includeDisabled: true })
const disabled = deployActions.filter((s) => s.isDisabled()).map((s) => s.name)
if (disabled.length > 0) {
const bold = disabled.map((d) => chalk.white(d))
const msg =
disabled.length === 1 ? `Deploy action ${bold} is disabled` : `Deploy actions ${naturalList(bold)} are disabled`
log.info(chalk.gray(msg))
}
const skipped = opts.skip || []
deployActions = deployActions.filter((s) => !s.isDisabled() && !skipped.includes(s.name))
if (deployActions.length === 0) {
log.error({ msg: "Nothing to deploy. Aborting." })
return { result: { aborted: true, success: true, graphResults: {} } }
}
const skipRuntimeDependencies = opts["skip-dependencies"]
if (skipRuntimeDependencies && (!args.names || args.names.length === 0)) {
const errMsg = deline`
No names were provided as CLI arguments, but the --skip-dependencies option was used. Please provide a
list of names when using the --skip-dependencies option.
`
log.error({ msg: errMsg })
return { result: { aborted: true, success: false, graphResults: {} } }
}
const force = opts.force
const startSync = !!opts.sync
await warnOnLinkedActions(garden, log, deployActions)
if (streamLogs) {
const resolved = await garden.resolveActions({ actions: deployActions, graph, log })
for (const action of Object.values(resolved)) {
const logMonitor = new LogMonitor({
garden,
log,
action,
graph,
collect: false,
hideService: false,
showTags: false,
msgPrefix: printEmoji("▶", log),
logLevel: parseLogLevel(opts["log-level"]),
tagFilters: undefined,
showTimestamps: opts["timestamps"],
since: "1m",
})
garden.monitors.addAndSubscribe(logMonitor, this)
}
}
const tasks = deployActions.map((action) => {
const events = new PluginEventBroker(garden)
const task = new DeployTask({
garden,
log,
graph,
action,
force,
forceBuild: opts["force-build"],
skipRuntimeDependencies,
startSync,
events,
})
if (monitor) {
task.on("ready", ({ result }) => {
const executedAction = result?.executedAction
const mode = executedAction.mode()
if (forward) {
// Start port forwards for ready deployments
const portForwardMonitor = new PortForwardMonitor({
garden,
log,
graph,
action: executedAction,
})
garden.monitors.addAndSubscribe(portForwardMonitor, this)
}
if (mode === "sync") {
const syncMonitor = new SyncMonitor({
garden,
log,
action: executedAction,
graph,
stopOnExit: true, // On this code path, we're running inside the `dev` command.
})
garden.monitors.addAndSubscribe(syncMonitor, this)
} else if (mode === "local" && result.attached) {
// Wait for local mode processes to complete.
const handlerMonitor = new HandlerMonitor({
type: "local-deploy",
garden,
log,
events,
key: action.key(),
description: "monitor for attached local mode process in " + action.longDescription(),
})
garden.monitors.addAndSubscribe(handlerMonitor, this)
} else if (result.attached) {
// Wait for other attached processes after deployment.
// Note: No plugin currently does this outside of local mode but we do support it.
const handlerMonitor = new HandlerMonitor({
type: "deploy",
garden,
log,
events,
key: action.key(),
description: "monitor for attached process in " + action.longDescription(),
})
garden.monitors.addAndSubscribe(handlerMonitor, this)
}
})
}
return task
})
const results = await garden.processTasks({ tasks, log })
return handleProcessResults(garden, log, "deploy", results)
}
}