Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(k8s): local mode for helm modules #3033

Merged
merged 12 commits into from
Jul 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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