diff --git a/garden-service/src/config/dashboard.ts b/garden-service/src/config/dashboard.ts new file mode 100644 index 0000000000..827a42d7ee --- /dev/null +++ b/garden-service/src/config/dashboard.ts @@ -0,0 +1,32 @@ +import Joi = require("joi") +import { joiArray } from "./common" + +export interface DashboardPage { + title: string + description: string + url: string + newWindow: boolean + // TODO: allow nested sections + // children: DashboardPage[] +} + +export const dashboardPageSchema = Joi.object() + .keys({ + title: Joi.string() + .length(32) + .required() + .description("The link title to show in the menu bar (max length 32)."), + description: Joi.string() + .required() + .description("A description to show when hovering over the link."), + url: Joi.string() + .uri() + .required() + .description("The URL to open in the dashboard pane when clicking the link."), + newWindow: Joi.boolean() + .default(false) + .description("Set to true if the link should open in a new browser tab/window."), + }) + +export const dashboardPagesSchema = joiArray(dashboardPageSchema) + .description("One or more pages to add to the Garden dashboard.") diff --git a/garden-service/src/config/project.ts b/garden-service/src/config/project.ts index 537b022877..0dd2a2d1a8 100644 --- a/garden-service/src/config/project.ts +++ b/garden-service/src/config/project.ts @@ -15,6 +15,7 @@ import { Primitive, joiRepositoryUrl, } from "./common" +import { DashboardPage } from "../config/dashboard" export interface ProviderConfig { name: string @@ -32,6 +33,7 @@ export const providerConfigBaseSchema = Joi.object() export interface Provider { name: string + dashboardPages: DashboardPage[] config: T } @@ -52,10 +54,14 @@ export const environmentConfigSchema = Joi.object() .description("A key/value map of variables that modules can reference when using this environment."), }) -export interface Environment extends CommonEnvironmentConfig { +export interface EnvironmentConfig extends CommonEnvironmentConfig { name: string } +export interface Environment extends EnvironmentConfig { + providers: Provider[] +} + export const environmentSchema = environmentConfigSchema .keys({ name: Joi.string() @@ -85,7 +91,7 @@ export interface ProjectConfig { name: string defaultEnvironment: string environmentDefaults: CommonEnvironmentConfig - environments: Environment[] + environments: EnvironmentConfig[] sources?: SourceConfig[] } @@ -93,7 +99,7 @@ export const defaultProviders = [ { name: "container" }, ] -export const defaultEnvironments: Environment[] = [ +export const defaultEnvironments: EnvironmentConfig[] = [ { name: "local", providers: [ @@ -142,5 +148,6 @@ export const projectSchema = Joi.object() // this is used for default handlers in the action handler export const defaultProvider: Provider = { name: "_default", + dashboardPages: [], config: {}, } diff --git a/garden-service/src/garden.ts b/garden-service/src/garden.ts index ecb23042ef..4ed440a24c 100644 --- a/garden-service/src/garden.ts +++ b/garden-service/src/garden.ts @@ -43,7 +43,7 @@ import { pluginModuleSchema, pluginSchema, } from "./types/plugin/plugin" -import { Environment, SourceConfig, defaultProvider } from "./config/project" +import { Environment, SourceConfig, defaultProvider, Provider } from "./config/project" import { findByName, getIgnorer, @@ -82,7 +82,7 @@ import { GardenPlugin, ModuleActions, } from "./types/plugin/plugin" -import { joiIdentifier, validate } from "./config/common" +import { joiIdentifier, validate, PrimitiveMap } from "./config/common" import { Service } from "./types/service" import { Task } from "./types/task" import { resolveTemplateStrings } from "./template-string" @@ -154,6 +154,7 @@ export class Garden { private readonly hotReloadScheduler: HotReloadScheduler private readonly taskGraph: TaskGraph + public readonly environment: Environment public readonly localConfigStore: LocalConfigStore public readonly vcs: VcsHandler public readonly cache: TreeCache @@ -162,7 +163,8 @@ export class Garden { constructor( public readonly projectRoot: string, public readonly projectName: string, - public readonly environment: Environment, + environmentName: string, + variables: PrimitiveMap, public readonly projectSources: SourceConfig[] = [], public readonly buildDir: BuildDir, log?: LogEntry, @@ -186,6 +188,13 @@ export class Garden { this.localConfigStore = new LocalConfigStore(this.projectRoot) this.cache = new TreeCache() + this.environment = { + name: environmentName, + // The providers are populated when adding plugins in the factory. + providers: [], + variables, + } + this.moduleConfigs = {} this.serviceNameIndex = {} this.taskNameIndex = {} @@ -269,25 +278,21 @@ export class Garden { const fixedProviders = fixedPlugins.map(name => ({ name })) - const mergedProviders = merge( + const mergedProviderConfigs = merge( fixedProviders, keyBy(environmentDefaults.providers, "name"), keyBy(environmentConfig.providers, "name"), ) - // Resolve the project configuration based on selected environment - const environment: Environment = { - name: environmentConfig.name, - providers: Object.values(mergedProviders), - variables: merge({}, environmentDefaults.variables, environmentConfig.variables), - } + const variables = merge({}, environmentDefaults.variables, environmentConfig.variables) const buildDir = await BuildDir.factory(projectRoot) const garden = new Garden( projectRoot, projectName, - environment, + environmentName, + variables, projectSources, buildDir, log, @@ -300,7 +305,7 @@ export class Garden { // Load configured plugins // Validate configuration - for (const provider of environment.providers) { + for (const provider of Object.values(mergedProviderConfigs)) { await garden.loadPlugin(provider.name, provider) } @@ -423,7 +428,12 @@ export class Garden { if (providerConfig) { extend(providerConfig, plugin.config, config) } else { - this.environment.providers.push(extend({ name: pluginName }, plugin.config, config)) + const provider: Provider = { + name: pluginName, + dashboardPages: plugin.dashboardPages, + config: extend({ name: pluginName }, plugin.config, config), + } + this.environment.providers.push(provider) } for (const modulePath of plugin.modules || []) { diff --git a/garden-service/src/plugin-context.ts b/garden-service/src/plugin-context.ts index a24e141b0c..540bf1d389 100644 --- a/garden-service/src/plugin-context.ts +++ b/garden-service/src/plugin-context.ts @@ -7,7 +7,7 @@ */ import { Garden } from "./garden" -import { mapValues, keyBy, cloneDeep } from "lodash" +import { keyBy, cloneDeep } from "lodash" import * as Joi from "joi" import { Provider, @@ -19,6 +19,7 @@ import { import { joiIdentifier, joiIdentifierMap } from "./config/common" import { PluginError } from "./exceptions" import { defaultProvider } from "./config/project" +import { dashboardPagesSchema } from "./config/dashboard" type WrappedFromGarden = Pick ({ name, config })) + const providers = keyBy(projectConfig.providers, "name") let provider = providers[providerName] if (providerName === "_default") { diff --git a/garden-service/src/types/plugin/plugin.ts b/garden-service/src/types/plugin/plugin.ts index ad0e46d19e..c50cb7afc2 100644 --- a/garden-service/src/types/plugin/plugin.ts +++ b/garden-service/src/types/plugin/plugin.ts @@ -19,6 +19,7 @@ import { serviceStatusSchema } from "../service" import { serviceOutputsSchema } from "../../config/service" import { LogNode } from "../../logger/log-node" import { Provider } from "../../config/project" +import { dashboardPagesSchema, DashboardPage } from "../../config/dashboard" import { ModuleActionParams, PluginActionParams, @@ -413,6 +414,9 @@ export interface GardenPlugin { modules?: string[] + // TODO: move this to the configureProvider output, once that's implemented + dashboardPages?: DashboardPage[] + actions?: Partial moduleActions?: { [moduleType: string]: Partial } } @@ -439,6 +443,7 @@ export const pluginSchema = Joi.object() "Plugins may use this key to override or augment their configuration " + "(as specified in the garden.yml provider configuration.", ), + dashboardPages: dashboardPagesSchema, modules: joiArray(Joi.string()) .description( "Plugins may optionally provide paths to Garden modules that are loaded as part of the plugin. " + diff --git a/garden-service/test/src/garden.ts b/garden-service/test/src/garden.ts index 90b5a86c2a..9409efa6fc 100644 --- a/garden-service/test/src/garden.ts +++ b/garden-service/test/src/garden.ts @@ -52,10 +52,10 @@ describe("Garden", () => { expect(garden.environment).to.eql({ name: "local", providers: [ - { name: "generic" }, - { name: "container" }, - { name: "test-plugin" }, - { name: "test-plugin-b" }, + { name: "generic", dashboardPages: [], config: { name: "generic" } }, + { name: "container", dashboardPages: [], config: { name: "container" } }, + { name: "test-plugin", dashboardPages: [], config: { name: "test-plugin" } }, + { name: "test-plugin-b", dashboardPages: [], config: { name: "test-plugin-b" } }, ], variables: { some: "variable", @@ -77,9 +77,9 @@ describe("Garden", () => { expect(garden.environment).to.eql({ name: "local", providers: [ - { name: "generic" }, - { name: "container" }, - { name: "test-plugin" }, + { name: "generic", dashboardPages: [], config: { name: "generic" } }, + { name: "container", dashboardPages: [], config: { name: "container" } }, + { name: "test-plugin", dashboardPages: [], config: { name: "test-plugin" } }, ], variables: { "some": "banana",