Skip to content

Commit

Permalink
feat(dashboard): add octant provider and dashboard integration (#2006)
Browse files Browse the repository at this point in the history
* feat(dashboard): add octant provider and dashboard integration

This adds an `octant` provider, that you can drop into any Garden
project that includes a `kubernetes`/`local-kubernetes` provider:

```
...
providers
  - name: octant
...
```

This will add an Octant tab to your dashboard, which will be loaded
on-demand, pointed at the context and namespace of your project.
You also get a `garden tools octant` command if you'd like to run it
standalone.

Most of the work in the PR is to do with the mechanism for providers to
add dashboard pages, which is now easy to do and can be done in a
dynamic fashion.

I also added a toggle for provider tools, to indicate whether they
should be pre-fetched as part of our container builds.
  • Loading branch information
edvald committed Aug 20, 2020
1 parent 79f3826 commit 5c6273c
Show file tree
Hide file tree
Showing 73 changed files with 1,068 additions and 409 deletions.
2 changes: 1 addition & 1 deletion alpine.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@ WORKDIR /project
RUN chmod +x /garden/garden \
&& ln -s /garden/garden /bin/garden \
&& chmod +x /bin/garden \
&& cd /garden/static && garden util fetch-tools --all --logger-type=basic
&& cd /garden/static && garden util fetch-tools --all --garden-image-build --logger-type=basic

ENTRYPOINT ["/garden/garden"]
2 changes: 1 addition & 1 deletion buster.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ RUN ln -s /garden/garden /bin/garden \
&& chmod +x /bin/garden \
&& cd /garden/static \
&& git init \
&& garden util fetch-tools --all --logger-type=basic
&& garden util fetch-tools --all --garden-image-build --logger-type=basic

ENTRYPOINT ["/garden/garden"]
8 changes: 8 additions & 0 deletions core/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ import { BuildDependencyConfig } from "./config/module"
import { Profile } from "./util/profiling"
import { ConfigGraph } from "./config-graph"
import { ModuleConfigContext } from "./config/config-context"
import { GetDashboardPageParams, GetDashboardPageResult } from "./types/plugin/provider/getDashboardPage"

const maxArtifactLogLines = 5 // max number of artifacts to list in console after task+test runs

Expand Down Expand Up @@ -266,6 +267,13 @@ export class ActionRouter implements TypeGuard {
return this.callActionHandler({ actionType: "deleteSecret", pluginName, params: omit(params, ["pluginName"]) })
}

async getDashboardPage(
params: RequirePluginName<ActionRouterParams<GetDashboardPageParams>>
): Promise<GetDashboardPageResult> {
const { pluginName } = params
return this.callActionHandler({ actionType: "getDashboardPage", pluginName, params: omit(params, ["pluginName"]) })
}

//endregion

//===========================================================================
Expand Down
4 changes: 4 additions & 0 deletions core/src/cli/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface ParameterConstructor<T> {
overrides?: string[]
cliDefault?: T
cliOnly?: boolean
hidden?: boolean
}

export abstract class Parameter<T> {
Expand All @@ -54,6 +55,7 @@ export abstract class Parameter<T> {
hints?: string
valueName: string
overrides: string[]
hidden: boolean

readonly cliDefault: T | undefined // Optionally specify a separate default for CLI invocation
readonly cliOnly: boolean // If true, only expose in the CLI, and not in the HTTP/WS server.
Expand All @@ -68,6 +70,7 @@ export abstract class Parameter<T> {
hints,
cliDefault,
cliOnly,
hidden,
}: ParameterConstructor<T>) {
this.help = help
this.required = required || false
Expand All @@ -78,6 +81,7 @@ export abstract class Parameter<T> {
this.overrides = overrides || []
this.cliDefault = cliDefault
this.cliOnly = cliOnly || false
this.hidden = hidden || false
}

// TODO: merge this and the parseString method?
Expand Down
14 changes: 8 additions & 6 deletions core/src/commands/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,10 +486,12 @@ export function describeParameters(args?: Parameters) {
if (!args) {
return
}
return Object.entries(args).map(([argName, arg]) => ({
name: argName,
usageName: arg.required ? `<${argName}>` : `[${argName}]`,
...arg,
help: stripAnsi(arg.help),
}))
return Object.entries(args)
.filter(([_, arg]) => !arg.hidden)
.map(([argName, arg]) => ({
name: argName,
usageName: arg.required ? `<${argName}>` : `[${argName}]`,
...arg,
help: stripAnsi(arg.help),
}))
}
2 changes: 1 addition & 1 deletion core/src/commands/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { PluginsCommand } from "./plugins"
import { LoginCommand } from "./login"
import { LogOutCommand } from "./logout"
import { ToolsCommand } from "./tools"
import { UtilCommand } from "./util"
import { UtilCommand } from "./util/util"

export const coreCommands: (Command | CommandGroup)[] = [
new BuildCommand(),
Expand Down
2 changes: 1 addition & 1 deletion core/src/commands/unlink/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class UnlinkModuleCommand extends Command<Args, Opts> {
const { modules = [] } = args

if (opts.all) {
await garden.configStore.set([localConfigKeys.linkedModuleSources], [])
await garden.configStore.set([localConfigKeys().linkedModuleSources], [])
log.info("Unlinked all modules")
return { result: [] }
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/commands/unlink/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class UnlinkSourceCommand extends Command<Args, Opts> {
const { sources = [] } = args

if (opts.all) {
await garden.configStore.set([localConfigKeys.linkedProjectSources], [])
await garden.configStore.set([localConfigKeys().linkedProjectSources], [])
log.info("Unlinked all sources")
return { result: [] }
}
Expand Down
37 changes: 20 additions & 17 deletions core/src/commands/util.ts → core/src/commands/util/fetch-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,28 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { Command, CommandParams, CommandGroup } from "./base"
import { RuntimeError } from "../exceptions"
import { Command, CommandParams } from "../base"
import { RuntimeError } from "../../exceptions"
import dedent from "dedent"
import { GardenPlugin } from "../types/plugin/plugin"
import { findProjectConfig } from "../config/base"
import { Garden, DummyGarden } from "../garden"
import { GardenPlugin } from "../../types/plugin/plugin"
import { findProjectConfig } from "../../config/base"
import { Garden, DummyGarden } from "../../garden"
import Bluebird from "bluebird"
import { PluginTool } from "../util/ext-tools"
import { PluginTool } from "../../util/ext-tools"
import { fromPairs, omit, uniqBy } from "lodash"
import { printHeader, printFooter } from "../logger/util"
import { BooleanParameter } from "../cli/params"

export class UtilCommand extends CommandGroup {
name = "util"
help = "Misc utility commands."

subCommands = [FetchToolsCommand]
}
import { printHeader, printFooter } from "../../logger/util"
import { BooleanParameter } from "../../cli/params"

const fetchToolsOpts = {
all: new BooleanParameter({
"all": new BooleanParameter({
help: "Fetch all tools for registered plugins, instead of just ones in the current env/project.",
required: false,
}),
"garden-image-build": new BooleanParameter({
help: "(Internal) Fetch only tools marked with _includeInGardenImage=true.",
required: false,
hidden: true,
}),
}

type FetchToolsOpts = typeof fetchToolsOpts
Expand Down Expand Up @@ -79,9 +77,14 @@ export class FetchToolsCommand extends Command<{}, FetchToolsOpts> {
printHeader(log, "Fetching all tools for the current project and environment", "hammer_and_wrench")
}

const tools = plugins.flatMap((plugin) =>
let tools = plugins.flatMap((plugin) =>
(plugin.tools || []).map((spec) => ({ plugin, tool: new PluginTool(spec) }))
)

if (opts["garden-image-build"]) {
tools = tools.filter((spec) => spec.tool.spec._includeInGardenImage)
}

// No need to fetch the same tools multiple times, if they're used in multiple providers
const deduplicated = uniqBy(tools, ({ tool }) => tool["versionPath"])

Expand Down
17 changes: 17 additions & 0 deletions core/src/commands/util/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright (C) 2018-2020 Garden Technologies, Inc. <info@garden.io>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { CommandGroup } from "../base"
import { FetchToolsCommand } from "./fetch-tools"

export class UtilCommand extends CommandGroup {
name = "util"
help = "Misc utility commands."

subCommands = [FetchToolsCommand]
}
19 changes: 10 additions & 9 deletions core/src/config-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import yaml from "js-yaml"
import { join } from "path"
import { ensureFile, readFile } from "fs-extra"
import { get, isPlainObject, unset } from "lodash"
import { get, isPlainObject, unset, mapValues } from "lodash"

import { Primitive, joiArray, joiPrimitive, joi } from "./config/common"
import { validateSchema } from "./config/validation"
Expand Down Expand Up @@ -185,20 +185,21 @@ const analyticsLocalConfigSchema = () =>
.meta({ internal: true })

const localConfigSchemaKeys = {
linkedModuleSources: joiArray(linkedSourceSchema()),
linkedProjectSources: joiArray(linkedSourceSchema()),
analytics: analyticsLocalConfigSchema(),
linkedModuleSources: () => joiArray(linkedSourceSchema()),
linkedProjectSources: () => joiArray(linkedSourceSchema()),
analytics: () => analyticsLocalConfigSchema(),
}

export const localConfigKeys = Object.keys(localConfigSchemaKeys).reduce((acc, key) => {
acc[key] = key
return acc
}, {}) as { [K in keyof typeof localConfigSchemaKeys]: K }
export const localConfigKeys = () =>
Object.keys(localConfigSchemaKeys).reduce((acc, key) => {
acc[key] = key
return acc
}, {}) as { [K in keyof typeof localConfigSchemaKeys]: K }

const localConfigSchema = () =>
joi
.object()
.keys(localConfigSchemaKeys)
.keys(mapValues(localConfigSchemaKeys, (schema) => schema()))
.meta({ internal: true })

// TODO: we should not be passing this to provider actions
Expand Down
29 changes: 29 additions & 0 deletions core/src/config/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,3 +484,32 @@ export const joiSchema = () => joi.object().unknown(true)
export function isPrimitive(value: any) {
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null
}

const versionStringSchema = () =>
joi
.string()
.regex(/^v/)
.required()
.description("String representation of the module version.")

const fileNamesSchema = () => joiArray(joi.string()).description("List of file paths included in the version.")

export const treeVersionSchema = () =>
joi.object().keys({
contentHash: joi
.string()
.required()
.description("The hash of all files in the directory, after filtering."),
files: fileNamesSchema(),
})

export const moduleVersionSchema = () =>
joi.object().keys({
versionString: versionStringSchema(),
dependencyVersions: joi
.object()
.pattern(/.+/, treeVersionSchema())
.default(() => ({}))
.description("The version of each of the dependencies of the module."),
files: fileNamesSchema(),
})
22 changes: 17 additions & 5 deletions core/src/config/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { uniq } from "lodash"
import { GardenPlugin } from "../types/plugin/plugin"
import { EnvironmentStatus } from "../types/plugin/provider/getEnvironmentStatus"
import { environmentStatusSchema } from "./status"
import { DashboardPage, dashboardPagesSchema } from "../types/plugin/provider/getDashboardPage"

export interface BaseProviderConfig {
name: string
Expand Down Expand Up @@ -61,6 +62,7 @@ export interface Provider<T extends BaseProviderConfig = BaseProviderConfig> {
moduleConfigs: ModuleConfig[]
config: T
status: EnvironmentStatus
dashboardPages: DashboardPage[]
}

export const providerSchema = () =>
Expand All @@ -71,6 +73,7 @@ export const providerSchema = () =>
config: providerConfigBaseSchema().required(),
moduleConfigs: joiArray(moduleConfigSchema().optional()),
status: environmentStatusSchema(),
dashboardPages: dashboardPagesSchema(),
})

export interface ProviderMap {
Expand All @@ -86,20 +89,29 @@ export const defaultProvider: Provider = {
moduleConfigs: [],
config: { name: "_default" },
status: { ready: true, outputs: {} },
dashboardPages: [],
}

export function providerFromConfig(
config: GenericProviderConfig,
dependencies: ProviderMap,
moduleConfigs: ModuleConfig[],
export function providerFromConfig({
plugin,
config,
dependencies,
moduleConfigs,
status,
}: {
plugin: GardenPlugin
config: GenericProviderConfig
dependencies: ProviderMap
moduleConfigs: ModuleConfig[]
status: EnvironmentStatus
): Provider {
}): Provider {
return {
name: config.name,
dependencies,
moduleConfigs,
config,
status,
dashboardPages: plugin.dashboardPages,
}
}

Expand Down
39 changes: 1 addition & 38 deletions core/src/config/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { joiArray, joi, joiVariables } 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()
.max(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())
.optional()
.description("One or more pages to add to the Garden dashboard.")
import { joi, joiVariables } from "./common"

export const environmentStatusSchema = () =>
joi
Expand All @@ -52,7 +16,6 @@ export const environmentStatusSchema = () =>
.boolean()
.required()
.description("Set to true if the environment is fully configured for a provider."),
dashboardPages: dashboardPagesSchema(),
detail: joi
.object()
.optional()
Expand Down
1 change: 1 addition & 0 deletions core/src/docs/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export async function writeConfigReferenceDocs(docsRoot: string) {
{ name: "kubernetes" },
{ name: "local-kubernetes" },
{ name: "maven-container" },
{ name: "octant" },
{ name: "openfaas" },
{ name: "terraform" },
],
Expand Down
Loading

0 comments on commit 5c6273c

Please sign in to comment.