Skip to content

Commit

Permalink
fix: plugin command issues (TBS)
Browse files Browse the repository at this point in the history
  • Loading branch information
edvald committed Aug 5, 2019
1 parent 2ca2774 commit da326b9
Show file tree
Hide file tree
Showing 11 changed files with 87 additions and 44 deletions.
10 changes: 9 additions & 1 deletion garden-service/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,15 @@ export class GardenCli {
const log = logger.placeholder()
const footerLog = logger.placeholder()

const contextOpts: GardenOpts = { environmentName: env, log }
const contextOpts: GardenOpts = {
commandInfo: {
name: command.getFullName(),
args: parsedArgs,
opts: parsedOpts,
},
environmentName: env,
log,
}
if (command.noProject) {
contextOpts.config = MOCK_CONFIG
}
Expand Down
11 changes: 9 additions & 2 deletions garden-service/src/commands/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { LogEntry } from "../logger/log-entry"
import { Garden } from "../garden"
import { Command, CommandResult, CommandParams, StringParameter } from "./base"
import * as Bluebird from "bluebird"
import { printHeader } from "../logger/util"

const pluginArgs = {
plugin: new StringParameter({
Expand Down Expand Up @@ -53,8 +54,8 @@ export class PluginsCommand extends Command<Args> {
arguments = pluginArgs

async action({ garden, log, args }: CommandParams<Args>): Promise<CommandResult> {
const providers = await garden.resolveProviders()
const configuredPlugins = providers.map(p => p.name)
const providerConfigs = await garden.getRawProviderConfigs()
const configuredPlugins = providerConfigs.map(p => p.name)

if (!args.command) {
// We're listing commands, not executing one
Expand All @@ -77,6 +78,12 @@ export class PluginsCommand extends Command<Args> {
}
}

if (command.title) {
const environmentName = garden.environmentName
const title = typeof command.title === "function" ? await command.title({ environmentName }) : command.title
printHeader(log, title, "gear")
}

const provider = await garden.resolveProvider(args.plugin)
const ctx = await garden.getPluginContext(provider)

Expand Down
5 changes: 3 additions & 2 deletions garden-service/src/garden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { LocalConfigStore, ConfigStore, GlobalConfigStore } from "./config-store
import { getLinkedSources, ExternalSourceType } from "./util/ext-source-util"
import { BuildDependencyConfig, ModuleConfig, ModuleResource, moduleConfigSchema } from "./config/module"
import { ModuleConfigContext, ContextResolveOpts } from "./config/config-context"
import { createPluginContext } from "./plugin-context"
import { createPluginContext, CommandInfo } from "./plugin-context"
import { ModuleAndRuntimeActions, Plugins, RegisterPluginParam } from "./types/plugin/plugin"
import { SUPPORTED_PLATFORMS, SupportedPlatform, DEFAULT_GARDEN_DIR_NAME } from "./constants"
import { platform, arch } from "os"
Expand Down Expand Up @@ -71,6 +71,7 @@ export type ModuleActionMap = {

export interface GardenOpts {
config?: ProjectConfig,
commandInfo?: CommandInfo,
gardenDirPath?: string,
environmentName?: string,
log?: LogEntry,
Expand Down Expand Up @@ -244,7 +245,7 @@ export class Garden {
}

getPluginContext(provider: Provider) {
return createPluginContext(this, provider)
return createPluginContext(this, provider, this.opts.commandInfo)
}

async clearBuilds() {
Expand Down
40 changes: 29 additions & 11 deletions garden-service/src/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import { Garden } from "./garden"
import { cloneDeep } from "lodash"
import { projectNameSchema, projectSourcesSchema, environmentNameSchema } from "./config/project"
import { Provider, providerSchema, ProviderConfig } from "./config/provider"
import { configStoreSchema } from "./config-store"
import { deline } from "./util/string"
import { joi } from "./config/common"
import { joi, joiVariables, PrimitiveMap } from "./config/common"

type WrappedFromGarden = Pick<Garden,
"projectName" |
Expand All @@ -21,11 +20,17 @@ type WrappedFromGarden = Pick<Garden,
"gardenDirPath" |
"workingCopyId" |
// TODO: remove this from the interface
"configStore" |
"environmentName"
>

export interface CommandInfo {
name: string
args: PrimitiveMap
opts: PrimitiveMap
}

export interface PluginContext<C extends ProviderConfig = ProviderConfig> extends WrappedFromGarden {
command?: CommandInfo
provider: Provider<C>
}

Expand All @@ -34,31 +39,44 @@ export interface PluginContext<C extends ProviderConfig = ProviderConfig> extend
export const pluginContextSchema = joi.object()
.options({ presence: "required" })
.keys({
projectName: projectNameSchema,
projectRoot: joi.string()
.description("The absolute path of the project root."),
command: joi.object()
.optional()
.keys({
name: joi.string()
.required()
.description("The command name currently being executed."),
args: joiVariables()
.required()
.description("The positional arguments passed to the command."),
opts: joiVariables()
.required()
.description("The optional flags passed to the command."),
})
.description("Information about the command being executed, if applicable."),
environmentName: environmentNameSchema,
gardenDirPath: joi.string()
.description(deline`
The absolute path of the project's Garden dir. This is the directory the contains builds, logs and
other meta data. A custom path can be set when initialising the Garden class. Defaults to \`.garden\`.
`),
projectName: projectNameSchema,
projectRoot: joi.string()
.description("The absolute path of the project root."),
projectSources: projectSourcesSchema,
configStore: configStoreSchema,
environmentName: environmentNameSchema,
provider: providerSchema
.description("The provider being used for this context."),
workingCopyId: joi.string()
.description("A unique ID assigned to the current project working copy."),
})

export function createPluginContext(garden: Garden, provider: Provider): PluginContext {
export function createPluginContext(garden: Garden, provider: Provider, command?: CommandInfo): PluginContext {
return {
command,
environmentName: garden.environmentName,
gardenDirPath: garden.gardenDirPath,
projectName: garden.projectName,
projectRoot: garden.projectRoot,
gardenDirPath: garden.gardenDirPath,
projectSources: cloneDeep(garden.projectSources),
configStore: garden.configStore,
provider,
workingCopyId: garden.workingCopyId,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export const cleanupClusterRegistry: PluginCommand = {
name: "cleanup-cluster-registry",
description: "Clean up unused images in the in-cluster registry and cache.",

title: "Cleaning up caches and unused images from the in-cluster registry",

handler: async ({ ctx, log }) => {
const result = {}

Expand Down
16 changes: 7 additions & 9 deletions garden-service/src/plugins/kubernetes/commands/cluster-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,27 @@ export const clusterInit: PluginCommand = {
name: "cluster-init",
description: "Initialize or update cluster-wide Garden services.",

handler: async ({ ctx, log }) => {
const entry = log.info({
msg: chalk.bold.magenta(
`Initializing/updating cluster-wide services for ${chalk.white(ctx.environmentName)} environment`,
),
})
title: ({ environmentName }) => {
return `Initializing/updating cluster-wide services for ${chalk.white(environmentName)} environment`
},

handler: async ({ ctx, log }) => {
const status = await getEnvironmentStatus({ ctx, log })
let result = {}

if (status.ready) {
entry.info("All services already initialized!")
log.info("All services already initialized!")
} else {
result = await prepareSystem({
ctx,
log: entry,
log,
force: true,
status,
clusterInit: true,
})
}

log.info(chalk.green("Done!"))
log.info(chalk.green("\nDone!"))

return { result }
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,20 @@ export const uninstallGardenServices: PluginCommand = {
name: "uninstall-garden-services",
description: "Clean up all installed cluster-wide Garden services.",

handler: async ({ ctx, log }) => {
const entry = log.info({
msg: chalk.bold.magenta(
`Removing cluster-wide services for ${chalk.white(ctx.environmentName)} environment`,
),
})
title: ({ environmentName }) => {
return `Removing cluster-wide services for ${chalk.white(environmentName)} environment`
},

handler: async ({ ctx, log }) => {
const k8sCtx = <KubernetesPluginContext>ctx
const variables = getKubernetesSystemVariables(k8sCtx.provider.config)

const sysGarden = await getSystemGarden(k8sCtx, variables || {}, log)
const actions = await sysGarden.getActionHelper()

const result = await actions.deleteEnvironment(entry)
const result = await actions.deleteEnvironment(log)

log.info(chalk.green("Done!"))
log.info(chalk.green("\nDone!"))

return { result }
},
Expand Down
26 changes: 17 additions & 9 deletions garden-service/src/plugins/kubernetes/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,21 +157,29 @@ export async function prepareSystem(
// If we require manual init and system services are ready OR outdated but none are *missing*, we warn
// in the prepareEnvironment handler, instead of flagging as not ready here. This avoids blocking users where
// there's variance in configuration between users of the same cluster, that often doesn't affect usage.
if (!clusterInit && remoteCluster && combinedState === "outdated" && !serviceStates.includes("missing")) {
log.warn({
symbol: "warning",
msg: chalk.yellow(deline`
One or more cluster-wide system services are outdated or their configuration does not match your current
configuration. You may want to run \`garden --env=${ctx.environmentName} plugins kubernetes cluster-init\`
to update them, or contact a cluster admin to do so.
`),
})
if (!clusterInit && remoteCluster) {
if (combinedState === "outdated" && !serviceStates.includes("missing")) {
log.warn({
symbol: "warning",
msg: chalk.yellow(deline`
One or more cluster-wide system services are outdated or their configuration does not match your current
configuration. You may want to run \`garden --env=${ctx.environmentName} plugins kubernetes cluster-init\`
to update them, or contact a cluster admin to do so.
`),
})
}
return {}
}

// We require manual init if we're installing any system services to remote clusters, to avoid conflicts
// between users or unnecessary work.
if (!clusterInit && remoteCluster && !systemReady) {
// Special-case so that this doesn't error when attempting to run the cluster init
const initCommandName = `plugins ${ctx.provider.name} cluster-init`
if (ctx.command && ctx.command.name === initCommandName) {
return {}
}

throw new KubernetesError(deline`
One or more cluster-wide system services are missing or not ready. You need to run
\`garden --env=${ctx.environmentName} plugins kubernetes cluster-init\`
Expand Down
1 change: 1 addition & 0 deletions garden-service/src/plugins/kubernetes/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export async function getSystemGarden(
providers: [sysProvider],
variables,
},
commandInfo: ctx.command,
log: log.info({ section: "garden-system", msg: "Initializing...", status: "active", indent: 1 }),
})
}
Expand Down
3 changes: 1 addition & 2 deletions garden-service/src/tasks/resolve-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { ModuleConfig } from "../config/module"
import { GardenPlugin } from "../types/plugin/plugin"
import { validateWithPath } from "../config/common"
import * as Bluebird from "bluebird"
import { createPluginContext } from "../plugin-context"
import { defaultEnvironmentStatus } from "../types/plugin/provider/getEnvironmentStatus"

interface Params extends TaskParams {
Expand Down Expand Up @@ -136,7 +135,7 @@ export class ResolveProviderTask extends BaseTask {
private async ensurePrepared(tmpProvider: Provider) {
const pluginName = tmpProvider.name
const actions = await this.garden.getActionHelper()
const ctx = createPluginContext(this.garden, tmpProvider)
const ctx = this.garden.getPluginContext(tmpProvider)

const log = this.log.placeholder()

Expand Down
3 changes: 3 additions & 0 deletions garden-service/src/types/plugin/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface PluginCommandHandler<T extends object = object> {
export interface PluginCommand {
name: string
description: string
title?: string | ((params: { environmentName: string }) => string | Promise<string>)
// TODO: allow arguments
handler: PluginCommandHandler
}
Expand All @@ -51,6 +52,8 @@ export const pluginCommandSchema = joi.object()
.required()
.max(80)
.description("A one-line description of the command (max 80 chars)."),
title: joi.alternatives(joi.string(), joi.func())
.description("A heading to print ahead of calling the command handler, or a function that returns it."),
handler: joi.func()
// TODO: see if we can define/output the function schema somehow
.description("The command handler."),
Expand Down

0 comments on commit da326b9

Please sign in to comment.