diff --git a/garden-service/src/plugins/kubernetes/config.ts b/garden-service/src/plugins/kubernetes/config.ts index a7dfa23eaa..7be88398cf 100644 --- a/garden-service/src/plugins/kubernetes/config.ts +++ b/garden-service/src/plugins/kubernetes/config.ts @@ -105,6 +105,7 @@ export interface KubernetesConfig extends ProviderConfig { gardenSystemNamespace: string tlsCertificates: IngressTlsCertificate[] certManager?: CertManagerConfig + clusterType?: "kind" | "minikube" | "microk8s" _systemServices: string[] } diff --git a/garden-service/src/plugins/kubernetes/container/build.ts b/garden-service/src/plugins/kubernetes/container/build.ts index 6f679ca09c..244e23cdbc 100644 --- a/garden-service/src/plugins/kubernetes/container/build.ts +++ b/garden-service/src/plugins/kubernetes/container/build.ts @@ -19,7 +19,7 @@ import { posix, resolve } from "path" import { KubeApi } from "../api" import { kubectl } from "../kubectl" import { LogEntry } from "../../../logger/log-entry" -import { KubernetesProvider, ContainerBuildMode, KubernetesPluginContext } from "../config" +import { KubernetesProvider, ContainerBuildMode, KubernetesPluginContext, KubernetesConfig } from "../config" import { PluginError } from "../../../exceptions" import { PodRunner } from "../run" import { getRegistryHostname, getKubernetesSystemVariables } from "../init" @@ -29,6 +29,7 @@ import { getPortForward } from "../port-forward" import { Writable } from "stream" import { LogLevel } from "../../../logger/log-node" import { exec, renderOutputStream } from "../../../util/util" +import { loadLocalImage } from "../local/kind" const dockerDaemonDeploymentName = "garden-docker-daemon" const dockerDaemonContainerName = "docker-daemon" @@ -96,6 +97,9 @@ const localBuild: BuildHandler = async (params) => { const buildResult = await buildContainerModule(params) if (!ctx.provider.config.deploymentRegistry) { + if ((ctx.provider.config as KubernetesConfig).clusterType === "kind") { + await loadLocalImage(buildResult, ctx.provider.config as KubernetesConfig) + } return buildResult } diff --git a/garden-service/src/plugins/kubernetes/local/config.ts b/garden-service/src/plugins/kubernetes/local/config.ts index a3352051c3..156dd79533 100644 --- a/garden-service/src/plugins/kubernetes/local/config.ts +++ b/garden-service/src/plugins/kubernetes/local/config.ts @@ -15,6 +15,7 @@ import { setMinikubeDockerEnv } from "./minikube" import { exec } from "../../../util/util" import { remove } from "lodash" import { getNfsStorageClass } from "../init" +import { isClusterKind } from "./kind" // TODO: split this into separate plugins to handle Docker for Mac and Minikube @@ -53,19 +54,19 @@ export async function configureProvider(params: ConfigureProviderParams + * + * 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 { exec } from "../../../util/util" +import { LogEntry } from "../../../logger/log-entry" +import { safeLoad } from "js-yaml" +import { BuildResult } from "../../../types/plugin/module/build" +import { KubernetesConfig, KubernetesProvider } from "../config" +import { RuntimeError } from "../../../exceptions" +import { KubeApi } from "../api" +import { KubernetesResource } from "../types" + +export async function loadLocalImage(buildResult: BuildResult, config: KubernetesConfig): Promise { + try { + const clusterName = await getClusterForContext(config.context) + if (clusterName !== null) { + await exec("kind", ["load", "docker-image", buildResult.details.identifier, `--name=${clusterName}`]) + } + } catch (err) { + throw new RuntimeError( + `An attempt to load image ${buildResult.details.identifier} into the kind cluster failed: ${err.message}`, + { err } + ) + } +} + +export async function isClusterKind(provider: KubernetesProvider, log: LogEntry): Promise { + return (await isKindInstalled(log)) && (await isKindContext(log, provider)) +} + +async function isKindInstalled(log: LogEntry): Promise { + try { + const kindVersion = (await exec("kind", ["version"])).stdout + log.debug(`Found kind with the following version details ${kindVersion}`) + return true + } catch (err) { + log.debug(`An attempt to get kind version failed with ${err}`) + } + + return false +} + +async function isKindContext(log: LogEntry, provider: KubernetesProvider): Promise { + const kubeApi = await KubeApi.factory(log, provider) + const manifest: KubernetesResource = { + apiVersion: "apps/v1", + kind: "DaemonSet", + metadata: { + name: "kindnet", + }, + } + try { + await kubeApi.readBySpec("kube-system", manifest, log) + return true + } catch (err) { + log.debug(`An attempt to get kindnet deamonset failed with ${err}`) + } + + return false +} + +async function getKindClusters(): Promise> { + try { + const clusters = (await exec("kind", ["get", "clusters"])).stdout + if (clusters) { + return clusters.split("\n") + } + return [] + } catch (err) {} + return [] +} + +async function getClusterForContext(context: string) { + for (let cluster of await getKindClusters()) { + if (await isContextAMatch(cluster, context)) { + return cluster + } + } + return null +} + +async function isContextAMatch(cluster: string, context: string): Promise { + try { + const kubeConfigString = (await exec("kind", ["get", "kubeconfig", `--name=${cluster}`])).stdout + const kubeConfig = safeLoad(kubeConfigString) + return kubeConfig["current-context"] === context + } catch (err) {} + return false +}