Skip to content

Commit 9d9295f

Browse files
committed
feat(build): Handle config changes in auto-reload.
Before this change, each module directory was watched separately for changes during auto-reload, and didn't properly handle module- or project-level configuration changes since the auto-reload watch was started. Here, we consolidate the module-level watches into a single project-level watch, and call the originating command with a fresh PluginContext when configuration changes. This includes the addition or removal of modules during the lifetime of the watch.
1 parent 12cb5e1 commit 9d9295f

File tree

12 files changed

+367
-184
lines changed

12 files changed

+367
-184
lines changed

src/cli/cli.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,13 @@ export class GardenCli {
206206
}
207207

208208
const logger = RootLogNode.initialize({ level, writers })
209-
const garden = await Garden.factory(root, { env, logger })
210-
211-
// TODO: enforce that commands always output DeepPrimitiveMap
212-
const result = await command.action(garden.pluginContext, parsedArgs, parsedOpts)
209+
let garden
210+
let result
211+
do {
212+
garden = await Garden.factory(root, { env, logger })
213+
// TODO: enforce that commands always output DeepPrimitiveMap
214+
result = await command.action(garden.pluginContext, parsedArgs, parsedOpts)
215+
} while (result.restartRequired)
213216

214217
// We attach the action result to cli context so that we can process it in the parse method
215218
cliContext.details.result = result

src/commands/base.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { PluginContext } from "../plugin-context"
1414
import { TaskResults } from "../task-graph"
1515
import { LoggerType } from "../logger/types"
16+
import { ProcessResults } from "../process"
1617

1718
export class ValidationError extends Error { }
1819

@@ -128,6 +129,7 @@ export interface CommandConstructor {
128129

129130
export interface CommandResult<T = any> {
130131
result?: T
132+
restartRequired?: boolean
131133
errors?: GardenError[]
132134
}
133135

@@ -172,19 +174,24 @@ export abstract class Command<T extends Parameters = {}, U extends Parameters =
172174
}
173175

174176
export async function handleTaskResults(
175-
ctx: PluginContext, taskType: string, result: TaskResults,
177+
ctx: PluginContext, taskType: string, results: ProcessResults,
176178
): Promise<CommandResult<TaskResults>> {
177-
const failed = Object.values(result).filter(r => !!r.error).length
179+
const failed = Object.values(results).filter(r => !!r.error).length
178180

179181
if (failed) {
180182
const error = new RuntimeError(`${failed} ${taskType} task(s) failed!`, {
181-
result,
183+
results,
182184
})
183185
return { errors: [error] }
184-
} else {
185-
ctx.log.info("")
186+
}
187+
188+
ctx.log.info("")
189+
if (!results.restartRequired) {
186190
ctx.log.header({ emoji: "heavy_check_mark", command: `Done!` })
187-
return { result }
191+
}
192+
return {
193+
result: results.taskResults,
194+
restartRequired: results.restartRequired,
188195
}
189196
}
190197

src/commands/build.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { BuildTask } from "../tasks/build"
1919
import { TaskResults } from "../task-graph"
2020
import dedent = require("dedent")
21+
import { processModules } from "../process"
2122

2223
export const buildArguments = {
2324
module: new StringParameter({
@@ -59,8 +60,9 @@ export class BuildCommand extends Command<typeof buildArguments, typeof buildOpt
5960

6061
ctx.log.header({ emoji: "hammer", command: "Build" })
6162

62-
const results = await ctx.processModules({
63+
const results = await processModules({
6364
modules,
65+
pluginContext: ctx,
6466
watch: opts.watch,
6567
process: async (module) => {
6668
return [await BuildTask.factory({ ctx, module, force: opts.force })]

src/commands/deploy.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
StringParameter,
1818
} from "./base"
1919
import { TaskResults } from "../task-graph"
20+
import { processServices } from "../process"
2021

2122
export const deployArgs = {
2223
service: new StringParameter({
@@ -75,9 +76,10 @@ export class DeployCommand extends Command<typeof deployArgs, typeof deployOpts>
7576
const force = opts.force
7677
const forceBuild = opts["force-build"]
7778

78-
const results = await ctx.processServices({
79+
const results = await processServices({
7980
services,
8081
watch,
82+
pluginContext: ctx,
8183
process: async (service) => {
8284
return [await DeployTask.factory({ ctx, service, force, forceBuild })]
8385
},

src/commands/dev.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { join } from "path"
1818
import { STATIC_DIR } from "../constants"
1919
import chalk from "chalk"
2020
import moment = require("moment")
21+
import { processModules } from "../process"
2122

2223
const imgcatPath = join(STATIC_DIR, "imgcat")
2324
const bannerPath = join(STATIC_DIR, "garden-banner-1-half.png")
@@ -60,8 +61,9 @@ export class DevCommand extends Command {
6061
return {}
6162
}
6263

63-
await ctx.processModules({
64+
await processModules({
6465
modules,
66+
pluginContext: ctx,
6567
watch: true,
6668
process: async (module) => {
6769
const testTasks: Task[] = await module.getTestTasks({})

src/commands/push.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ export class PushCommand extends Command<typeof pushArgs, typeof pushOpts> {
6565
const names = args.module ? args.module.split(",") : undefined
6666
const modules = await ctx.getModules(names)
6767

68-
const result = await pushModules(ctx, modules, !!opts["force-build"], !!opts["allow-dirty"])
68+
const results = await pushModules(ctx, modules, !!opts["force-build"], !!opts["allow-dirty"])
6969

70-
return handleTaskResults(ctx, "push", result)
70+
return handleTaskResults(ctx, "push", { taskResults: results })
7171
}
7272
}
7373

src/commands/test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
CommandResult,
1717
} from "./base"
1818
import { TaskResults } from "../task-graph"
19+
import { processModules } from "../process"
1920

2021
export const testArgs = {
2122
module: new StringParameter({
@@ -75,8 +76,9 @@ export class TestCommand extends Command<typeof testArgs, typeof testOpts> {
7576
const force = opts.force
7677
const forceBuild = opts["force-build"]
7778

78-
const results = await ctx.processModules({
79+
const results = await processModules({
7980
modules,
81+
pluginContext: ctx,
8082
watch: opts.watch,
8183
process: async (module) => module.getTestTasks({ name, force, forceBuild }),
8284
})

src/plugin-context.ts

Lines changed: 0 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
Garden,
2121
} from "./garden"
2222
import { EntryStyle } from "./logger/types"
23-
import { TaskResults } from "./task-graph"
2423
import {
2524
PrimitiveMap,
2625
validate,
@@ -82,24 +81,13 @@ import {
8281
mapValues,
8382
toPairs,
8483
values,
85-
padEnd,
8684
keyBy,
8785
omit,
88-
flatten,
8986
} from "lodash"
90-
import { Task } from "./types/task"
9187
import {
92-
getNames,
9388
Omit,
94-
registerCleanupFunction,
95-
sleep,
9689
} from "./util"
9790
import { TreeVersion } from "./vcs/base"
98-
import {
99-
autoReloadModules,
100-
computeAutoReloadDependants,
101-
FSWatcher,
102-
} from "./watch"
10391

10492
export type PluginContextGuard = {
10593
readonly [P in keyof (PluginActionParams | ModuleActionParams<any>)]: (...args: any[]) => Promise<any>
@@ -133,18 +121,6 @@ export type WrappedFromGarden = Pick<Garden,
133121
"addTask" |
134122
"processTasks">
135123

136-
export interface ProcessModulesParams {
137-
modules: Module[],
138-
watch: boolean,
139-
process: (module: Module) => Promise<Task[]>,
140-
}
141-
142-
export interface ProcessServicesParams {
143-
services: Service[],
144-
watch: boolean,
145-
process: (service: Service) => Promise<Task[]>,
146-
}
147-
148124
export interface PluginContext extends PluginContextGuard, WrappedFromGarden {
149125
getEnvironmentStatus: (params: {}) => Promise<EnvironmentStatusMap>
150126
configureEnvironment: (params: { force?: boolean }) => Promise<EnvironmentStatusMap>
@@ -189,8 +165,6 @@ export interface PluginContext extends PluginContextGuard, WrappedFromGarden {
189165
getModuleVersion: (module: Module, force?: boolean) => Promise<TreeVersion>
190166
stageBuild: (moduleName: string) => Promise<void>
191167
getStatus: () => Promise<ContextStatus>
192-
processModules: (params: ProcessModulesParams) => Promise<TaskResults>
193-
processServices: (params: ProcessServicesParams) => Promise<TaskResults>
194168
}
195169

196170
export function createPluginContext(garden: Garden): PluginContext {
@@ -524,83 +498,6 @@ export function createPluginContext(garden: Garden): PluginContext {
524498
}
525499
},
526500

527-
processModules: async ({ modules, watch, process }: ProcessModulesParams) => {
528-
// TODO: log errors as they happen, instead of after processing all tasks
529-
const logErrors = (taskResults: TaskResults) => {
530-
for (const result of values(taskResults).filter(r => !!r.error)) {
531-
const divider = padEnd("", 80, "—")
532-
533-
ctx.log.error(`\nFailed ${result.description}. Here is the output:`)
534-
ctx.log.error(divider)
535-
ctx.log.error(result.error + "")
536-
ctx.log.error(divider + "\n")
537-
}
538-
}
539-
540-
for (const module of modules) {
541-
const tasks = await process(module)
542-
await Bluebird.map(tasks, ctx.addTask)
543-
}
544-
545-
const results = await ctx.processTasks()
546-
logErrors(results)
547-
548-
if (!watch) {
549-
return results
550-
}
551-
552-
const modulesToWatch = await autoReloadModules(modules)
553-
const autoReloadDependants = await computeAutoReloadDependants(ctx)
554-
555-
async function handleChanges(module: Module) {
556-
const tasks = await process(module)
557-
await Bluebird.map(tasks, ctx.addTask)
558-
559-
const dependantsForModule = autoReloadDependants[module.name]
560-
if (!dependantsForModule) {
561-
return
562-
}
563-
564-
for (const dependant of dependantsForModule) {
565-
await handleChanges(dependant)
566-
}
567-
}
568-
569-
const watcher = new FSWatcher(ctx)
570-
571-
// TODO: should the prefix here be different or set explicitly per run?
572-
await watcher.watchModules(modulesToWatch, "addTasksForAutoReload/",
573-
async (changedModule) => {
574-
ctx.log.debug({ msg: `Files changed for module ${changedModule.name}` })
575-
await handleChanges(changedModule)
576-
logErrors(await ctx.processTasks())
577-
})
578-
579-
registerCleanupFunction("clearAutoReloadWatches", () => {
580-
watcher.end()
581-
ctx.log.info({ msg: "\nDone!" })
582-
})
583-
584-
while (true) {
585-
await sleep(1000)
586-
}
587-
},
588-
589-
processServices: async ({ services, watch, process }: ProcessServicesParams) => {
590-
const serviceNames = getNames(services)
591-
const modules = Array.from(new Set(services.map(s => s.module)))
592-
593-
return ctx.processModules({
594-
modules,
595-
watch,
596-
process: async (module) => {
597-
const moduleServices = await module.getServices()
598-
const servicesToDeploy = moduleServices.filter(s => serviceNames.includes(s.name))
599-
return flatten(await Bluebird.map(servicesToDeploy, process))
600-
},
601-
})
602-
},
603-
604501
//endregion
605502
}
606503

src/plugins/kubernetes/local.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
isSystemGarden,
4040
} from "./system"
4141
import { readFile } from "fs-extra"
42+
import { processServices } from "../../process"
4243

4344
// TODO: split this into separate plugins to handle Docker for Mac and Minikube
4445

@@ -96,15 +97,16 @@ async function configureLocalEnvironment(
9697

9798
const services = await sysCtx.getServices(provider.config._systemServices)
9899

99-
const results = await sysCtx.processServices({
100+
const results = await processServices({
100101
services,
102+
pluginContext: ctx,
101103
watch: false,
102104
process: async (service) => {
103105
return [await DeployTask.factory({ ctx: sysCtx, service, force, forceBuild: false })]
104106
},
105107
})
106108

107-
const failed = values(results).filter(r => !!r.error).length
109+
const failed = values(results.taskResults).filter(r => !!r.error).length
108110

109111
if (failed) {
110112
throw new PluginError(`local-kubernetes: ${failed} errors occurred when configuring environments`, {

0 commit comments

Comments
 (0)