Skip to content

Commit

Permalink
feat: template variables can now access provider name and config
Browse files Browse the repository at this point in the history
This needed some changes to how we resolve configs. Basically it's
now done ahead of time in the plugin context methods, before handing
over Module and Service instances to action handlers.
  • Loading branch information
edvald committed May 23, 2018
1 parent b97528f commit 51e2f33
Show file tree
Hide file tree
Showing 14 changed files with 82 additions and 52 deletions.
3 changes: 1 addition & 2 deletions src/build-dir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,8 @@ export class BuildDir {
async syncDependencyProducts<T extends Module>(ctx: PluginContext, module: T) {
await this.syncFromSrc(module)
const buildPath = await this.buildPath(module)
const config = await module.getConfig()

await bluebirdMap(config.build.dependencies || [], async (depConfig) => {
await bluebirdMap(module.config.build.dependencies || [], async (depConfig) => {
if (!depConfig.copy) {
return []
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class CallCommand extends Command<typeof callArgs> {
path = "/" + path

// TODO: better error when service doesn't exist
const service = await (await ctx.getService(serviceName)).resolveConfig()
const service = await ctx.getService(serviceName)
const status = await ctx.getServiceStatus(service)

if (status.state !== "ready") {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/run/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class RunTestCommand extends Command<typeof runArgs, typeof runOpts> {
const moduleName = args.module
const testName = args.test
const module = await ctx.getModule(moduleName)
const config = await module.getConfig()
const config = module.config

const testSpec = findByName(config.test, testName)

Expand Down
2 changes: 1 addition & 1 deletion src/commands/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class ScanCommand extends Command {
const modules = await ctx.getModules()

const output = await Bluebird.map(modules, async (m) => {
const config = await m.getConfig()
const config = await m.config
return {
name: m.name,
type: m.type,
Expand Down
2 changes: 1 addition & 1 deletion src/garden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ export class Garden {
@param force - add the module again, even if it's already registered
*/
async addModule(module: Module, force = false) {
const config = await module.getConfig()
const config = module.config

if (!force && this.modules[config.name]) {
const pathA = relative(this.projectRoot, this.modules[config.name].path)
Expand Down
61 changes: 41 additions & 20 deletions src/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ import {
TestModuleParams,
RunModuleParams,
RunServiceParams,
Provider,
} from "./types/plugin"
import {
Service,
RuntimeContext,
Service,
ServiceStatus,
} from "./types/service"
import {
Expand Down Expand Up @@ -113,9 +114,7 @@ export interface PluginContext extends PluginContextGuard, WrappedFromGarden {
configureEnvironment: () => Promise<EnvironmentStatusMap>
destroyEnvironment: () => Promise<EnvironmentStatusMap>
getServiceStatus: <T extends Module>(service: Service<T>) => Promise<ServiceStatus>
deployService: <T extends Module>(
service: Service<T>, runtimeContext?: RuntimeContext, logEntry?: LogEntry,
) => Promise<ServiceStatus>
deployService: <T extends Module>(service: Service<T>, logEntry?: LogEntry) => Promise<ServiceStatus>
getServiceOutputs: <T extends Module>(service: Service<T>) => Promise<PrimitiveMap>
execInService: <T extends Module>(service: Service<T>, command: string[]) => Promise<ExecInServiceResult>
getServiceLogs: <T extends Module>(
Expand Down Expand Up @@ -147,20 +146,34 @@ export function createPluginContext(garden: Garden): PluginContext {
const projectConfig = { ...garden.config }
const providerConfigs = keyBy(projectConfig.providers, "name")

function getProvider(handler): Provider {
return {
name: handler["pluginName"],
config: providerConfigs[handler["pluginName"]],
}
}

// TODO: find a nicer way to do this (like a type-safe wrapper function)
function commonParams(handler): PluginActionParamsBase {
const providerName = handler["pluginName"]
const providerConfig = providerConfigs[providerName]
const env = garden.getEnvironment()

return {
ctx,
env,
provider: {
name: providerName,
config: providerConfig,
},
env: garden.getEnvironment(),
provider: getProvider(handler),
}
}

async function resolveModule<T extends Module>(handler, module: T): Promise<T> {
const provider = <any>getProvider(handler)
return module.resolveConfig({ provider })
}

async function resolveService<T extends Service>(handler, service: T, runtimeContext?: RuntimeContext): Promise<T> {
const provider = <any>getProvider(handler)
service.module = await resolveModule(handler, service.module)
if (!runtimeContext) {
runtimeContext = await service.prepareRuntimeContext()
}
return service.resolveConfig({ provider, ...runtimeContext })
}

const ctx: PluginContext = {
Expand Down Expand Up @@ -195,13 +208,15 @@ export function createPluginContext(garden: Garden): PluginContext {
getModuleBuildStatus: async <T extends Module>(module: T, logEntry?: LogEntry) => {
const defaultHandler = garden.getModuleActionHandler("getModuleBuildStatus", "generic")
const handler = garden.getModuleActionHandler("getModuleBuildStatus", module.type, defaultHandler)
module = await resolveModule(handler, module)
return handler({ ...commonParams(handler), module, logEntry })
},

buildModule: async <T extends Module>(module: T, buildContext: PrimitiveMap, logEntry?: LogEntry) => {
await ctx.stageBuild(module)
const defaultHandler = garden.getModuleActionHandler("buildModule", "generic")
const handler = garden.getModuleActionHandler("buildModule", module.type, defaultHandler)
module = await resolveModule(handler, module)
await ctx.stageBuild(module)
return handler({ ...commonParams(handler), module, buildContext, logEntry })
},

Expand All @@ -211,11 +226,13 @@ export function createPluginContext(garden: Garden): PluginContext {

pushModule: async <T extends Module>(module: T, logEntry?: LogEntry) => {
const handler = garden.getModuleActionHandler("pushModule", module.type, dummyPushHandler)
module = await resolveModule(handler, module)
return handler({ ...commonParams(handler), module, logEntry })
},

runModule: async <T extends Module>(params: OmitBase<RunModuleParams<T>>) => {
const handler = garden.getModuleActionHandler("runModule", params.module.type)
params.module = await resolveModule(handler, params.module)
return handler({ ...commonParams(handler), ...params })
},

Expand All @@ -224,6 +241,7 @@ export function createPluginContext(garden: Garden): PluginContext {

const defaultHandler = garden.getModuleActionHandler("testModule", "generic")
const handler = garden.getModuleActionHandler("testModule", module.type, defaultHandler)
params.module = await resolveModule(handler, params.module)

return handler({ ...commonParams(handler), ...params })
},
Expand All @@ -232,6 +250,7 @@ export function createPluginContext(garden: Garden): PluginContext {
module: T, testName: string, version: TreeVersion, logEntry?: LogEntry,
) => {
const handler = garden.getModuleActionHandler("getTestResult", module.type, async () => null)
module = await resolveModule(handler, module)
return handler({ ...commonParams(handler), module, testName, version, logEntry })
},

Expand Down Expand Up @@ -273,17 +292,15 @@ export function createPluginContext(garden: Garden): PluginContext {

getServiceStatus: async <T extends Module>(service: Service<T>) => {
const handler = garden.getModuleActionHandler("getServiceStatus", service.module.type)
service = await resolveService(handler, service)
return handler({ ...commonParams(handler), service })
},

deployService: async <T extends Module>(
service: Service<T>, runtimeContext?: RuntimeContext, logEntry?: LogEntry,
) => {
deployService: async <T extends Module>(service: Service<T>, logEntry?: LogEntry) => {
const handler = garden.getModuleActionHandler("deployService", service.module.type)

if (!runtimeContext) {
runtimeContext = { envVars: {}, dependencies: {} }
}
const runtimeContext = await service.prepareRuntimeContext()
service = await resolveService(handler, service, runtimeContext)

return handler({ ...commonParams(handler), service, runtimeContext, logEntry })
},
Expand All @@ -296,21 +313,25 @@ export function createPluginContext(garden: Garden): PluginContext {
} catch (err) {
return {}
}
service = await resolveService(handler, service)
return handler({ ...commonParams(handler), service })
},

execInService: async <T extends Module>(service: Service<T>, command: string[]) => {
const handler = garden.getModuleActionHandler("execInService", service.module.type)
service = await resolveService(handler, service)
return handler({ ...commonParams(handler), service, command })
},

getServiceLogs: async <T extends Module>(service: Service<T>, stream: Stream<ServiceLogEntry>, tail?: boolean) => {
const handler = garden.getModuleActionHandler("getServiceLogs", service.module.type, dummyLogStreamer)
service = await resolveService(handler, service)
return handler({ ...commonParams(handler), service, stream, tail })
},

runService: async <T extends Module>(params: OmitBase<RunServiceParams<T>>) => {
const handler = garden.getModuleActionHandler("runService", params.service.module.type)
params.service = await resolveService(handler, params.service)
return handler({ ...commonParams(handler), ...params })
},

Expand Down
4 changes: 2 additions & 2 deletions src/plugins/generic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ export const genericPlugin = {
async getModuleBuildStatus({ module }: GetModuleBuildStatusParams): Promise<BuildStatus> {
// Each module handler should keep track of this for now.
// Defaults to return false if a build command is specified.
return { ready: !(await module.getConfig()).build.command }
return { ready: !module.config.build.command }
},

async buildModule({ module, buildContext }: BuildModuleParams): Promise<BuildResult> {
// By default we run the specified build command in the module root, if any.
// TODO: Keep track of which version has been built (needs local data store/cache).
const config: ModuleConfig = await module.getConfig()
const config: ModuleConfig = module.config

const contextEnv = mapValues(buildContext, v => v + "")

Expand Down
2 changes: 1 addition & 1 deletion src/plugins/local/local-google-cloud-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ async function getEmulatorModule(ctx: PluginContext, module: GoogleCloudFunction
}
})

const config = await module.getConfig()
const config = module.config
const version = await module.getVersion()

return new ContainerModule(ctx, <ContainerModuleConfig>{
Expand Down
9 changes: 2 additions & 7 deletions src/tasks/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,9 @@ export class DeployTask extends Task {
entryStyle: EntryStyle.activity,
})

// we resolve the config again because context may have changed after dependencies are deployed
const dependencies = await this.service.getDependencies()
const runtimeContext = await this.service.module.prepareRuntimeContext(dependencies)
const service = await this.service.resolveConfig(runtimeContext)

// TODO: get version from build task results
const { versionString } = await this.service.module.getVersion()
const status = await this.ctx.getServiceStatus(service)
const status = await this.ctx.getServiceStatus(this.service)

if (
!this.force &&
Expand All @@ -104,7 +99,7 @@ export class DeployTask extends Task {

entry.setState({ section: this.service.name, msg: "Deploying" })

const result = await this.ctx.deployService(service, runtimeContext, entry)
const result = await this.ctx.deployService(this.service, entry)

entry.setSuccess({ msg: chalk.green(`Ready`), append: true })

Expand Down
4 changes: 2 additions & 2 deletions src/tasks/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class PushTask extends Task {
}

async getDependencies() {
if (!(await this.module.getConfig()).allowPush) {
if (!this.module.config.allowPush) {
return []
}
return [await BuildTask.factory({ ctx: this.ctx, module: this.module, force: this.forceBuild })]
Expand All @@ -56,7 +56,7 @@ export class PushTask extends Task {
}

async process(): Promise<PushResult> {
if (!(await this.module.getConfig()).allowPush) {
if (!this.module.config.allowPush) {
this.ctx.log.info({
section: this.module.name,
msg: "Push disabled",
Expand Down
17 changes: 9 additions & 8 deletions src/types/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ import {
Service,
ServiceConfig,
} from "./service"
import { resolveTemplateStrings, TemplateStringContext } from "../template-string"
import { Memoize } from "typescript-memoize"
import {
resolveTemplateStrings,
TemplateStringContext,
} from "../template-string"
import { TreeVersion } from "../vcs/base"
import { join } from "path"

Expand Down Expand Up @@ -137,15 +139,14 @@ export class Module<T extends ModuleConfig = ModuleConfig> {

_ConfigType: T

constructor(private ctx: PluginContext, private config: T) {
constructor(private ctx: PluginContext, public config: T) {
this.name = config.name
this.type = config.type
this.path = config.path
this.services = config.services
}

@Memoize()
async getConfig(context?: TemplateStringContext): Promise<ModuleConfig> {
async resolveConfig(context?: TemplateStringContext) {
// TODO: allow referencing other module configs (non-trivial, need to save for later)
const templateContext = await this.ctx.getTemplateContext(context)
const config = <T>extend({}, this.config)
Expand All @@ -154,7 +155,8 @@ export class Module<T extends ModuleConfig = ModuleConfig> {
config.test = await Bluebird.map(config.test, t => resolveTemplateStrings(t, templateContext))
config.variables = await resolveTemplateStrings(config.variables, templateContext)

return config
const cls = Object.getPrototypeOf(this).constructor
return new cls(this.ctx, config)
}

updateConfig(key: string, value: any) {
Expand Down Expand Up @@ -250,9 +252,8 @@ export class Module<T extends ModuleConfig = ModuleConfig> {
{ group, force = false, forceBuild = false }: { group?: string, force?: boolean, forceBuild?: boolean },
) {
const tasks: Promise<TestTask>[] = []
const config = await this.getConfig()

for (const test of config.test) {
for (const test of this.config.test) {
if (group && test.name !== group) {
continue
}
Expand Down
7 changes: 6 additions & 1 deletion src/types/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class Service<M extends Module = Module> {
/**
* Resolves all template strings in the service and returns a new Service instance with the resolved config.
*/
async resolveConfig(context?: TemplateStringContext, opts?: TemplateOpts): Promise<Service<M>> {
async resolveConfig(context?: TemplateStringContext, opts?: TemplateOpts) {
if (!context) {
const dependencies = await this.getDependencies()
const runtimeContext = await this.module.prepareRuntimeContext(dependencies)
Expand All @@ -106,4 +106,9 @@ export class Service<M extends Module = Module> {
const cls = Object.getPrototypeOf(this).constructor
return new cls(this.ctx, this.module, this.name, resolved)
}

async prepareRuntimeContext(extraEnvVars?: PrimitiveMap) {
const dependencies = await this.getDependencies()
return this.module.prepareRuntimeContext(dependencies, extraEnvVars)
}
}
Loading

0 comments on commit 51e2f33

Please sign in to comment.