From dec8e358e6086c404e2bbd98f24fdd729267fb14 Mon Sep 17 00:00:00 2001 From: Jon Edvald Date: Tue, 31 Jul 2018 01:06:41 +0200 Subject: [PATCH] refactor(ctx): allow specifying plugin name when calling plugin actions --- garden-cli/src/garden.ts | 89 +++++++++++++------ garden-cli/src/plugin-context.ts | 82 +++++++++-------- garden-cli/src/types/plugin/params.ts | 10 ++- .../test/data/test-project-generic/garden.yml | 11 +++ .../module-a/.garden-version | 4 + .../test-project-generic/module-a/garden.yml | 8 ++ .../test-project-generic/module-b/garden.yml | 10 +++ .../test-project-generic/module-c/garden.yml | 9 ++ garden-cli/test/helpers.ts | 6 +- garden-cli/test/src/plugins/generic.ts | 4 +- 10 files changed, 161 insertions(+), 72 deletions(-) create mode 100644 garden-cli/test/data/test-project-generic/garden.yml create mode 100644 garden-cli/test/data/test-project-generic/module-a/.garden-version create mode 100644 garden-cli/test/data/test-project-generic/module-a/garden.yml create mode 100644 garden-cli/test/data/test-project-generic/module-b/garden.yml create mode 100644 garden-cli/test/data/test-project-generic/module-c/garden.yml diff --git a/garden-cli/src/garden.ts b/garden-cli/src/garden.ts index dcaee804e7..db74e2efb7 100644 --- a/garden-cli/src/garden.ts +++ b/garden-cli/src/garden.ts @@ -102,6 +102,7 @@ import { getLinkedSources, ExternalSourceType, } from "./util/ext-source-util" +import { pickBy } from "lodash" export interface ModuleMap { [key: string]: T @@ -804,27 +805,46 @@ export class Garden { /** * Get a handler for the specified action. */ - public getActionHandlers(actionType: T): ActionHandlerMap { - return pick(this.actionHandlers[actionType], this.getEnvPlugins()) + public getActionHandlers(actionType: T, pluginName?: string): ActionHandlerMap { + return this.filterActionHandlers(this.actionHandlers[actionType], pluginName) } /** * Get a handler for the specified module action. */ public getModuleActionHandlers>( - actionType: T, moduleType: string, + actionType: T, moduleType: string, pluginName?: string, ): ModuleActionHandlerMap { - 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( - 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] @@ -832,25 +852,31 @@ export class Garden { 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( - type: T, moduleType: string, defaultHandler?: ModuleActions[T], + type: T, moduleType: string, pluginName?: string, defaultHandler?: ModuleActions[T], ): ModuleActions[T] { - const handlers = values(this.getModuleActionHandlers(type, moduleType)) + const handlers = values(this.getModuleActionHandlers(type, moduleType, pluginName)) if (handlers.length) { return handlers[handlers.length - 1] @@ -858,16 +884,25 @@ export class Garden { 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, + ) + } } /** diff --git a/garden-cli/src/plugin-context.ts b/garden-cli/src/plugin-context.ts index c7b60a0792..e6e9f87ed8 100644 --- a/garden-cli/src/plugin-context.ts +++ b/garden-cli/src/plugin-context.ts @@ -61,6 +61,12 @@ import { ServiceActionParams, SetConfigParams, TestModuleParams, + GetLoginStatusParams, + LoginParams, + LogoutParams, + GetEnvironmentStatusParams, + ConfigureEnvironmentParams, + DestroyEnvironmentParams, } from "./types/plugin/params" import { Service, @@ -92,12 +98,13 @@ export interface ContextStatus { services: { [name: string]: ServiceStatus } } -export type PluginContextParams = Omit +export type PluginContextParams = + Omit & { pluginName?: string } export type PluginContextModuleParams = - Omit & { moduleName: string } + Omit & { moduleName: string, pluginName?: string } export type PluginContextServiceParams = Omit - & { serviceName: string, runtimeContext?: RuntimeContext } + & { serviceName: string, runtimeContext?: RuntimeContext, pluginName?: string } export type WrappedFromGarden = Pick Promise - configureEnvironment: (params: { force?: boolean }) => Promise - destroyEnvironment: (params: {}) => Promise + getEnvironmentStatus: (params: PluginContextParams) => Promise + configureEnvironment: (params: { force?: boolean, pluginName?: string }) => Promise + destroyEnvironment: (params: PluginContextParams) => Promise getConfig: (params: PluginContextParams) => Promise setConfig: (params: PluginContextParams) => Promise deleteConfig: (params: PluginContextParams) => Promise - getLoginStatus: (params: {}) => Promise - login: (params: {}) => Promise - logout: (params: {}) => Promise + getLoginStatus: (params: PluginContextParams) => Promise + login: (params: PluginContextParams) => Promise + logout: (params: PluginContextParams) => Promise getModuleBuildStatus: (params: PluginContextModuleParams>) => Promise @@ -193,10 +200,10 @@ export function createPluginContext(garden: Garden): PluginContext { } async function getModuleAndHandler( - 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 { @@ -210,7 +217,9 @@ export function createPluginContext(garden: Garden): PluginContext { actionType: T, defaultHandler?: ModuleActions[T], ): Promise { - 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), ...omit(params, ["moduleName"]), @@ -225,7 +234,9 @@ export function createPluginContext(garden: Garden): PluginContext { ): Promise { 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 @@ -268,13 +279,13 @@ export function createPluginContext(garden: Garden): PluginContext { //region Environment Actions //=========================================================================== - getEnvironmentStatus: async () => { - const handlers = garden.getActionHandlers("getEnvironmentStatus") + getEnvironmentStatus: async ({ pluginName }: PluginContextParams) => { + 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({}) @@ -298,44 +309,44 @@ export function createPluginContext(garden: Garden): PluginContext { return ctx.getEnvironmentStatus({}) }, - destroyEnvironment: async () => { - const handlers = garden.getActionHandlers("destroyEnvironment") + destroyEnvironment: async ({ pluginName }: PluginContextParams) => { + const handlers = garden.getActionHandlers("destroyEnvironment", pluginName) await Bluebird.each(values(handlers), h => h({ ...commonParams(h) })) return ctx.getEnvironmentStatus({}) }, - getConfig: async ({ key }: PluginContextParams) => { + getConfig: async ({ key, pluginName }: PluginContextParams) => { 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) => { + setConfig: async ({ key, value, pluginName }: PluginContextParams) => { garden.validateConfigKey(key) - const handler = garden.getActionHandler("setConfig") + const handler = garden.getActionHandler("setConfig", pluginName) return handler({ ...commonParams(handler), key, value }) }, - deleteConfig: async ({ key }: PluginContextParams) => { + deleteConfig: async ({ key, pluginName }: PluginContextParams) => { 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) => { + 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) => { + 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) => { + const handlers = garden.getActionHandlers("logout", pluginName) await Bluebird.each(values(handlers), h => h({ ...commonParams(h) })) return ctx.getLoginStatus({}) }, @@ -349,13 +360,11 @@ export function createPluginContext(garden: Garden): PluginContext { getModuleBuildStatus: async ( params: PluginContextModuleParams>, ) => { - const defaultHandler = garden.getModuleActionHandler("getModuleBuildStatus", "generic") - return callModuleHandler(params, "getModuleBuildStatus", defaultHandler) + return callModuleHandler(params, "getModuleBuildStatus", async () => ({ ready: false })) }, buildModule: async (params: PluginContextModuleParams>) => { - 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 }) }, @@ -369,8 +378,7 @@ export function createPluginContext(garden: Garden): PluginContext { }, testModule: async (params: PluginContextModuleParams>) => { - const defaultHandler = garden.getModuleActionHandler("testModule", "generic") - return callModuleHandler(params, "testModule", defaultHandler) + return callModuleHandler(params, "testModule") }, getTestResult: async (params: PluginContextModuleParams>) => { diff --git a/garden-cli/src/types/plugin/params.ts b/garden-cli/src/types/plugin/params.ts index 55f592acf8..f5a7b98300 100644 --- a/garden-cli/src/types/plugin/params.ts +++ b/garden-cli/src/types/plugin/params.ts @@ -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 @@ -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 extends PluginModuleActionParamsBase { diff --git a/garden-cli/test/data/test-project-generic/garden.yml b/garden-cli/test/data/test-project-generic/garden.yml new file mode 100644 index 0000000000..bd1f17723f --- /dev/null +++ b/garden-cli/test/data/test-project-generic/garden.yml @@ -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 diff --git a/garden-cli/test/data/test-project-generic/module-a/.garden-version b/garden-cli/test/data/test-project-generic/module-a/.garden-version new file mode 100644 index 0000000000..1d754b7141 --- /dev/null +++ b/garden-cli/test/data/test-project-generic/module-a/.garden-version @@ -0,0 +1,4 @@ +{ + "latestCommit": "1234567890", + "dirtyTimestamp": null +} diff --git a/garden-cli/test/data/test-project-generic/module-a/garden.yml b/garden-cli/test/data/test-project-generic/module-a/garden.yml new file mode 100644 index 0000000000..3ac80dc6b6 --- /dev/null +++ b/garden-cli/test/data/test-project-generic/module-a/garden.yml @@ -0,0 +1,8 @@ +module: + name: module-a + type: generic + build: + command: [echo, A] + tests: + - name: unit + command: [echo, OK] diff --git a/garden-cli/test/data/test-project-generic/module-b/garden.yml b/garden-cli/test/data/test-project-generic/module-b/garden.yml new file mode 100644 index 0000000000..7b568e2daf --- /dev/null +++ b/garden-cli/test/data/test-project-generic/module-b/garden.yml @@ -0,0 +1,10 @@ +module: + name: module-b + type: generic + build: + command: [echo, B] + dependencies: + - module-a + tests: + - name: unit + command: [echo, OK] diff --git a/garden-cli/test/data/test-project-generic/module-c/garden.yml b/garden-cli/test/data/test-project-generic/module-c/garden.yml new file mode 100644 index 0000000000..7290058036 --- /dev/null +++ b/garden-cli/test/data/test-project-generic/module-c/garden.yml @@ -0,0 +1,9 @@ +module: + name: module-c + type: generic + build: + dependencies: + - module-b + tests: + - name: unit + command: [echo, OK] diff --git a/garden-cli/test/helpers.ts b/garden-cli/test/helpers.ts index f93c2d136c..60fb9aa4d8 100644 --- a/garden-cli/test/helpers.ts +++ b/garden-cli/test/helpers.ts @@ -15,9 +15,7 @@ import { containerModuleSpecSchema, ContainerServiceConfig, } from "../src/plugins/container" -import { - testGenericModule, -} from "../src/plugins/generic" +import { testGenericModule, buildGenericModule } from "../src/plugins/generic" import { TaskResults } from "../src/task-graph" import { validate, @@ -141,6 +139,8 @@ export const testPlugin: PluginFactory = (): GardenPlugin => { } }, + buildModule: buildGenericModule, + async runModule(params: RunModuleParams) { const version = await params.module.getVersion() diff --git a/garden-cli/test/src/plugins/generic.ts b/garden-cli/test/src/plugins/generic.ts index 3af54de7f6..436acb8f16 100644 --- a/garden-cli/test/src/plugins/generic.ts +++ b/garden-cli/test/src/plugins/generic.ts @@ -7,7 +7,7 @@ import { Garden } from "../../../src/garden" import { PluginContext } from "../../../src/plugin-context" import { gardenPlugin, -} from "../../../src/plugins/container" +} from "../../../src/plugins/generic" import { GARDEN_BUILD_VERSION_FILENAME } from "../../../src/constants" import { writeModuleVersionFile, @@ -19,7 +19,7 @@ import { } from "../../helpers" describe("generic plugin", () => { - const projectRoot = resolve(dataDir, "test-project-a") + const projectRoot = resolve(dataDir, "test-project-generic") const moduleName = "module-a" let garden: Garden