Skip to content

Commit

Permalink
improvement(k8s): add explicit cluster-init command for remote clusters
Browse files Browse the repository at this point in the history
This avoids complications relating to differing provider configurations
and Garden versions, by removing cluster-wide init from the standard
`garden init` flow and making it an explicit plugin command.

`local-kubernetes` environments are not affected by this, since they are
not shared between users/CI/etc.
  • Loading branch information
edvald committed Jun 27, 2019
1 parent dcc8c3f commit 0a70a06
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 135 deletions.
13 changes: 13 additions & 0 deletions docs/using-garden/remote-clusters.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ The plugin will create two or more namespaces per user and project, one to run s
metadata and configuration (this is so that your environment can be reset without
clearing your configuration variables), and potentially more to support specific plugins/providers.
## Initializing the cluster
When you're connecting to a new cluster, or after you have updated your provider configuration or Garden itself,
you need to install/update cluster-wide services that Garden needs to operate. When using `local-kubernetes` this
happens automatically when you're deploying or testing, but for remote clusters this requires a manual step. This is
so that different users don't end up "competing" with different configurations or versions.

To initialize or update your cluster-wide services, run:

```sh
garden --env=<environment-name> plugins kubernetes cluster-init
```

## Building and pushing images

Garden supports multiple methods for building images and making them available to the cluster. Below we detail how
Expand Down
28 changes: 4 additions & 24 deletions garden-service/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { PublishModuleParams, PublishResult } from "./types/plugin/module/publis
import { SetSecretParams, SetSecretResult } from "./types/plugin/provider/setSecret"
import { validate, joi } from "./config/common"
import { defaultProvider, Provider } from "./config/provider"
import { ConfigurationError, ParameterError, PluginError } from "./exceptions"
import { ParameterError, PluginError } from "./exceptions"
import { ActionHandlerMap, Garden, ModuleActionHandlerMap, ModuleActionMap, PluginActionMap } from "./garden"
import { LogEntry } from "./logger/log-entry"
import { ProcessResults, processServices } from "./process"
Expand Down Expand Up @@ -156,34 +156,15 @@ export class ActionHelper implements TypeGuard {
/**
* Checks environment status and calls prepareEnvironment for each provider that isn't flagged as ready.
*
* If any of the getEnvironmentStatus handlers return ready=false AND needManualInit=true, this throws and guides
* the user to run `garden init`
* If any of the getEnvironmentStatus handlers return ready=false.
*/
async prepareEnvironment(
{ force = false, pluginName, log, manualInit = false }:
{ force?: boolean, pluginName?: string, log: LogEntry, manualInit?: boolean },
{ force = false, pluginName, log }:
{ force?: boolean, pluginName?: string, log: LogEntry },
) {
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 (!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`
: `Providers ${names} have been updated or haven't been configured, and require manual initialization`

entry.setError()

throw new ConfigurationError(
`${msgPrefix}. Please run \`garden init\` and then re-run this command.`,
{ statuses },
)
}

const prepareHandlers = this.getActionHandlers("prepareEnvironment", pluginName)

const needPrep = Object.entries(prepareHandlers).filter(([name]) => {
Expand All @@ -209,7 +190,6 @@ export class ActionHelper implements TypeGuard {

await handler({
...await this.commonParams(handler, log),
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 {
printHeader(headerLog, `Initializing ${name} environment`, "gear")

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

printFooter(footerLog)

Expand Down
43 changes: 43 additions & 0 deletions garden-service/src/plugins/kubernetes/commands/cluster-init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2018 Garden Technologies, Inc. <[email protected]>
*
* 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 { PluginCommand } from "../../../types/plugin/command"
import { prepareSystem, getEnvironmentStatus } from "../init"
import chalk from "chalk"

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`,
),
})

const status = await getEnvironmentStatus({ ctx, log })
let result = {}

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

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

return { result }
},
}
10 changes: 7 additions & 3 deletions garden-service/src/plugins/kubernetes/helm/tiller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,15 @@ export async function installTiller({ ctx, log, provider, force = false }: Insta

// Need to install the RBAC stuff ahead of Tiller
const roleResources = getRoleResources(namespace)
entry.setState("Applying Tiller RBAC resources...")
await apply({ log, context, manifests: roleResources, namespace })
await waitForResources({ ctx, provider, serviceName: "tiller", resources: roleResources, log })
await waitForResources({ ctx, provider, serviceName: "tiller", resources: roleResources, log: entry })

const tillerResources = await getTillerResources(ctx, provider, log)
const pruneSelector = "app=helm,name=tiller"
entry.setState("Deploying Tiller...")
await apply({ log, context, manifests: tillerResources, namespace, pruneSelector })
await waitForResources({ ctx, provider, serviceName: "tiller", resources: tillerResources, log })
await waitForResources({ ctx, provider, serviceName: "tiller", resources: tillerResources, log: entry })

entry.setSuccess({ msg: chalk.green(`Done (took ${entry.getDuration(1)} sec)`), append: true })
}
Expand All @@ -82,7 +84,9 @@ async function getTillerResources(
"--debug",
)

return safeLoadAll(tillerManifests)
const resources = safeLoadAll(tillerManifests)

return resources
}

function getRoleResources(namespace: string) {
Expand Down
Loading

0 comments on commit 0a70a06

Please sign in to comment.