Skip to content

Commit

Permalink
feat(k8s): add exec to kubernetes and helm modules
Browse files Browse the repository at this point in the history
Implemented `execInService` handlers for the `kubernetes` and `helm`
module types.

These are very similar to the one used for `container` modules,
differing mostly in the way the target workload is located.
  • Loading branch information
thsig committed May 17, 2021
1 parent a8fa1a3 commit ea11bb6
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ import { CLUSTER_REGISTRY_DEPLOYMENT_NAME, inClusterRegistryHostname, dockerDaem
import { PluginError } from "../../../exceptions"
import { apply } from "../kubectl"
import { waitForResources } from "../status/status"
import { execInWorkload } from "../container/exec"
import { dedent, deline } from "../../../util/string"
import { sharedBuildSyncDeploymentName } from "../container/build/common"
import { getDeploymentPod } from "../util"
import { execInWorkload, getDeploymentPod } from "../util"
import { getSystemNamespace } from "../namespace"
import { PluginContext } from "../../../plugin-context"
import { PodRunner } from "../run"
Expand Down
57 changes: 2 additions & 55 deletions core/src/plugins/kubernetes/container/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,11 @@
import { includes } from "lodash"
import { DeploymentError } from "../../../exceptions"
import { ContainerModule } from "../../container/config"
import { KubeApi } from "../api"
import { getAppNamespace } from "../namespace"
import { getContainerServiceStatus } from "./status"
import { KubernetesPluginContext, KubernetesProvider } from "../config"
import { KubernetesPluginContext } from "../config"
import { execInWorkload } from "../util"
import { ExecInServiceParams } from "../../../types/plugin/service/execInService"
import { LogEntry } from "../../../logger/log-entry"
import { getCurrentWorkloadPods } from "../util"
import { KubernetesWorkload } from "../types"
import { PluginContext } from "../../../plugin-context"
import { PodRunner } from "../run"

export async function execInService(params: ExecInServiceParams<ContainerModule>) {
const { ctx, log, service, command, interactive } = params
Expand Down Expand Up @@ -46,51 +41,3 @@ export async function execInService(params: ExecInServiceParams<ContainerModule>

return execInWorkload({ ctx, provider, log, namespace, workload: status.detail.workload, command, interactive })
}

export async function execInWorkload({
ctx,
provider,
log,
namespace,
workload,
command,
interactive,
}: {
ctx: PluginContext
provider: KubernetesProvider
log: LogEntry
namespace: string
workload: KubernetesWorkload
command: string[]
interactive: boolean
}) {
const api = await KubeApi.factory(log, ctx, provider)
const pods = await getCurrentWorkloadPods(api, namespace, workload)

const pod = pods[0]

if (!pod) {
// This should not happen because of the prior status check, but checking to be sure
throw new DeploymentError(`Could not find running pod for ${workload.kind}/${workload.metadata.name}`, {
workload,
})
}

const runner = new PodRunner({
api,
ctx,
provider,
namespace,
pod,
})

const res = await runner.exec({
log,
command,
timeoutSec: 999999,
tty: interactive,
buffer: true,
})

return { code: res.exitCode, output: res.log }
}
65 changes: 65 additions & 0 deletions core/src/plugins/kubernetes/helm/exec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (C) 2018-2021 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 { includes } from "lodash"
import { DeploymentError } from "../../../exceptions"
import { getAppNamespace } from "../namespace"
import { KubernetesPluginContext } from "../config"
import { execInWorkload, findServiceResource, getServiceResourceSpec } from "../util"
import { ExecInServiceParams } from "../../../types/plugin/service/execInService"
import { HelmModule } from "./config"
import { getServiceStatus } from "./status"
import { getBaseModule, getChartResources } from "./common"

export async function execInHelmService(params: ExecInServiceParams<HelmModule>) {
const { ctx, log, service, command, interactive } = params
const module = service.module
const k8sCtx = <KubernetesPluginContext>ctx
const provider = k8sCtx.provider
const status = await getServiceStatus({
...params,
// The runtime context doesn't matter here. We're just checking if the service is running.
runtimeContext: {
envVars: {},
dependencies: [],
},
devMode: false,
hotReload: false,
})
const namespace = await getAppNamespace(k8sCtx, log, k8sCtx.provider)

const baseModule = getBaseModule(module)
const serviceResourceSpec = getServiceResourceSpec(module, baseModule)
const manifests = await getChartResources({
ctx: k8sCtx,
module,
devMode: false,
hotReload: false,
log,
version: service.version,
})

const serviceResource = await findServiceResource({
ctx,
log,
module,
baseModule,
manifests,
resourceSpec: serviceResourceSpec,
})

// TODO: this check should probably live outside of the plugin
if (!serviceResource || !includes(["ready", "outdated"], status.state)) {
throw new DeploymentError(`Service ${service.name} is not running`, {
name: service.name,
state: status.state,
})
}

return execInWorkload({ ctx, provider, log, namespace, workload: serviceResource, command, interactive })
}
3 changes: 2 additions & 1 deletion core/src/plugins/kubernetes/helm/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ 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"

export const helmHandlers: Partial<ModuleAndRuntimeActionHandlers<HelmModule>> = {
build: buildHelmModule,
Expand All @@ -37,7 +38,7 @@ export const helmHandlers: Partial<ModuleAndRuntimeActionHandlers<HelmModule>> =
},
}
},
// TODO: add execInService handler
execInService: execInHelmService,
deleteService,
deployService: deployHelmService,
// Use the same getPortForward handler as container and kubernetes-module, except set the namespace
Expand Down
3 changes: 1 addition & 2 deletions core/src/plugins/kubernetes/hot-reload/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import { deline, gardenAnnotationKey } from "../../../util/string"
import { set, flatten } from "lodash"
import { GardenService } from "../../../types/service"
import { LogEntry } from "../../../logger/log-entry"
import { getResourceContainer, getServiceResourceSpec } from "../util"
import { execInWorkload } from "../container/exec"
import { execInWorkload, getResourceContainer, getServiceResourceSpec } from "../util"
import { getPortForward, killPortForward } from "../port-forward"
import { rsyncPort, buildSyncVolumeName, rsyncPortName } from "../constants"
import { KubernetesPluginContext } from "../config"
Expand Down
54 changes: 54 additions & 0 deletions core/src/plugins/kubernetes/kubernetes-module/exec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2018-2021 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 { includes } from "lodash"
import { DeploymentError } from "../../../exceptions"
import { KubernetesModule } from "./config"
import { getAppNamespace } from "../namespace"
import { KubernetesPluginContext } from "../config"
import { execInWorkload, findServiceResource, getServiceResourceSpec } from "../util"
import { getKubernetesServiceStatus } from "./handlers"
import { ExecInServiceParams } from "../../../types/plugin/service/execInService"

export async function execInKubernetesService(params: ExecInServiceParams<KubernetesModule>) {
const { ctx, log, service, command, interactive } = params
const module = service.module
const k8sCtx = <KubernetesPluginContext>ctx
const provider = k8sCtx.provider
const status = await getKubernetesServiceStatus({
...params,
// The runtime context doesn't matter here. We're just checking if the service is running.
runtimeContext: {
envVars: {},
dependencies: [],
},
devMode: false,
hotReload: false,
})
const namespace = await getAppNamespace(k8sCtx, log, k8sCtx.provider)

const serviceResourceSpec = getServiceResourceSpec(module, undefined)
const serviceResource = await findServiceResource({
ctx,
log,
module,
baseModule: undefined,
manifests: status.detail.remoteResources,
resourceSpec: serviceResourceSpec,
})

// TODO: this check should probably live outside of the plugin
if (!serviceResource || !includes(["ready", "outdated"], status.state)) {
throw new DeploymentError(`Service ${service.name} is not running`, {
name: service.name,
state: status.state,
})
}

return execInWorkload({ ctx, provider, log, namespace, workload: serviceResource, command, interactive })
}
54 changes: 28 additions & 26 deletions core/src/plugins/kubernetes/kubernetes-module/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,48 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { V1DaemonSet, V1Deployment, V1StatefulSet } from "@kubernetes/client-node"
import Bluebird from "bluebird"
import { cloneDeep, partition, set, uniq } from "lodash"
import chalk from "chalk"

import { KubernetesModule, configureKubernetesModule, KubernetesService } from "./config"
import { KubernetesPluginContext } from "../config"
import { BaseResource, KubernetesResource, KubernetesServerResource } from "../types"
import { ServiceStatus } from "../../../types/service"
import { compareDeployedResources, waitForResources } from "../status/status"
import { KubeApi } from "../api"
import { ModuleAndRuntimeActionHandlers } from "../../../types/plugin/plugin"
import { streamK8sLogs } from "../logs"
import { deleteObjectsBySelector, apply } from "../kubectl"
import { cloneDeep, partition, set, uniq } from "lodash"
import { LogEntry } from "../../../logger/log-entry"
import { PluginContext } from "../../../plugin-context"
import { NamespaceStatus } from "../../../types/plugin/base"
import { BuildModuleParams, BuildResult } from "../../../types/plugin/module/build"
import { GetServiceStatusParams } from "../../../types/plugin/service/getServiceStatus"
import { DeployServiceParams } from "../../../types/plugin/service/deployService"
import { ModuleAndRuntimeActionHandlers } from "../../../types/plugin/plugin"
import { DeleteServiceParams } from "../../../types/plugin/service/deleteService"
import { DeployServiceParams } from "../../../types/plugin/service/deployService"
import { GetServiceLogsParams } from "../../../types/plugin/service/getServiceLogs"
import { GetServiceStatusParams } from "../../../types/plugin/service/getServiceStatus"
import { ServiceStatus } from "../../../types/service"
import { gardenAnnotationKey } from "../../../util/string"
import { KubeApi } from "../api"
import { KubernetesPluginContext } from "../config"
import { configureDevMode, startDevModeSync } from "../dev-mode"
import { HelmService } from "../helm/config"
import { configureHotReload, getHotReloadContainerName, getHotReloadSpec } from "../hot-reload/helpers"
import { HotReloadableResource, hotReloadK8s } from "../hot-reload/hot-reload"
import { apply, deleteObjectsBySelector } from "../kubectl"
import { streamK8sLogs } from "../logs"
import { getModuleNamespace, getModuleNamespaceStatus } from "../namespace"
import { getForwardablePorts, getPortForwardHandler, killPortForwards } from "../port-forward"
import { getManifests, readManifests, gardenNamespaceAnnotationValue } from "./common"
import { testKubernetesModule } from "./test"
import { runKubernetesTask } from "./run"
import { getTestResult } from "../test-results"
import { compareDeployedResources, waitForResources } from "../status/status"
import { getTaskResult } from "../task-results"
import { getModuleNamespace, getModuleNamespaceStatus } from "../namespace"
import { HotReloadableResource, hotReloadK8s } from "../hot-reload/hot-reload"
import { getTestResult } from "../test-results"
import { BaseResource, KubernetesResource, KubernetesServerResource } from "../types"
import { findServiceResource, getServiceResourceSpec } from "../util"
import { getHotReloadSpec, configureHotReload, getHotReloadContainerName } from "../hot-reload/helpers"
import { LogEntry } from "../../../logger/log-entry"
import { PluginContext } from "../../../plugin-context"
import { V1Deployment, V1DaemonSet, V1StatefulSet } from "@kubernetes/client-node"
import { HelmService } from "../helm/config"
import { configureDevMode, startDevModeSync } from "../dev-mode"
import chalk from "chalk"
import { NamespaceStatus } from "../../../types/plugin/base"
import { gardenNamespaceAnnotationValue, getManifests, readManifests } from "./common"
import { configureKubernetesModule, KubernetesModule, KubernetesService } from "./config"
import { execInKubernetesService } from "./exec"
import { runKubernetesTask } from "./run"
import { testKubernetesModule } from "./test"

export const kubernetesHandlers: Partial<ModuleAndRuntimeActionHandlers<KubernetesModule>> = {
build,
configure: configureKubernetesModule,
deleteService,
execInService: execInKubernetesService,
deployService: deployKubernetesService,
getPortForward: getPortForwardHandler,
getServiceLogs,
Expand Down
51 changes: 50 additions & 1 deletion core/src/plugins/kubernetes/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { KubeApi, KubernetesError } from "./api"
import { gardenAnnotationKey, base64, deline, stableStringify } from "../../util/string"
import { MAX_CONFIGMAP_DATA_SIZE, systemDockerAuthSecretName } from "./constants"
import { ContainerEnvVars } from "../container/config"
import { ConfigurationError, PluginError } from "../../exceptions"
import { ConfigurationError, DeploymentError, PluginError } from "../../exceptions"
import { ServiceResourceSpec, KubernetesProvider } from "./config"
import { LogEntry } from "../../logger/log-entry"
import { PluginContext } from "../../plugin-context"
Expand All @@ -28,6 +28,7 @@ import { KubernetesModule } from "./kubernetes-module/config"
import { getChartPath, renderHelmTemplateString } from "./helm/common"
import { HotReloadableResource } from "./hot-reload/hot-reload"
import { ProviderMap } from "../../config/provider"
import { PodRunner } from "./run"

export const skopeoImage = "gardendev/skopeo:1.41.0-2"

Expand Down Expand Up @@ -194,6 +195,54 @@ export async function getPods(
)
}

export async function execInWorkload({
ctx,
provider,
log,
namespace,
workload,
command,
interactive,
}: {
ctx: PluginContext
provider: KubernetesProvider
log: LogEntry
namespace: string
workload: KubernetesWorkload
command: string[]
interactive: boolean
}) {
const api = await KubeApi.factory(log, ctx, provider)
const pods = await getCurrentWorkloadPods(api, namespace, workload)

const pod = pods[0]

if (!pod) {
// This should not happen because of the prior status check, but checking to be sure
throw new DeploymentError(`Could not find running pod for ${workload.kind}/${workload.metadata.name}`, {
workload,
})
}

const runner = new PodRunner({
api,
ctx,
provider,
namespace,
pod,
})

const res = await runner.exec({
log,
command,
timeoutSec: 999999,
tty: interactive,
buffer: true,
})

return { code: res.exitCode, output: res.log }
}

/**
* Returns the API group of the resource. Returns empty string for "v1" objects.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import { getServiceStatuses } from "../../../../../../src/tasks/base"
import { expectError, grouped } from "../../../../../helpers"
import stripAnsi = require("strip-ansi")
import { gardenAnnotationKey } from "../../../../../../src/util/string"
import { execInWorkload } from "../../../../../../src/plugins/kubernetes/container/exec"
import { getContainerServiceStatus } from "../../../../../../src/plugins/kubernetes/container/status"
import { sleep } from "../../../../../../src/util/util"
import { pathExists, readFile, remove, writeFile } from "fs-extra"
import { execInWorkload } from "../../../../../../src/plugins/kubernetes/util"

describe("kubernetes container deployment handlers", () => {
let garden: Garden
Expand Down

0 comments on commit ea11bb6

Please sign in to comment.