Skip to content

Commit

Permalink
refactor(plugins): make plugin definition interface more intuitive
Browse files Browse the repository at this point in the history
This is to prepare for more changes related to the plugin SDK, and to
overall make the interface more intuitive. For example, we now
distinguish more explicitly between creating and extending module types.
Also added a bit of validation when loading plugins (see the added
tests).

There should be no visible change in usage or operation.
  • Loading branch information
edvald committed Oct 25, 2019
1 parent cd6ef69 commit de9b3c9
Show file tree
Hide file tree
Showing 49 changed files with 1,048 additions and 980 deletions.
105 changes: 53 additions & 52 deletions garden-service/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { fromPairs, mapValues, omit, pickBy } from "lodash"

import { PublishModuleParams, PublishResult } from "./types/plugin/module/publishModule"
import { SetSecretParams, SetSecretResult } from "./types/plugin/provider/setSecret"
import { validate, joi } from "./config/common"
import { validate } from "./config/common"
import { defaultProvider } from "./config/provider"
import { ParameterError, PluginError, ConfigurationError } from "./exceptions"
import { ActionHandlerMap, Garden, ModuleActionHandlerMap, ModuleActionMap, PluginActionMap } from "./garden"
Expand All @@ -37,17 +37,17 @@ import { TestModuleParams } from "./types/plugin/module/testModule"
import {
ModuleActionOutputs,
ModuleActionParams,
ModuleActions,
ModuleAndRuntimeActions,
ModuleActionHandlers,
ModuleAndRuntimeActionHandlers,
PluginActionOutputs,
PluginActionParams,
PluginActions,
PluginActionHandlers,
ServiceActionOutputs,
ServiceActionParams,
ServiceActions,
ServiceActionHandlers,
TaskActionOutputs,
TaskActionParams,
TaskActions,
TaskActionHandlers,
moduleActionDescriptions,
moduleActionNames,
pluginActionDescriptions,
Expand Down Expand Up @@ -120,24 +120,29 @@ export class ActionHelper implements TypeGuard {
private readonly actionHandlers: PluginActionMap
private readonly moduleActionHandlers: ModuleActionMap

constructor(private garden: Garden, plugins: { [key: string]: GardenPlugin }) {
constructor(private garden: Garden, plugins: GardenPlugin[]) {
this.actionHandlers = <PluginActionMap>fromPairs(pluginActionNames.map(n => [n, {}]))
this.moduleActionHandlers = <ModuleActionMap>fromPairs(moduleActionNames.map(n => [n, {}]))

for (const [name, plugin] of Object.entries(plugins)) {
const actions = plugin.actions || {}
for (const plugin of plugins) {
const handlers = plugin.handlers || {}

for (const actionType of pluginActionNames) {
const handler = actions[actionType]
handler && this.addActionHandler(name, plugin, actionType, handler)
const handler = handlers[actionType]
handler && this.addActionHandler(plugin, actionType, handler)
}

const moduleActions = plugin.moduleActions || {}
for (const spec of plugin.createModuleTypes || []) {
for (const actionType of moduleActionNames) {
const handler = spec.handlers[actionType]
handler && this.addModuleActionHandler(plugin, actionType, spec.name, handler)
}
}

for (const moduleType of Object.keys(moduleActions)) {
for (const spec of plugin.extendModuleTypes || []) {
for (const actionType of moduleActionNames) {
const handler = moduleActions[moduleType][actionType]
handler && this.addModuleActionHandler(name, plugin, actionType, moduleType, handler)
const handler = spec.handlers[actionType]
handler && this.addModuleActionHandler(plugin, actionType, spec.name, handler)
}
}
}
Expand Down Expand Up @@ -206,20 +211,6 @@ export class ActionHelper implements TypeGuard {
//region Module Actions
//===========================================================================

async describeType(moduleType: string) {
const handler = await this.getModuleActionHandler({
actionType: "describeType",
moduleType,
defaultHandler: async ({ }) => ({
docs: "",
moduleOutputsSchema: joi.object().options({ allowUnknown: true }),
schema: joi.object().options({ allowUnknown: true }),
}),
})

return handler({})
}

async getBuildStatus<T extends Module>(
params: ModuleActionHelperParams<GetBuildStatusParams<T>>,
): Promise<BuildStatus> {
Expand Down Expand Up @@ -454,13 +445,13 @@ export class ActionHelper implements TypeGuard {
}
}

private async callActionHandler<T extends keyof Omit<PluginActions, "configureProvider">>(
private async callActionHandler<T extends keyof Omit<PluginActionHandlers, "configureProvider">>(
{ params, actionType, pluginName, defaultHandler }:
{
params: ActionHelperParams<PluginActionParams[T]>,
actionType: T,
pluginName: string,
defaultHandler?: PluginActions[T],
defaultHandler?: PluginActionHandlers[T],
},
): Promise<PluginActionOutputs[T]> {
this.garden.log.silly(`Calling '${actionType}' handler on '${pluginName}'`)
Expand All @@ -478,9 +469,13 @@ export class ActionHelper implements TypeGuard {
return result
}

private async callModuleHandler<T extends keyof Omit<ModuleActions, "describeType" | "configure">>(
private async callModuleHandler<T extends keyof Omit<ModuleActionHandlers, "configure">>(
{ params, actionType, defaultHandler }:
{ params: ModuleActionHelperParams<ModuleActionParams[T]>, actionType: T, defaultHandler?: ModuleActions[T] },
{
params: ModuleActionHelperParams<ModuleActionParams[T]>,
actionType: T,
defaultHandler?: ModuleActionHandlers[T],
},
): Promise<ModuleActionOutputs[T]> {
const { module, pluginName, log } = params

Expand All @@ -490,7 +485,7 @@ export class ActionHelper implements TypeGuard {
moduleType: module.type,
actionType,
pluginName,
defaultHandler: defaultHandler as ModuleAndRuntimeActions[T],
defaultHandler: defaultHandler as ModuleAndRuntimeActionHandlers[T],
})

const handlerParams = {
Expand All @@ -505,9 +500,13 @@ export class ActionHelper implements TypeGuard {
return (<Function>handler)(handlerParams)
}

private async callServiceHandler<T extends keyof ServiceActions>(
private async callServiceHandler<T extends keyof ServiceActionHandlers>(
{ params, actionType, defaultHandler }:
{ params: ServiceActionHelperParams<ServiceActionParams[T]>, actionType: T, defaultHandler?: ServiceActions[T] },
{
params: ServiceActionHelperParams<ServiceActionParams[T]>,
actionType: T,
defaultHandler?: ServiceActionHandlers[T],
},
): Promise<ServiceActionOutputs[T]> {
let { log, service, runtimeContext } = params
let module = omit(service.module, ["_ConfigType"])
Expand All @@ -518,7 +517,7 @@ export class ActionHelper implements TypeGuard {
moduleType: module.type,
actionType,
pluginName: params.pluginName,
defaultHandler: defaultHandler as ModuleAndRuntimeActions[T],
defaultHandler: defaultHandler as ModuleAndRuntimeActionHandlers[T],
})

// Resolve ${runtime.*} template strings if needed.
Expand Down Expand Up @@ -552,11 +551,11 @@ export class ActionHelper implements TypeGuard {
return (<Function>handler)(handlerParams)
}

private async callTaskHandler<T extends keyof TaskActions>(
private async callTaskHandler<T extends keyof TaskActionHandlers>(
{ params, actionType, defaultHandler }:
{
params: TaskActionHelperParams<TaskActionParams[T]>, actionType: T,
defaultHandler?: TaskActions[T],
defaultHandler?: TaskActionHandlers[T],
},
): Promise<TaskActionOutputs[T]> {
let { task, log } = params
Expand All @@ -569,7 +568,7 @@ export class ActionHelper implements TypeGuard {
moduleType: module.type,
actionType,
pluginName: params.pluginName,
defaultHandler: defaultHandler as ModuleAndRuntimeActions[T],
defaultHandler: defaultHandler as ModuleAndRuntimeActionHandlers[T],
})

// Resolve ${runtime.*} template strings if needed.
Expand Down Expand Up @@ -603,9 +602,10 @@ export class ActionHelper implements TypeGuard {
return (<Function>handler)(handlerParams)
}

private addActionHandler<T extends keyof PluginActions>(
pluginName: string, plugin: GardenPlugin, actionType: T, handler: PluginActions[T],
private addActionHandler<T extends keyof PluginActionHandlers>(
plugin: GardenPlugin, actionType: T, handler: PluginActionHandlers[T],
) {
const pluginName = plugin.name
const schema = pluginActionDescriptions[actionType].resultSchema

const wrapped = async (...args) => {
Expand All @@ -627,9 +627,10 @@ export class ActionHelper implements TypeGuard {
typeHandlers[pluginName] = wrapped
}

private addModuleActionHandler<T extends keyof ModuleActions>(
pluginName: string, plugin: GardenPlugin, actionType: T, moduleType: string, handler: ModuleActions[T],
private addModuleActionHandler<T extends keyof ModuleActionHandlers>(
plugin: GardenPlugin, actionType: T, moduleType: string, handler: ModuleActionHandlers[T],
) {
const pluginName = plugin.name
const schema = moduleActionDescriptions[actionType].resultSchema

const wrapped = async (...args: any[]) => {
Expand Down Expand Up @@ -662,7 +663,7 @@ export class ActionHelper implements TypeGuard {
/**
* Get a handler for the specified action.
*/
public async getActionHandlers<T extends keyof PluginActions>(
public async getActionHandlers<T extends keyof PluginActionHandlers>(
actionType: T, pluginName?: string,
): Promise<ActionHandlerMap<T>> {
return this.filterActionHandlers(this.actionHandlers[actionType], pluginName)
Expand All @@ -671,7 +672,7 @@ export class ActionHelper implements TypeGuard {
/**
* Get a handler for the specified module action.
*/
public async getModuleActionHandlers<T extends keyof ModuleAndRuntimeActions>(
public async getModuleActionHandlers<T extends keyof ModuleAndRuntimeActionHandlers>(
{ actionType, moduleType, pluginName }:
{ actionType: T, moduleType: string, pluginName?: string },
): Promise<ModuleActionHandlerMap<T>> {
Expand All @@ -694,10 +695,10 @@ export class ActionHelper implements TypeGuard {
/**
* Get the last configured handler for the specified action (and optionally module type).
*/
public async getActionHandler<T extends keyof PluginActions>(
public async getActionHandler<T extends keyof PluginActionHandlers>(
{ actionType, pluginName, defaultHandler }:
{ actionType: T, pluginName: string, defaultHandler?: PluginActions[T] },
): Promise<PluginActions[T]> {
{ actionType: T, pluginName: string, defaultHandler?: PluginActionHandlers[T] },
): Promise<PluginActionHandlers[T]> {

const handlers = Object.values(await this.getActionHandlers(actionType, pluginName))

Expand Down Expand Up @@ -730,10 +731,10 @@ export class ActionHelper implements TypeGuard {
/**
* Get the last configured handler for the specified action.
*/
public async getModuleActionHandler<T extends keyof ModuleAndRuntimeActions>(
public async getModuleActionHandler<T extends keyof ModuleAndRuntimeActionHandlers>(
{ actionType, moduleType, pluginName, defaultHandler }:
{ actionType: T, moduleType: string, pluginName?: string, defaultHandler?: ModuleAndRuntimeActions[T] },
): Promise<ModuleAndRuntimeActions[T]> {
{ actionType: T, moduleType: string, pluginName?: string, defaultHandler?: ModuleAndRuntimeActionHandlers[T] },
): Promise<ModuleAndRuntimeActionHandlers[T]> {

const handlers = Object.values(await this.getModuleActionHandlers({ actionType, moduleType, pluginName }))

Expand Down
9 changes: 8 additions & 1 deletion garden-service/src/config/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { ProjectConfigContext } from "./config-context"
import { findByName, getNames } from "../util/util"
import { ConfigurationError, ParameterError } from "../exceptions"
import { PrimitiveMap } from "./common"
import { fixedPlugins } from "../plugins/plugins"
import { cloneDeep, omit } from "lodash"
import { providerConfigBaseSchema, Provider, ProviderConfig } from "./provider"
import { DEFAULT_API_VERSION } from "../constants"
Expand All @@ -37,6 +36,14 @@ import { resolve } from "path"
export const defaultVarfilePath = "garden.env"
export const defaultEnvVarfilePath = (environmentName: string) => `garden.${environmentName}.env`

// These plugins are always loaded
const fixedPlugins = [
"exec",
"container",
// TODO: remove this after we've implemented module type inheritance
"maven-container",
]

export interface CommonEnvironmentConfig {
providers?: ProviderConfig[] // further validated by each plugin
variables: { [key: string]: Primitive }
Expand Down
12 changes: 6 additions & 6 deletions garden-service/src/docs/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import titleize from "titleize"
import humanize from "humanize-string"
import { resolve } from "path"
import { projectSchema, environmentSchema } from "../config/project"
import { get, flatten, startCase, uniq, find } from "lodash"
import { get, flatten, startCase, uniq, keyBy, find } from "lodash"
import { baseModuleSpecSchema } from "../config/module"
import handlebars = require("handlebars")
import { joiArray, joi } from "../config/common"
Expand All @@ -24,8 +24,7 @@ import { indent, renderMarkdownTable } from "./util"
import { ModuleContext, ServiceRuntimeContext, TaskRuntimeContext } from "../config/config-context"
import { defaultDotIgnoreFiles } from "../util/fs"
import { providerConfigBaseSchema } from "../config/provider"
import { GardenPlugin } from "../types/plugin/plugin"
import { ModuleTypeDescription } from "../types/plugin/module/describeType"
import { GardenPlugin, ModuleTypeDefinition } from "../types/plugin/plugin"

export const TEMPLATES_DIR = resolve(GARDEN_SERVICE_ROOT, "src", "docs", "templates")
const partialTemplatePath = resolve(TEMPLATES_DIR, "config-partial.hbs")
Expand Down Expand Up @@ -452,7 +451,7 @@ function renderProviderReference(name: string, plugin: GardenPlugin) {
* Generates the module types reference from the module-type.hbs template.
* The reference includes the rendered output from the config-partial.hbs template.
*/
function renderModuleTypeReference(name: string, desc: ModuleTypeDescription) {
function renderModuleTypeReference(name: string, desc: ModuleTypeDefinition) {
let { schema, docs } = desc

const moduleTemplatePath = resolve(TEMPLATES_DIR, "module-type.hbs")
Expand Down Expand Up @@ -570,10 +569,11 @@ export async function writeConfigReferenceDocs(docsRoot: string) {
// Render module type docs
const moduleTypeDir = resolve(referenceDir, "module-types")
const readme = ["# Module Types", ""]
const moduleTypeDefinitions = keyBy(await garden.getModuleTypeDefinitions(), "name")

for (const { name } of moduleTypes) {
const path = resolve(moduleTypeDir, `${name}.md`)
const actions = await garden.getActionHelper()
const desc = await actions.describeType(name)
const desc = moduleTypeDefinitions[name]

console.log("->", path)
writeFileSync(path, renderModuleTypeReference(name, desc))
Expand Down
Loading

0 comments on commit de9b3c9

Please sign in to comment.