Skip to content

Commit

Permalink
feat(k8s): local mode for helm modules (#3033)
Browse files Browse the repository at this point in the history
  • Loading branch information
vvagaytsev authored Jul 8, 2022
1 parent 7ff078a commit a7722b5
Show file tree
Hide file tree
Showing 90 changed files with 1,999 additions and 190 deletions.
2 changes: 1 addition & 1 deletion core/src/plugins/kubernetes/helm/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { move } from "fs-extra"
import tmp from "tmp-promise"
import { HelmModule } from "./config"
import { containsBuildSource, getChartPath, getBaseModule } from "./common"
import { containsBuildSource, getBaseModule, getChartPath } from "./common"
import { helm } from "./helm-cli"
import { ConfigurationError } from "../../../exceptions"
import { deline } from "../../../util/string"
Expand Down
35 changes: 21 additions & 14 deletions core/src/plugins/kubernetes/helm/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { isPlainObject, flatten, cloneDeep } from "lodash"
import { cloneDeep, flatten, isPlainObject } from "lodash"
import { join, resolve } from "path"
import { pathExists, writeFile, remove, readFile } from "fs-extra"
import cryptoRandomString = require("crypto-random-string")
import { pathExists, readFile, remove, writeFile } from "fs-extra"
import { apply as jsonMerge } from "json-merge-patch"

import { PluginContext } from "../../../plugin-context"
Expand All @@ -22,11 +21,12 @@ import { HelmModule, HelmModuleConfig } from "./config"
import { ConfigurationError, PluginError } from "../../../exceptions"
import { GardenModule } from "../../../types/module"
import { deline, tailString } from "../../../util/string"
import { getAnnotation, flattenResources } from "../util"
import { flattenResources, getAnnotation } from "../util"
import { KubernetesPluginContext } from "../config"
import { RunResult } from "../../../types/plugin/base"
import { MAX_RUN_RESULT_LOG_LENGTH } from "../constants"
import { dumpYaml } from "../../../util/util"
import cryptoRandomString = require("crypto-random-string")

const gardenValuesFilename = "garden-values.yml"

Expand Down Expand Up @@ -69,6 +69,7 @@ interface GetChartResourcesParams {
module: GardenModule
devMode: boolean
hotReload: boolean
localMode: boolean
log: LogEntry
version: string
}
Expand All @@ -90,7 +91,7 @@ export async function getChartResources(params: GetChartResourcesParams) {
* Renders the given Helm module and returns a multi-document YAML string.
*/
export async function renderTemplates(params: GetChartResourcesParams): Promise<string> {
const { ctx, module, devMode, hotReload, version, log } = params
const { ctx, module, devMode, hotReload, localMode, version, log } = params
const { namespace, releaseName, chartPath } = await prepareTemplates(params)

log.debug("Preparing chart...")
Expand All @@ -100,6 +101,7 @@ export async function renderTemplates(params: GetChartResourcesParams): Promise<
module,
devMode,
hotReload,
localMode,
version,
log,
namespace,
Expand Down Expand Up @@ -160,7 +162,7 @@ export async function prepareTemplates({
}

export async function prepareManifests(params: PrepareManifestsParams): Promise<string> {
const { ctx, module, devMode, hotReload, log, namespace, releaseName, chartPath } = params
const { ctx, module, devMode, hotReload, localMode, log, namespace, releaseName, chartPath } = params
const res = await helm({
ctx,
log,
Expand All @@ -177,7 +179,7 @@ export async function prepareManifests(params: PrepareManifestsParams): Promise<
"json",
"--timeout",
module.spec.timeout.toString(10) + "s",
...(await getValueArgs(module, devMode, hotReload)),
...(await getValueArgs(module, devMode, hotReload, localMode)),
],
})

Expand Down Expand Up @@ -264,7 +266,7 @@ export function getGardenValuesPath(chartPath: string) {
/**
* Get the value files arguments that should be applied to any helm install/render command.
*/
export async function getValueArgs(module: HelmModule, devMode: boolean, hotReload: boolean) {
export async function getValueArgs(module: HelmModule, devMode: boolean, hotReload: boolean, localMode: boolean) {
const chartPath = await getChartPath(module)
const gardenValuesPath = getGardenValuesPath(chartPath)

Expand All @@ -274,11 +276,16 @@ export async function getValueArgs(module: HelmModule, devMode: boolean, hotRelo

const args = flatten(valueFiles.map((f) => ["--values", f]))

if (devMode) {
args.push("--set", "\\.garden.devMode=true")
}
if (hotReload) {
args.push("--set", "\\.garden.hotReload=true")
// Local mode always takes precedence over dev mode
if (localMode) {
args.push("--set", "\\.garden.localMode=true")
} else {
if (devMode) {
args.push("--set", "\\.garden.devMode=true")
}
if (hotReload) {
args.push("--set", "\\.garden.hotReload=true")
}
}

return args
Expand Down Expand Up @@ -329,7 +336,7 @@ export async function renderHelmTemplateString(
"--namespace",
namespace,
"--dependency-update",
...(await getValueArgs(module, false, false)),
...(await getValueArgs(module, false, false, false)),
"--show-only",
relPath,
chartPath,
Expand Down
2 changes: 2 additions & 0 deletions core/src/plugins/kubernetes/helm/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
hotReloadArgsSchema,
kubernetesDevModeSchema,
KubernetesDevModeSpec,
kubernetesLocalModeSchema,
KubernetesLocalModeSpec,
kubernetesTaskSchema,
KubernetesTaskSpec,
Expand Down Expand Up @@ -176,6 +177,7 @@ export const helmModuleSpecSchema = () =>
"List of names of services that should be deployed before this chart."
),
devMode: kubernetesDevModeSchema(),
localMode: kubernetesLocalModeSchema(),
include: joiModuleIncludeDirective(dedent`
If neither \`include\` nor \`exclude\` is set, and the module has local chart sources, Garden
automatically sets \`include\` to: \`["*", "charts/**/*", "templates/**/*"]\`.
Expand Down
66 changes: 51 additions & 15 deletions core/src/plugins/kubernetes/helm/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ import { waitForResources } from "../status/status"
import { helm } from "./helm-cli"
import { HelmModule } from "./config"
import {
filterManifests,
getBaseModule,
getChartPath,
getReleaseName,
getValueArgs,
getBaseModule,
prepareTemplates,
prepareManifests,
filterManifests,
prepareTemplates,
} from "./common"
import {
gardenCloudAECPauseAnnotation,
getPausedResources,
getReleaseStatus,
HelmServiceStatus,
getRenderedResources,
getPausedResources,
gardenCloudAECPauseAnnotation,
HelmServiceStatus,
} from "./status"
import { SyncableResource } from "../hot-reload/hot-reload"
import { apply, deleteResources } from "../kubectl"
Expand All @@ -35,9 +35,10 @@ import { DeleteServiceParams } from "../../../types/plugin/service/deleteService
import { getForwardablePorts, killPortForwards } from "../port-forward"
import { getServiceResource, getServiceResourceSpec } from "../util"
import { getModuleNamespace, getModuleNamespaceStatus } from "../namespace"
import { getHotReloadSpec, configureHotReload, getHotReloadContainerName } from "../hot-reload/helpers"
import { configureHotReload, getHotReloadContainerName, getHotReloadSpec } from "../hot-reload/helpers"
import { configureDevMode, startDevModeSync } from "../dev-mode"
import { KubeApi } from "../api"
import { configureLocalMode, startServiceInLocalMode } from "../local-mode"

export async function deployHelmService({
ctx,
Expand All @@ -47,6 +48,7 @@ export async function deployHelmService({
force,
devMode,
hotReload,
localMode,
}: DeployServiceParams<HelmModule>): Promise<HelmServiceStatus> {
let hotReloadSpec: ContainerHotReloadSpec | null = null
let serviceResourceSpec: ServiceResourceSpec | null = null
Expand All @@ -69,20 +71,30 @@ export async function deployHelmService({
module,
devMode,
hotReload,
localMode,
log,
version: service.version,
})

const chartPath = await getChartPath(module)
const releaseName = getReleaseName(module)
const releaseStatus = await getReleaseStatus({ ctx: k8sCtx, module, service, releaseName, log, devMode, hotReload })
const releaseStatus = await getReleaseStatus({
ctx: k8sCtx,
module,
service,
releaseName,
log,
devMode,
hotReload,
localMode,
})

const commonArgs = [
"--namespace",
namespace,
"--timeout",
module.spec.timeout.toString(10) + "s",
...(await getValueArgs(module, devMode, hotReload)),
...(await getValueArgs(module, devMode, hotReload, localMode)),
]

if (module.spec.atomicInstall) {
Expand Down Expand Up @@ -135,14 +147,15 @@ export async function deployHelmService({
module,
devMode,
hotReload,
localMode,
version: service.version,
namespace: preparedTemplates.namespace,
releaseName: preparedTemplates.releaseName,
chartPath: preparedTemplates.chartPath,
})
const manifests = await filterManifests(preparedManifests)

if ((devMode && module.spec.devMode) || hotReload) {
if ((devMode && module.spec.devMode) || hotReload || (localMode && module.spec.localMode)) {
serviceResourceSpec = getServiceResourceSpec(module, getBaseModule(module))
serviceResource = await getServiceResource({
ctx,
Expand All @@ -155,12 +168,23 @@ export async function deployHelmService({
}

// Because we need to modify the Deployment, and because there is currently no reliable way to do that before
// installing/upgrading via Helm, we need to separately update the target here for dev-mode/hot-reload.
if (devMode && service.spec.devMode && serviceResourceSpec && serviceResource) {
// installing/upgrading via Helm, we need to separately update the target here for dev-mode/hot-reload/local-mode.
// Local mode always takes precedence over dev mode.
if (localMode && service.spec.localMode && serviceResourceSpec && serviceResource) {
await configureLocalMode({
ctx,
spec: service.spec.localMode,
targetResource: serviceResource,
gardenService: service,
log,
containerName: service.spec.localMode.containerName,
})
await apply({ log, ctx, api, provider, manifests: [serviceResource], namespace })
} else if (devMode && service.spec.devMode && serviceResourceSpec && serviceResource) {
configureDevMode({
target: serviceResource,
spec: service.spec.devMode,
containerName: service.spec.devMode?.containerName,
containerName: service.spec.devMode.containerName,
})
await apply({ log, ctx, api, provider, manifests: [serviceResource], namespace })
} else if (hotReload && hotReloadSpec && serviceResourceSpec && serviceResource) {
Expand All @@ -185,12 +209,24 @@ export async function deployHelmService({
timeoutSec: module.spec.timeout,
})

const forwardablePorts = getForwardablePorts(manifests, service)
// Local mode has its own port-forwarding configuration
const forwardablePorts = localMode && service.spec.localMode ? [] : getForwardablePorts(manifests, service)

// Make sure port forwards work after redeployment
killPortForwards(service, forwardablePorts || [], log)

if (devMode && service.spec.devMode && serviceResource && serviceResourceSpec) {
// Local mode always takes precedence over dev mode.
if (localMode && service.spec.localMode && serviceResource && serviceResourceSpec) {
await startServiceInLocalMode({
ctx,
spec: service.spec.localMode,
targetResource: serviceResource,
gardenService: service,
namespace,
log,
containerName: service.spec.localMode.containerName,
})
} else if (devMode && service.spec.devMode && serviceResource && serviceResourceSpec) {
await startDevModeSync({
ctx,
log,
Expand Down
1 change: 1 addition & 0 deletions core/src/plugins/kubernetes/helm/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export async function execInHelmService(params: ExecInServiceParams<HelmModule>)
module,
devMode: false,
hotReload: false,
localMode: false,
log,
version: service.version,
})
Expand Down
8 changes: 4 additions & 4 deletions core/src/plugins/kubernetes/helm/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
*/

import { ModuleAndRuntimeActionHandlers } from "../../../types/plugin/plugin"
import { HelmModule, configureHelmModule } from "./config"
import { configureHelmModule, HelmModule } from "./config"
import { buildHelmModule } from "./build"
import { getServiceStatus } from "./status"
import { deployHelmService, deleteService } from "./deployment"
import { deleteService, deployHelmService } from "./deployment"
import { getTestResult } from "../test-results"
import { runHelmTask, runHelmModule } from "./run"
import { runHelmModule, runHelmTask } from "./run"
import { getServiceLogs } from "./logs"
import { testHelmModule } from "./test"
import { getPortForwardHandler } from "../port-forward"
Expand All @@ -22,11 +22,11 @@ import { KubernetesPluginContext } from "../config"
import { getModuleNamespace } from "../namespace"
import { join } from "path"
import { pathExists } from "fs-extra"
import chalk = require("chalk")
import { SuggestModulesParams, SuggestModulesResult } from "../../../types/plugin/module/suggestModules"
import { getReleaseName } from "./common"
import { hotReloadK8s } from "../hot-reload/hot-reload"
import { execInHelmService } from "./exec"
import chalk = require("chalk")

export const helmHandlers: Partial<ModuleAndRuntimeActionHandlers<HelmModule>> = {
build: buildHelmModule,
Expand Down
17 changes: 13 additions & 4 deletions core/src/plugins/kubernetes/helm/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@

import { HelmModule } from "./config"
import { PodRunner, runAndCopy } from "../run"
import { getChartResources, getBaseModule } from "./common"
import { getBaseModule, getChartResources } from "./common"
import {
getServiceResource,
getResourceContainer,
getResourcePodSpec,
getServiceResource,
getServiceResourceSpec,
makePodName,
prepareEnvVars,
} from "../util"
import { ConfigurationError } from "../../../exceptions"
import { KubernetesPluginContext } from "../config"
Expand All @@ -23,7 +24,6 @@ import { RunModuleParams } from "../../../types/plugin/module/runModule"
import { RunResult } from "../../../types/plugin/base"
import { RunTaskParams, RunTaskResult } from "../../../types/plugin/task/runTask"
import { uniqByName } from "../../../util/util"
import { prepareEnvVars } from "../util"
import { KubeApi } from "../api"
import { getModuleNamespaceStatus } from "../namespace"
import { DEFAULT_TASK_TIMEOUT } from "../../../constants"
Expand Down Expand Up @@ -60,7 +60,15 @@ export async function runHelmModule({
)
}

const manifests = await getChartResources({ ctx: k8sCtx, module, devMode: false, hotReload: false, log, version })
const manifests = await getChartResources({
ctx: k8sCtx,
module,
devMode: false,
hotReload: false,
localMode: false,
log,
version,
})
const target = await getServiceResource({
ctx: k8sCtx,
log,
Expand Down Expand Up @@ -130,6 +138,7 @@ export async function runHelmTask(params: RunTaskParams<HelmModule>): Promise<Ru
module,
devMode: false,
hotReload: false,
localMode: false,
log,
version: module.version.versionString,
})
Expand Down
Loading

0 comments on commit a7722b5

Please sign in to comment.