Skip to content

Commit

Permalink
refactor(core): added manualInit flag to prepareEnvironment handler
Browse files Browse the repository at this point in the history
This signals to the handler whether it's being run implicitly, or explicitly
in the context of `garden init`
  • Loading branch information
edvald committed Jun 10, 2019
1 parent a124d31 commit 66aa473
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 45 deletions.
24 changes: 9 additions & 15 deletions garden-service/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,22 +161,17 @@ export class ActionHelper implements TypeGuard {
* the user to run `garden init`
*/
async prepareEnvironment(
{ force = false, pluginName, log, allowUserInput = false }:
{ force?: boolean, pluginName?: string, log: LogEntry, allowUserInput?: boolean },
{ force = false, pluginName, log, manualInit = false }:
{ force?: boolean, pluginName?: string, log: LogEntry, manualInit?: boolean },
) {
const handlers = this.getActionHelpers("prepareEnvironment", pluginName)
// FIXME: We're calling getEnvironmentStatus before preparing the environment.
// Results in 404 errors for unprepared/missing services.
// See: https://github.com/garden-io/garden/issues/353

const entry = log.info({ section: "providers", msg: "Getting status...", status: "active" })
const statuses = await this.getEnvironmentStatus({ pluginName, log: entry })

const needManualInit = Object.entries(statuses)
.map(([name, status]) => ({ ...status, name }))
.filter(status => status.ready === false && status.needManualInit === true)

if (!allowUserInput && needManualInit.length > 0) {
if (!manualInit && needManualInit.length > 0) {
const names = needManualInit.map(s => s.name).join(", ")
const msgPrefix = needManualInit.length === 1
? `Provider ${names} has been updated or hasn't been configured, and requires manual initialization`
Expand All @@ -190,11 +185,11 @@ export class ActionHelper implements TypeGuard {
)
}

const needPrep = Object.entries(handlers).filter(([name]) => {
const prepareHandlers = this.getActionHelpers("prepareEnvironment", pluginName)

const needPrep = Object.entries(prepareHandlers).filter(([name]) => {
const status = statuses[name] || { ready: false }
const needForce = status.detail && !!status.detail.needForce
const forcePrep = force || needForce
return forcePrep || !status.ready
return (force || !status.ready)
})

const output = {}
Expand All @@ -206,8 +201,6 @@ export class ActionHelper implements TypeGuard {
// sequentially go through the preparation steps, to allow plugins to request user input
for (const [name, handler] of needPrep) {
const status = statuses[name] || { ready: false }
const needForce = status.detail && !!status.detail.needForce
const forcePrep = force || needForce

const envLogEntry = entry.info({
status: "active",
Expand All @@ -217,7 +210,8 @@ export class ActionHelper implements TypeGuard {

await handler({
...await this.commonParams(handler, log),
force: forcePrep,
manualInit,
force,
status,
log: envLogEntry,
})
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class InitCommand extends Command {
logHeader({ log, emoji: "gear", command: `Initializing ${name} environment` })

const actions = await garden.getActionHelper()
await actions.prepareEnvironment({ log, force: opts.force, allowUserInput: true })
await actions.prepareEnvironment({ log, force: opts.force, manualInit: true })

log.info("")
logFooter({ log, emoji: "heavy_check_mark", command: `Done!` })
Expand Down
65 changes: 37 additions & 28 deletions garden-service/src/plugins/kubernetes/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import { KubeApi } from "./api"
import { getAppNamespace, prepareNamespaces, deleteNamespaces } from "./namespace"
import { KubernetesPluginContext, KubernetesConfig } from "./config"
import { KubernetesPluginContext, KubernetesConfig, KubernetesProvider } from "./config"
import { checkTillerStatus, installTiller } from "./helm/tiller"
import {
prepareSystemServices,
Expand Down Expand Up @@ -56,6 +56,8 @@ export async function getEnvironmentStatus({ ctx, log }: GetEnvironmentStatusPar
const systemServiceNames = k8sCtx.provider.config._systemServices
let needManualInit = false

const detail = { systemReady, projectReady, serviceStatuses: {}, systemServiceState: "unknown" }

if (systemServiceNames.length > 0) {
// Check Tiller status in system namespace
let systemTillerReady = true
Expand All @@ -69,42 +71,32 @@ export async function getEnvironmentStatus({ ctx, log }: GetEnvironmentStatusPar
const sysNamespaceUpToDate = await systemNamespaceUpToDate(api, log, namespace, contextForLog)

// Get system service statuses
const systemServiceStatuses = await getSystemServiceStatus({
const systemServiceStatus = await getSystemServiceStatus({
ctx: k8sCtx,
log,
namespace,
serviceNames: systemServiceNames,
variables: variables || {},
})

systemReady = systemTillerReady && sysNamespaceUpToDate && (
systemServiceStatuses.state === "ready"
||
(needManualInit && systemServiceStatuses.state === "outdated")
)

dashboardPages = systemServiceStatuses.dashboardPages

if (needManualInit && systemServiceStatuses.state === "outdated") {
// If we require manual init and system services are outdated (as opposed to unhealthy, missing etc.), we warn
// instead of aborting. This avoids blocking users where there's variance in configuration between users of the
// same cluster, that most likely shouldn't affect usage.
log.warn({
symbol: "warning",
msg: chalk.yellow(
"One or more cluster-wide services are outdated or their configuration does not match your current " +
"configuration. You may want to run \`garden init\` to update them, or contact your cluster admin.",
),
})
// We require manual init if we're installing any system services to remote clusters, to avoid conflicts
// between users or unnecessary work.
needManualInit = checkManualInit(k8sCtx.provider)
systemReady = systemTillerReady && sysNamespaceUpToDate && systemServiceStatus.state === "ready"
dashboardPages = systemServiceStatus.dashboardPages

// If we require manual init and system services are outdated (as opposed to unhealthy, missing etc.), 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 most likely shouldn't affect usage.
if (needManualInit && systemServiceStatus.state === "outdated") {
needManualInit = false
}

// We always require manual init if we're installing any system services to remote clusters, to avoid conflicts
// between users or unnecessary work.
needManualInit = ctx.provider.name !== "local-kubernetes"
detail.systemReady = systemReady
detail.serviceStatuses = systemServiceStatus.serviceStatuses
detail.systemServiceState = systemServiceStatus.state
}

const detail = { systemReady, projectReady }

return {
ready: projectReady && systemReady,
detail,
Expand All @@ -113,23 +105,40 @@ export async function getEnvironmentStatus({ ctx, log }: GetEnvironmentStatusPar
}
}

function checkManualInit(provider: KubernetesProvider) {
return provider.name !== "local-kubernetes"
}

/**
* Performs the following actions to prepare the environment
* 1. Installs Tiller in project namespace
* 2. Installs Tiller in system namespace (if provider has system services)
* 3. Deploys system services (if provider has system services)
*/
export async function prepareEnvironment({ ctx, log, force, status }: PrepareEnvironmentParams) {
export async function prepareEnvironment({ ctx, log, force, manualInit, status }: PrepareEnvironmentParams) {
const k8sCtx = <KubernetesPluginContext>ctx
const variables = getVariables(k8sCtx.provider.config)
const systemReady = status.detail && !!status.detail.systemReady && !force

// Install Tiller to project namespace
await installTiller({ ctx: k8sCtx, provider: k8sCtx.provider, log, force })

const systemReady = status.detail && !!status.detail.systemReady && !force
const systemServiceNames = k8sCtx.provider.config._systemServices

if (systemServiceNames.length > 0 && !systemReady) {
const needManualInit = checkManualInit(k8sCtx.provider)

if (!manualInit && needManualInit && status.detail && status.detail.systemServiceState === "outdated") {
log.warn({
symbol: "warning",
msg: chalk.yellow(
"One or more cluster-wide services are outdated or their configuration does not match your current " +
"configuration. You may want to run \`garden init\` to update them, or contact a cluster admin to do so.",
),
})
return {}
}

// Install Tiller to system namespace
const sysGarden = await getSystemGarden(k8sCtx.provider, variables || {})
const sysCtx = <KubernetesPluginContext>await sysGarden.getPluginContext(k8sCtx.provider.name)
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 @@ -155,6 +155,7 @@ export async function getSystemServiceStatus(

return {
state,
serviceStatuses,
dashboardPages,
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { environmentStatusSchema, EnvironmentStatus } from "./getEnvironmentStat
import { dedent } from "../../../util/string"

export interface PrepareEnvironmentParams extends PluginActionParamsBase {
manualInit: boolean
status: EnvironmentStatus
force: boolean
}
Expand All @@ -34,9 +35,11 @@ export const prepareEnvironment = {
`,
paramsSchema: actionParamsSchema
.keys({
status: environmentStatusSchema,
force: Joi.boolean()
.description("Force re-configuration of the environment."),
manualInit: Joi.boolean()
.description("Set to true if the environment is being explicitly initialized via `garden init`."),
status: environmentStatusSchema,
}),
resultSchema: Joi.object().keys({}),
}

0 comments on commit 66aa473

Please sign in to comment.