Skip to content

Commit

Permalink
refactor(ctx): allow specifying plugin name when calling plugin actions
Browse files Browse the repository at this point in the history
  • Loading branch information
edvald committed Aug 17, 2018
1 parent e72d5d0 commit dec8e35
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 72 deletions.
89 changes: 62 additions & 27 deletions garden-cli/src/garden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ import {
getLinkedSources,
ExternalSourceType,
} from "./util/ext-source-util"
import { pickBy } from "lodash"

export interface ModuleMap<T extends Module> {
[key: string]: T
Expand Down Expand Up @@ -804,70 +805,104 @@ export class Garden {
/**
* Get a handler for the specified action.
*/
public getActionHandlers<T extends keyof PluginActions>(actionType: T): ActionHandlerMap<T> {
return pick(this.actionHandlers[actionType], this.getEnvPlugins())
public getActionHandlers<T extends keyof PluginActions>(actionType: T, pluginName?: string): ActionHandlerMap<T> {
return this.filterActionHandlers(this.actionHandlers[actionType], pluginName)
}

/**
* Get a handler for the specified module action.
*/
public getModuleActionHandlers<T extends keyof ModuleActions<any>>(
actionType: T, moduleType: string,
actionType: T, moduleType: string, pluginName?: string,
): ModuleActionHandlerMap<T> {
return pick((this.moduleActionHandlers[actionType] || {})[moduleType], this.getEnvPlugins())
return this.filterActionHandlers((this.moduleActionHandlers[actionType] || {})[moduleType], pluginName)
}

private filterActionHandlers(handlers, pluginName?: string) {
const loadedPlugins = this.getEnvPlugins()

if (!!pluginName && !this.loadedPlugins[pluginName]) {
throw new ConfigurationError(
`Plugin ${pluginName} has not been loaded. Are you missing a provider configuration?`,
{
loadedPlugins,
pluginName,
},
)
}

return pickBy(handlers, (handler, name) => (
loadedPlugins.includes(name)
&& (!pluginName || handler["pluginName"] === pluginName)
))
}

/**
* Get the last configured handler for the specified action (and optionally module type).
*/
public getActionHandler<T extends keyof PluginActions>(
type: T, defaultHandler?: PluginActions[T],
type: T, pluginName?: string, defaultHandler?: PluginActions[T],
): PluginActions[T] {

const handlers = values(this.getActionHandlers(type))
const handlers = values(this.getActionHandlers(type, pluginName))

if (handlers.length) {
return handlers[handlers.length - 1]
} else if (defaultHandler) {
return defaultHandler
}

// TODO: Make these error messages nicer
throw new ParameterError(
`No '${type}' handler configured in environment '${this.environment}'. ` +
`Are you missing a provider configuration?`,
{
requestedHandlerType: type,
environment: this.environment,
},
)
const errorDetails = {
requestedHandlerType: type,
environment: this.environment,
pluginName,
}

if (pluginName) {
throw new PluginError(`Plugin '${pluginName}' does not have a '${type}' handler.`, errorDetails)
} else {
throw new ParameterError(
`No '${type}' handler configured in environment '${this.environment}'. ` +
`Are you missing a provider configuration?`,
errorDetails,
)
}
}

/**
* Get the last configured handler for the specified action.
*/
public getModuleActionHandler<T extends keyof ModuleActions>(
type: T, moduleType: string, defaultHandler?: ModuleActions<any>[T],
type: T, moduleType: string, pluginName?: string, defaultHandler?: ModuleActions<any>[T],
): ModuleActions<any>[T] {

const handlers = values(this.getModuleActionHandlers(type, moduleType))
const handlers = values(this.getModuleActionHandlers(type, moduleType, pluginName))

if (handlers.length) {
return handlers[handlers.length - 1]
} else if (defaultHandler) {
return defaultHandler
}

// TODO: Make these error messages nicer
throw new ParameterError(
`No '${type}' handler configured for module type '${moduleType}' in environment '${this.environment}'. ` +
`Are you missing a provider configuration?`,
{
requestedHandlerType: type,
requestedModuleType: moduleType,
environment: this.environment,
},
)
const errorDetails = {
requestedHandlerType: type,
requestedModuleType: moduleType,
environment: this.environment,
pluginName,
}

if (pluginName) {
throw new PluginError(
`Plugin '${pluginName}' does not have a '${type}' handler for module type '${moduleType}'.`,
errorDetails,
)
} else {
throw new ParameterError(
`No '${type}' handler configured for module type '${moduleType}' in environment '${this.environment}'. ` +
`Are you missing a provider configuration?`,
errorDetails,
)
}
}

/**
Expand Down
82 changes: 45 additions & 37 deletions garden-cli/src/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ import {
ServiceActionParams,
SetConfigParams,
TestModuleParams,
GetLoginStatusParams,
LoginParams,
LogoutParams,
GetEnvironmentStatusParams,
ConfigureEnvironmentParams,
DestroyEnvironmentParams,
} from "./types/plugin/params"
import {
Service,
Expand Down Expand Up @@ -92,12 +98,13 @@ export interface ContextStatus {
services: { [name: string]: ServiceStatus }
}

export type PluginContextParams<T extends PluginActionParamsBase> = Omit<T, keyof PluginActionContextParams>
export type PluginContextParams<T extends PluginActionParamsBase> =
Omit<T, keyof PluginActionContextParams> & { pluginName?: string }
export type PluginContextModuleParams<T extends PluginModuleActionParamsBase> =
Omit<T, "module" | keyof PluginActionContextParams> & { moduleName: string }
Omit<T, "module" | keyof PluginActionContextParams> & { moduleName: string, pluginName?: string }
export type PluginContextServiceParams<T extends PluginServiceActionParamsBase> =
Omit<T, "module" | "service" | "runtimeContext" | keyof PluginActionContextParams>
& { serviceName: string, runtimeContext?: RuntimeContext }
& { serviceName: string, runtimeContext?: RuntimeContext, pluginName?: string }

export type WrappedFromGarden = Pick<Garden,
"projectName" |
Expand All @@ -120,15 +127,15 @@ export type WrappedFromGarden = Pick<Garden,
export interface PluginContext extends PluginContextGuard, WrappedFromGarden {
providers: { [name: string]: Provider }

getEnvironmentStatus: (params: {}) => Promise<EnvironmentStatusMap>
configureEnvironment: (params: { force?: boolean }) => Promise<EnvironmentStatusMap>
destroyEnvironment: (params: {}) => Promise<EnvironmentStatusMap>
getEnvironmentStatus: (params: PluginContextParams<GetEnvironmentStatusParams>) => Promise<EnvironmentStatusMap>
configureEnvironment: (params: { force?: boolean, pluginName?: string }) => Promise<EnvironmentStatusMap>
destroyEnvironment: (params: PluginContextParams<DestroyEnvironmentParams>) => Promise<EnvironmentStatusMap>
getConfig: (params: PluginContextParams<GetConfigParams>) => Promise<GetConfigResult>
setConfig: (params: PluginContextParams<SetConfigParams>) => Promise<SetConfigResult>
deleteConfig: (params: PluginContextParams<DeleteConfigParams>) => Promise<DeleteConfigResult>
getLoginStatus: (params: {}) => Promise<LoginStatusMap>
login: (params: {}) => Promise<LoginStatusMap>
logout: (params: {}) => Promise<LoginStatusMap>
getLoginStatus: (params: PluginContextParams<GetLoginStatusParams>) => Promise<LoginStatusMap>
login: (params: PluginContextParams<LoginParams>) => Promise<LoginStatusMap>
logout: (params: PluginContextParams<LogoutParams>) => Promise<LoginStatusMap>

getModuleBuildStatus: <T extends Module>(params: PluginContextModuleParams<GetModuleBuildStatusParams<T>>)
=> Promise<BuildStatus>
Expand Down Expand Up @@ -193,10 +200,10 @@ export function createPluginContext(garden: Garden): PluginContext {
}

async function getModuleAndHandler<T extends (keyof ModuleActions | keyof ServiceActions)>(
moduleName: string, actionType: T, defaultHandler?: (ModuleActions & ServiceActions)[T],
moduleName: string, actionType: T, pluginName?: string, defaultHandler?: (ModuleActions & ServiceActions)[T],
): Promise<{ handler: (ModuleActions & ServiceActions)[T], module: Module }> {
const module = await garden.getModule(moduleName)
const handler = garden.getModuleActionHandler(actionType, module.type, defaultHandler)
const handler = garden.getModuleActionHandler(actionType, module.type, pluginName, defaultHandler)
const provider = getProvider(handler)

return {
Expand All @@ -210,7 +217,9 @@ export function createPluginContext(garden: Garden): PluginContext {
actionType: T,
defaultHandler?: ModuleActions[T],
): Promise<ModuleActionOutputs[T]> {
const { module, handler } = await getModuleAndHandler(params.moduleName, actionType, defaultHandler)
const { module, handler } = await getModuleAndHandler(
params.moduleName, actionType, params.pluginName, defaultHandler,
)
const handlerParams: ModuleActionParams[T] = {
...commonParams(handler),
...<object>omit(params, ["moduleName"]),
Expand All @@ -225,7 +234,9 @@ export function createPluginContext(garden: Garden): PluginContext {
): Promise<ServiceActionOutputs[T]> {
const service = await garden.getService(params.serviceName)

const { module, handler } = await getModuleAndHandler(service.module.name, actionType, defaultHandler)
const { module, handler } = await getModuleAndHandler(
service.module.name, actionType, params.pluginName, defaultHandler,
)
service.module = module

// TODO: figure out why this doesn't compile without the casts
Expand Down Expand Up @@ -268,13 +279,13 @@ export function createPluginContext(garden: Garden): PluginContext {
//region Environment Actions
//===========================================================================

getEnvironmentStatus: async () => {
const handlers = garden.getActionHandlers("getEnvironmentStatus")
getEnvironmentStatus: async ({ pluginName }: PluginContextParams<GetEnvironmentStatusParams>) => {
const handlers = garden.getActionHandlers("getEnvironmentStatus", pluginName)
return Bluebird.props(mapValues(handlers, h => h({ ...commonParams(h) })))
},

configureEnvironment: async ({ force = false }: { force?: boolean }) => {
const handlers = garden.getActionHandlers("configureEnvironment")
configureEnvironment: async ({ force = false, pluginName }: { force?: boolean, pluginName?: string }) => {
const handlers = garden.getActionHandlers("configureEnvironment", pluginName)

const statuses = await ctx.getEnvironmentStatus({})

Expand All @@ -298,44 +309,44 @@ export function createPluginContext(garden: Garden): PluginContext {
return ctx.getEnvironmentStatus({})
},

destroyEnvironment: async () => {
const handlers = garden.getActionHandlers("destroyEnvironment")
destroyEnvironment: async ({ pluginName }: PluginContextParams<DestroyEnvironmentParams>) => {
const handlers = garden.getActionHandlers("destroyEnvironment", pluginName)
await Bluebird.each(values(handlers), h => h({ ...commonParams(h) }))
return ctx.getEnvironmentStatus({})
},

getConfig: async ({ key }: PluginContextParams<GetConfigParams>) => {
getConfig: async ({ key, pluginName }: PluginContextParams<GetConfigParams>) => {
garden.validateConfigKey(key)
// TODO: allow specifying which provider to use for configs
const handler = garden.getActionHandler("getConfig")
const handler = garden.getActionHandler("getConfig", pluginName)
return handler({ ...commonParams(handler), key })
},

setConfig: async ({ key, value }: PluginContextParams<SetConfigParams>) => {
setConfig: async ({ key, value, pluginName }: PluginContextParams<SetConfigParams>) => {
garden.validateConfigKey(key)
const handler = garden.getActionHandler("setConfig")
const handler = garden.getActionHandler("setConfig", pluginName)
return handler({ ...commonParams(handler), key, value })
},

deleteConfig: async ({ key }: PluginContextParams<DeleteConfigParams>) => {
deleteConfig: async ({ key, pluginName }: PluginContextParams<DeleteConfigParams>) => {
garden.validateConfigKey(key)
const handler = garden.getActionHandler("deleteConfig")
const handler = garden.getActionHandler("deleteConfig", pluginName)
return handler({ ...commonParams(handler), key })
},

getLoginStatus: async () => {
const handlers = garden.getActionHandlers("getLoginStatus")
getLoginStatus: async ({ pluginName }: PluginContextParams<GetLoginStatusParams>) => {
const handlers = garden.getActionHandlers("getLoginStatus", pluginName)
return Bluebird.props(mapValues(handlers, h => h({ ...commonParams(h) })))
},

login: async () => {
const handlers = garden.getActionHandlers("login")
login: async ({ pluginName }: PluginContextParams<LoginParams>) => {
const handlers = garden.getActionHandlers("login", pluginName)
await Bluebird.each(values(handlers), h => h({ ...commonParams(h) }))
return ctx.getLoginStatus({})
},

logout: async () => {
const handlers = garden.getActionHandlers("logout")
logout: async ({ pluginName }: PluginContextParams<LogoutParams>) => {
const handlers = garden.getActionHandlers("logout", pluginName)
await Bluebird.each(values(handlers), h => h({ ...commonParams(h) }))
return ctx.getLoginStatus({})
},
Expand All @@ -349,13 +360,11 @@ export function createPluginContext(garden: Garden): PluginContext {
getModuleBuildStatus: async <T extends Module>(
params: PluginContextModuleParams<GetModuleBuildStatusParams<T>>,
) => {
const defaultHandler = garden.getModuleActionHandler("getModuleBuildStatus", "generic")
return callModuleHandler(params, "getModuleBuildStatus", defaultHandler)
return callModuleHandler(params, "getModuleBuildStatus", async () => ({ ready: false }))
},

buildModule: async <T extends Module>(params: PluginContextModuleParams<BuildModuleParams<T>>) => {
const defaultHandler = garden.getModuleActionHandler("buildModule", "generic")
const { module, handler } = await getModuleAndHandler(params.moduleName, "buildModule", defaultHandler)
const { module, handler } = await getModuleAndHandler(params.moduleName, "buildModule", params.pluginName)
await garden.buildDir.syncDependencyProducts(module)
return handler({ ...commonParams(handler), module, logEntry: params.logEntry })
},
Expand All @@ -369,8 +378,7 @@ export function createPluginContext(garden: Garden): PluginContext {
},

testModule: async <T extends Module>(params: PluginContextModuleParams<TestModuleParams<T>>) => {
const defaultHandler = garden.getModuleActionHandler("testModule", "generic")
return callModuleHandler(params, "testModule", defaultHandler)
return callModuleHandler(params, "testModule")
},

getTestResult: async <T extends Module>(params: PluginContextModuleParams<GetTestResultParams<T>>) => {
Expand Down
10 changes: 7 additions & 3 deletions garden-cli/src/types/plugin/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ export interface DeleteConfigParams extends PluginActionParamsBase {
key: string[]
}

export interface GetLoginStatusParams extends PluginActionParamsBase { }
export interface LoginParams extends PluginActionParamsBase { }
export interface LogoutParams extends PluginActionParamsBase { }

export interface PluginActionParams {
getEnvironmentStatus: GetEnvironmentStatusParams
configureEnvironment: ConfigureEnvironmentParams
Expand All @@ -86,9 +90,9 @@ export interface PluginActionParams {
setConfig: SetConfigParams
deleteConfig: DeleteConfigParams

getLoginStatus: PluginActionParamsBase
login: PluginActionParamsBase
logout: PluginActionParamsBase
getLoginStatus: GetLoginStatusParams
login: LoginParams
logout: LogoutParams
}

export interface GetModuleBuildStatusParams<T extends Module = Module> extends PluginModuleActionParamsBase<T> {
Expand Down
11 changes: 11 additions & 0 deletions garden-cli/test/data/test-project-generic/garden.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
project:
name: test-project-a
environmentDefaults:
variables:
some: variable
environments:
- name: local
providers:
- name: test-plugin
- name: test-plugin-b
- name: other
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"latestCommit": "1234567890",
"dirtyTimestamp": null
}
8 changes: 8 additions & 0 deletions garden-cli/test/data/test-project-generic/module-a/garden.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module:
name: module-a
type: generic
build:
command: [echo, A]
tests:
- name: unit
command: [echo, OK]
10 changes: 10 additions & 0 deletions garden-cli/test/data/test-project-generic/module-b/garden.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module:
name: module-b
type: generic
build:
command: [echo, B]
dependencies:
- module-a
tests:
- name: unit
command: [echo, OK]
Loading

0 comments on commit dec8e35

Please sign in to comment.