Skip to content

Commit

Permalink
refactor(core): allow plugins to augment the module graph
Browse files Browse the repository at this point in the history
Providers are now able to augment the stack graph using a
new `augmentGraph` handler, which is called in dependency
order for each provider that specifies it. This allows
providers to add modules (and by extension services, tasks,
tests), and to add dependency relations between existing
modules and services/tasks.

See the new augmentGraph plugin handler for details on usage.

Also made a small refactor to no longer require a build
handler for every module type, and to avoid unnecessary
build tasks when a module doesn't require building.
  • Loading branch information
edvald committed Dec 12, 2019
1 parent 3f9da06 commit b2509e9
Show file tree
Hide file tree
Showing 81 changed files with 1,478 additions and 564 deletions.
46 changes: 34 additions & 12 deletions garden-service/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ import { DeleteServiceTask, deletedServiceStatuses } from "./tasks/delete-servic
import { realpath, writeFile } from "fs-extra"
import { relative, join } from "path"
import { getArtifactKey } from "./util/artifacts"
import { AugmentGraphResult, AugmentGraphParams } from "./types/plugin/provider/augmentGraph"

const maxArtifactLogLines = 5 // max number of artifacts to list in console after task+test runs

Expand Down Expand Up @@ -181,7 +182,7 @@ export class ActionRouter implements TypeGuard {

const handlerParams: PluginActionParams["configureProvider"] = {
...omit(params, ["pluginName"]),
base: this.wrapBase(handler.base),
base: this.wrapBase(handler!.base),
}

const result = (<Function>handler)(handlerParams)
Expand All @@ -191,6 +192,17 @@ export class ActionRouter implements TypeGuard {
return result
}

async augmentGraph(params: RequirePluginName<ActionRouterParams<AugmentGraphParams>>): Promise<AugmentGraphResult> {
const { pluginName } = params

return this.callActionHandler({
actionType: "augmentGraph",
pluginName,
params: omit(params, ["pluginName"]),
defaultHandler: async () => ({ addBuildDependencies: [], addRuntimeDependencies: [], addModules: [] }),
})
}

async getEnvironmentStatus(
params: RequirePluginName<ActionRouterParams<GetEnvironmentStatusParams>> & { ctx?: PluginContext }
): Promise<EnvironmentStatus> {
Expand Down Expand Up @@ -283,7 +295,11 @@ export class ActionRouter implements TypeGuard {
}

async build<T extends Module>(params: ModuleActionRouterParams<BuildModuleParams<T>>): Promise<BuildResult> {
return this.callModuleHandler({ params, actionType: "build" })
return this.callModuleHandler({
params,
actionType: "build",
defaultHandler: async () => ({}),
})
}

async publishModule<T extends Module>(
Expand Down Expand Up @@ -516,7 +532,7 @@ export class ActionRouter implements TypeGuard {
log: LogEntry
serviceNames?: string[]
}): Promise<ServiceStatusMap> {
const graph = await this.garden.getConfigGraph()
const graph = await this.garden.getConfigGraph(log)
const services = await graph.getServices(serviceNames)

const tasks = services.map(
Expand All @@ -540,7 +556,7 @@ export class ActionRouter implements TypeGuard {
forceBuild = false,
log,
}: DeployServicesParams): Promise<ProcessResults> {
const graph = await this.garden.getConfigGraph()
const graph = await this.garden.getConfigGraph(log)
const services = await graph.getServices(serviceNames)

return processServices({
Expand All @@ -566,7 +582,7 @@ export class ActionRouter implements TypeGuard {
* Deletes all services and cleans up the specified environment.
*/
async deleteEnvironment(log: LogEntry) {
const graph = await this.garden.getConfigGraph()
const graph = await this.garden.getConfigGraph(log)

const servicesLog = log.info({ msg: chalk.white("Deleting services..."), status: "active" })

Expand Down Expand Up @@ -678,7 +694,7 @@ export class ActionRouter implements TypeGuard {
})

const handlerParams: PluginActionParams[T] = {
...(await this.commonParams(handler, params.log)),
...(await this.commonParams(handler!, params.log)),
...(<any>params),
}

Expand Down Expand Up @@ -746,7 +762,7 @@ export class ActionRouter implements TypeGuard {
if (runtimeContext && (await getRuntimeTemplateReferences(module)).length > 0) {
log.silly(`Resolving runtime template strings for service '${service.name}'`)
const configContext = await this.garden.getModuleConfigContext(runtimeContext)
const graph = await this.garden.getConfigGraph({ configContext })
const graph = await this.garden.getConfigGraph(log, { configContext })
service = await graph.getService(service.name)
module = service.module

Expand Down Expand Up @@ -803,7 +819,7 @@ export class ActionRouter implements TypeGuard {
if (runtimeContext && (await getRuntimeTemplateReferences(module)).length > 0) {
log.silly(`Resolving runtime template strings for task '${task.name}'`)
const configContext = await this.garden.getModuleConfigContext(runtimeContext)
const graph = await this.garden.getConfigGraph({ configContext })
const graph = await this.garden.getConfigGraph(log, { configContext })
task = await graph.getTask(task.name)
module = task.module

Expand Down Expand Up @@ -971,15 +987,17 @@ export class ActionRouter implements TypeGuard {
/**
* Get the last configured handler for the specified action (and optionally module type).
*/
private async getActionHandler<T extends keyof WrappedPluginActionHandlers>({
async getActionHandler<T extends keyof WrappedPluginActionHandlers>({
actionType,
pluginName,
defaultHandler,
throwIfMissing = true,
}: {
actionType: T
pluginName: string
defaultHandler?: PluginActionHandlers[T]
}): Promise<WrappedPluginActionHandlers[T]> {
throwIfMissing?: boolean
}): Promise<WrappedPluginActionHandlers[T] | null> {
const handlers = Object.values(await this.getActionHandlers(actionType, pluginName))

// Since we only allow retrieving by plugin name, the length is always either 0 or 1
Expand All @@ -995,6 +1013,10 @@ export class ActionRouter implements TypeGuard {
)
}

if (!throwIfMissing) {
return null
}

const errorDetails = {
requestedHandlerType: actionType,
environment: this.garden.environmentName,
Expand All @@ -1013,9 +1035,9 @@ export class ActionRouter implements TypeGuard {
}

/**
* Get the last configured handler for the specified action.
* Get the configured handler for the specified action.
*/
private async getModuleActionHandler<T extends keyof ModuleAndRuntimeActionHandlers>({
async getModuleActionHandler<T extends keyof ModuleAndRuntimeActionHandlers>({
actionType,
moduleType,
pluginName,
Expand Down
14 changes: 8 additions & 6 deletions garden-service/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ import {
StringsParameter,
PrepareParams,
} from "./base"
import { BuildTask } from "../tasks/build"
import { getBuildTasks } from "../tasks/build"
import { TaskResults } from "../task-graph"
import dedent = require("dedent")
import { processModules } from "../process"
import { printHeader } from "../logger/util"
import { startServer, GardenServer } from "../server/server"
import { flatten } from "lodash"

const buildArguments = {
modules: new StringsParameter({
Expand Down Expand Up @@ -81,24 +82,25 @@ export class BuildCommand extends Command<Args, Opts> {

await garden.clearBuilds()

const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
const modules = await graph.getModules(args.modules)
const moduleNames = modules.map((m) => m.name)

const results = await processModules({
garden,
graph: await garden.getConfigGraph(),
graph: await garden.getConfigGraph(log),
log,
footerLog,
modules,
watch: opts.watch,
handler: async (_, module) => [new BuildTask({ garden, log, module, force: opts.force })],
handler: async (_, module) => getBuildTasks({ garden, log, module, force: opts.force }),
changeHandler: async (_, module) => {
const dependantModules = (await graph.getDependants("build", module.name, true)).build
return [module]
const tasks = [module]
.concat(dependantModules)
.filter((m) => moduleNames.includes(m.name))
.map((m) => new BuildTask({ garden, log, module: m, force: true }))
.map((m) => getBuildTasks({ garden, log, module: m, force: true }))
return flatten(await Promise.all(tasks))
},
})

Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class CallCommand extends Command<Args> {
let [serviceName, path] = splitFirst(args.serviceAndPath, "/")

// TODO: better error when service doesn't exist
const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
const service = await graph.getService(serviceName)
// No need for full context, since we're just checking if the service is running.
const runtimeContext = emptyRuntimeContext
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class DeleteServiceCommand extends Command {
`

async action({ garden, log, headerLog, args }: CommandParams<DeleteServiceArgs>): Promise<CommandResult> {
const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
const services = await graph.getServices(args.services)

if (services.length === 0) {
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export class DeployCommand extends Command<Args, Opts> {
this.server.setGarden(garden)
}

const initGraph = await garden.getConfigGraph()
const initGraph = await garden.getConfigGraph(log)
const services = await initGraph.getServices(args.services)

if (services.length === 0) {
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export class DevCommand extends Command<Args, Opts> {
async action({ garden, log, footerLog, opts }: CommandParams<Args, Opts>): Promise<CommandResult> {
this.server.setGarden(garden)

const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
const modules = await graph.getModules()

if (modules.length === 0) {
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class ExecCommand extends Command<Args> {
"runner"
)

const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
const service = await graph.getService(serviceName)
const actions = await garden.getActionRouter()
const result = await actions.execInService({
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/get/get-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class GetConfigCommand extends Command {
help = "Outputs the fully resolved configuration for this project and environment."

async action({ garden, log }: CommandParams): Promise<CommandResult<ConfigDump>> {
const config = await garden.dumpConfig()
const config = await garden.dumpConfig(log)

// TODO: do a nicer print of this by default
log.info({ data: config })
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/get/get-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class GetGraphCommand extends Command {
help = "Outputs the dependency relationships specified in this project's garden.yml files."

async action({ garden, log }: CommandParams): Promise<CommandResult<GraphOutput>> {
const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
const renderedGraph = graph.render()
const output: GraphOutput = {
nodes: renderedGraph.nodes,
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/get/get-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class GetStatusCommand extends Command {
let result: AllEnvironmentStatus

if (opts.output) {
const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
result = await Bluebird.props({
...status,
tests: getTestStatuses(garden, graph, log),
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/get/get-task-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class GetTaskResultCommand extends Command<Args> {
}: CommandParams<Args>): Promise<CommandResult<GetTaskResultCommandResult>> {
const taskName = args.name

const graph: ConfigGraph = await garden.getConfigGraph()
const graph: ConfigGraph = await garden.getConfigGraph(log)
const task = await graph.getTask(taskName)

const actions = await garden.getActionRouter()
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/get/get-tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class GetTasksCommand extends Command<Args> {
}

async action({ args, garden, log }: CommandParams<Args>): Promise<CommandResult> {
const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
const tasks = await graph.getTasks(args.tasks)
const taskModuleNames = uniq(tasks.map((t) => t.module.name))
const modules = sortBy(await graph.getModules(taskModuleNames), (m) => m.name)
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/get/get-test-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class GetTestResultCommand extends Command<Args> {
"heavy_check_mark"
)

const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
const actions = await garden.getActionRouter()

const module = await graph.getModule(moduleName)
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/link/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class LinkModuleCommand extends Command<Args> {
const sourceType = "module"

const { module: moduleName, path } = args
const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
const moduleToLink = await graph.getModule(moduleName)

const isRemote = [moduleToLink].filter(hasRemoteSource)[0]
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class LogsCommand extends Command<Args, Opts> {

async action({ garden, log, args, opts }: CommandParams<Args, Opts>): Promise<CommandResult<ServiceLogEntry[]>> {
const { follow, tail } = opts
const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
const services = await graph.getServices(args.services)

const result: ServiceLogEntry[] = []
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class PublishCommand extends Command<Args, Opts> {
}: CommandParams<Args, Opts>): Promise<CommandResult<TaskResults>> {
printHeader(headerLog, "Publish modules", "rocket")

const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
const modules = await graph.getModules(args.modules)

const results = await publishModules(garden, log, modules, !!opts["force-build"], !!opts["allow-dirty"])
Expand Down
8 changes: 4 additions & 4 deletions garden-service/src/commands/run/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { RunResult } from "../../types/plugin/base"
import { BooleanParameter, Command, CommandParams, StringParameter, CommandResult, StringsParameter } from "../base"
import { printRuntimeContext } from "./run"
import { printHeader } from "../../logger/util"
import { BuildTask } from "../../tasks/build"
import { getBuildTasks } from "../../tasks/build"
import { dedent, deline } from "../../util/string"
import { prepareRuntimeContext } from "../../runtime-context"

Expand Down Expand Up @@ -74,7 +74,7 @@ export class RunModuleCommand extends Command<Args, Opts> {
async action({ garden, log, headerLog, args, opts }: CommandParams<Args, Opts>): Promise<CommandResult<RunResult>> {
const moduleName = args.module

const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
const module = await graph.getModule(moduleName)

const msg = args.arguments
Expand All @@ -85,13 +85,13 @@ export class RunModuleCommand extends Command<Args, Opts> {

const actions = await garden.getActionRouter()

const buildTask = new BuildTask({
const buildTasks = await getBuildTasks({
garden,
log,
module,
force: opts["force-build"],
})
await garden.processTasks([buildTask])
await garden.processTasks(buildTasks)

const dependencies = await graph.getDependencies("build", module.name, false)

Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/run/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class RunServiceCommand extends Command<Args, Opts> {

async action({ garden, log, headerLog, args, opts }: CommandParams<Args, Opts>): Promise<CommandResult<RunResult>> {
const serviceName = args.service
const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
const service = await graph.getService(serviceName)
const module = service.module

Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/run/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class RunTaskCommand extends Command<Args, Opts> {
args,
opts,
}: CommandParams<Args, Opts>): Promise<CommandResult<TaskResult | null>> {
const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
const task = await graph.getTask(args.task)

const msg = `Running task ${chalk.white(task.name)}`
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/run/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class RunTestCommand extends Command<Args, Opts> {
const moduleName = args.module
const testName = args.test

const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)
const module = await graph.getModule(moduleName)

const testConfig = findByName(module.testConfigs, testName)
Expand Down
3 changes: 2 additions & 1 deletion garden-service/src/commands/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export class ScanCommand extends Command {
help = "Scans your project and outputs an overview of all modules."

async action({ garden, log }: CommandParams): Promise<CommandResult<DeepPrimitiveMap>> {
const modules = (await garden.resolveModuleConfigs()).map((m) => {
const graph = await garden.getConfigGraph(log)
const modules = (await graph.getModules()).map((m) => {
return omit(m, ["_ConfigType", "cacheContext", "serviceNames", "taskNames"])
})

Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export class TestCommand extends Command<Args, Opts> {
this.server.setGarden(garden)
}

const graph = await garden.getConfigGraph()
const graph = await garden.getConfigGraph(log)

let modules: Module[]
if (args.modules) {
Expand Down
Loading

0 comments on commit b2509e9

Please sign in to comment.