From 4787e513e6657424b17e08f32b4a7ce29066eccd Mon Sep 17 00:00:00 2001 From: Thorarinn Sigurdsson Date: Wed, 13 Mar 2019 12:03:32 +0100 Subject: [PATCH] refactor: split provider from KubeApi This is to facilitate use of the KubeApi class e.g. in integration tests, where some k8s operations need to be run outside of a Garden project. --- garden-service/src/config/project.ts | 4 +- garden-service/src/plugin-context.ts | 5 +- garden-service/src/plugins/kubernetes/api.ts | 5 +- .../kubernetes/container/deployment.ts | 39 ++++++---- .../plugins/kubernetes/container/handlers.ts | 2 +- .../plugins/kubernetes/container/ingress.ts | 38 +++++----- .../src/plugins/kubernetes/container/logs.ts | 6 +- .../src/plugins/kubernetes/container/run.ts | 12 ++-- .../plugins/kubernetes/container/status.ts | 13 ++-- .../src/plugins/kubernetes/helm/build.ts | 4 +- .../src/plugins/kubernetes/helm/common.ts | 7 +- .../src/plugins/kubernetes/helm/deployment.ts | 12 ++-- .../src/plugins/kubernetes/helm/logs.ts | 6 +- .../src/plugins/kubernetes/helm/run.ts | 15 ++-- .../src/plugins/kubernetes/helm/status.ts | 17 +++-- .../src/plugins/kubernetes/helm/test.ts | 12 ++-- .../src/plugins/kubernetes/helm/tiller.ts | 2 +- .../src/plugins/kubernetes/hot-reload.ts | 6 +- garden-service/src/plugins/kubernetes/init.ts | 72 +++++++++++-------- .../src/plugins/kubernetes/kubernetes.ts | 2 + .../src/plugins/kubernetes/namespace.ts | 2 +- .../src/plugins/kubernetes/secrets.ts | 17 +++-- .../src/plugins/kubernetes/status.ts | 9 +-- garden-service/src/plugins/kubernetes/test.ts | 11 +-- .../src/plugins/openfaas/openfaas.ts | 67 +++++++++-------- .../plugins/kubernetes/container/ingress.ts | 71 +++++++++++------- 26 files changed, 272 insertions(+), 184 deletions(-) diff --git a/garden-service/src/config/project.ts b/garden-service/src/config/project.ts index 283bdaae94..3caf086936 100644 --- a/garden-service/src/config/project.ts +++ b/garden-service/src/config/project.ts @@ -31,7 +31,7 @@ export const providerConfigBaseSchema = Joi.object() .unknown(true) .meta({ extendable: true }) -export interface Provider { +export interface Provider { name: string config: T } @@ -151,5 +151,5 @@ export const projectSchema = Joi.object() // this is used for default handlers in the action handler export const defaultProvider: Provider = { name: "_default", - config: {}, + config: { name: "_default" }, } diff --git a/garden-service/src/plugin-context.ts b/garden-service/src/plugin-context.ts index b830949aa9..03deb1a4ec 100644 --- a/garden-service/src/plugin-context.ts +++ b/garden-service/src/plugin-context.ts @@ -15,6 +15,7 @@ import { projectSourcesSchema, environmentSchema, providerConfigBaseSchema, + ProviderConfig, } from "./config/project" import { joiIdentifier, joiIdentifierMap } from "./config/common" import { PluginError } from "./exceptions" @@ -37,8 +38,8 @@ const providerSchema = Joi.object() config: providerConfigBaseSchema, }) -export interface PluginContext extends WrappedFromGarden { - provider: Provider +export interface PluginContext extends WrappedFromGarden { + provider: Provider providers: { [name: string]: Provider } } diff --git a/garden-service/src/plugins/kubernetes/api.ts b/garden-service/src/plugins/kubernetes/api.ts index 222f679ba8..7cb3278dcc 100644 --- a/garden-service/src/plugins/kubernetes/api.ts +++ b/garden-service/src/plugins/kubernetes/api.ts @@ -23,7 +23,6 @@ import { safeLoad } from "js-yaml" import { zip, omitBy, isObject } from "lodash" import { GardenBaseError } from "../../exceptions" import { homedir } from "os" -import { KubernetesProvider } from "./kubernetes" import { KubernetesResource } from "./types" import * as dedent from "dedent" @@ -72,7 +71,6 @@ export class KubernetesError extends GardenBaseError { } export class KubeApi { - public context: string private config: KubeConfig public apiExtensions: Apiextensions_v1beta1Api @@ -82,8 +80,7 @@ export class KubeApi { public policy: Policy_v1beta1Api public rbac: RbacAuthorization_v1Api - constructor(public provider: KubernetesProvider) { - this.context = provider.config.context + constructor(public context: string) { this.config = getConfig(this.context) for (const [name, cls] of Object.entries(apiTypes)) { diff --git a/garden-service/src/plugins/kubernetes/container/deployment.ts b/garden-service/src/plugins/kubernetes/container/deployment.ts index 10a261778c..a351fdb6e1 100644 --- a/garden-service/src/plugins/kubernetes/container/deployment.ts +++ b/garden-service/src/plugins/kubernetes/container/deployment.ts @@ -18,12 +18,13 @@ import { getAppNamespace } from "../namespace" import { PluginContext } from "../../../plugin-context" import { GARDEN_ANNOTATION_KEYS_VERSION } from "../../../constants" import { KubeApi } from "../api" -import { KubernetesProvider } from "../kubernetes" +import { KubernetesProvider, KubernetesPluginContext } from "../kubernetes" import { configureHotReload } from "../hot-reload" import { KubernetesResource, KubeEnvVar } from "../types" import { ConfigurationError } from "../../../exceptions" import { getContainerServiceStatus } from "./status" import { containerHelpers } from "../../container/helpers" +import { LogEntry } from "../../../logger/log-entry" export const DEFAULT_CPU_REQUEST = "10m" export const DEFAULT_CPU_LIMIT = "500m" @@ -32,14 +33,21 @@ export const DEFAULT_MEMORY_LIMIT = "512Mi" export async function deployContainerService(params: DeployServiceParams): Promise { const { ctx, service, runtimeContext, force, log, hotReload } = params + const k8sCtx = ctx - const namespace = await getAppNamespace(ctx, ctx.provider) - const objects = await createContainerObjects(ctx, service, runtimeContext, hotReload) + const namespace = await getAppNamespace(k8sCtx, k8sCtx.provider) + const objects = await createContainerObjects(k8sCtx, service, runtimeContext, hotReload) // TODO: use Helm instead of kubectl apply const pruneSelector = "service=" + service.name - await applyMany(ctx.provider.config.context, objects, { force, namespace, pruneSelector }) - await waitForResources({ ctx, provider: ctx.provider, serviceName: service.name, resources: objects, log }) + await applyMany(k8sCtx.provider.config.context, objects, { force, namespace, pruneSelector }) + await waitForResources({ + ctx: k8sCtx, + provider: k8sCtx.provider, + serviceName: service.name, + resources: objects, + log, + }) return getContainerServiceStatus(params) } @@ -50,11 +58,13 @@ export async function createContainerObjects( runtimeContext: RuntimeContext, enableHotReload: boolean, ) { + const k8sCtx = ctx const version = service.module.version - const namespace = await getAppNamespace(ctx, ctx.provider) - const api = new KubeApi(ctx.provider) - const ingresses = await createIngressResources(api, namespace, service) - const deployment = await createDeployment(ctx.provider, service, runtimeContext, namespace, enableHotReload) + const provider = k8sCtx.provider + const namespace = await getAppNamespace(k8sCtx, provider) + const api = new KubeApi(provider.config.context) + const ingresses = await createIngressResources(api, provider, namespace, service) + const deployment = await createDeployment(provider, service, runtimeContext, namespace, enableHotReload) const kubeservices = await createServiceResources(service, namespace) const objects = [deployment, ...kubeservices, ...ingresses] @@ -367,11 +377,12 @@ export function rsyncTargetPath(path: string) { export async function deleteService(params: DeleteServiceParams): Promise { const { ctx, log, service } = params - const namespace = await getAppNamespace(ctx, ctx.provider) - const provider = ctx.provider + const k8sCtx = ctx + const namespace = await getAppNamespace(k8sCtx, k8sCtx.provider) + const provider = k8sCtx.provider const context = provider.config.context - await deleteContainerDeployment({ namespace, provider, serviceName: service.name, log }) + await deleteContainerDeployment({ namespace, context, serviceName: service.name, log }) await deleteObjectsByLabel({ context, namespace, @@ -385,11 +396,11 @@ export async function deleteService(params: DeleteServiceParams): Promise{}) diff --git a/garden-service/src/plugins/kubernetes/container/handlers.ts b/garden-service/src/plugins/kubernetes/container/handlers.ts index a946854750..e0eb5654c7 100644 --- a/garden-service/src/plugins/kubernetes/container/handlers.ts +++ b/garden-service/src/plugins/kubernetes/container/handlers.ts @@ -23,7 +23,7 @@ async function configure(params: ConfigureModuleParams) { const config = await configureContainerModule(params) // validate ingress specs - const provider: KubernetesProvider = params.ctx.provider + const provider = params.ctx.provider for (const serviceConfig of config.serviceConfigs) { for (const ingressSpec of serviceConfig.spec.ingresses) { diff --git a/garden-service/src/plugins/kubernetes/container/ingress.ts b/garden-service/src/plugins/kubernetes/container/ingress.ts index dd7d5eefd7..7e73e365c4 100644 --- a/garden-service/src/plugins/kubernetes/container/ingress.ts +++ b/garden-service/src/plugins/kubernetes/container/ingress.ts @@ -11,7 +11,7 @@ import { certpem } from "certpem" import { find, extend } from "lodash" import { findByName } from "../../../util/util" import { ContainerService, ContainerIngressSpec } from "../../container/config" -import { IngressTlsCertificate } from "../kubernetes" +import { IngressTlsCertificate, KubernetesProvider } from "../kubernetes" import { ServiceIngress, ServiceProtocol } from "../../../types/service" import { KubeApi } from "../api" import { ConfigurationError, PluginError } from "../../../exceptions" @@ -24,12 +24,14 @@ interface ServiceIngressWithCert extends ServiceIngress { const certificateHostnames: { [name: string]: string[] } = {} -export async function createIngressResources(api: KubeApi, namespace: string, service: ContainerService) { +export async function createIngressResources( + api: KubeApi, provider: KubernetesProvider, namespace: string, service: ContainerService, +) { if (service.spec.ingresses.length === 0) { return [] } - const allIngresses = await getIngressesWithCert(service, api) + const allIngresses = await getIngressesWithCert(service, api, provider) return Bluebird.map(allIngresses, async (ingress) => { const rules = [{ @@ -51,8 +53,8 @@ export async function createIngressResources(api: KubeApi, namespace: string, se "ingress.kubernetes.io/force-ssl-redirect": !!cert + "", } - if (api.provider.config.ingressClass) { - annotations["kubernetes.io/ingress.class"] = api.provider.config.ingressClass + if (provider.config.ingressClass) { + annotations["kubernetes.io/ingress.class"] = provider.config.ingressClass } extend(annotations, ingress.spec.annotations) @@ -82,19 +84,19 @@ export async function createIngressResources(api: KubeApi, namespace: string, se } async function getIngress( - service: ContainerService, api: KubeApi, spec: ContainerIngressSpec, + service: ContainerService, api: KubeApi, provider: KubernetesProvider, spec: ContainerIngressSpec, ): Promise { - const hostname = spec.hostname || api.provider.config.defaultHostname + const hostname = spec.hostname || provider.config.defaultHostname if (!hostname) { // this should be caught when parsing the module throw new PluginError(`Missing hostname in ingress spec`, { serviceSpec: service.spec, ingressSpec: spec }) } - const certificate = await pickCertificate(service, api, hostname) + const certificate = await pickCertificate(service, api, provider, hostname) // TODO: support other protocols const protocol: ServiceProtocol = !!certificate ? "https" : "http" - const port = !!certificate ? api.provider.config.ingressHttpsPort : api.provider.config.ingressHttpPort + const port = !!certificate ? provider.config.ingressHttpsPort : provider.config.ingressHttpPort return { ...spec, @@ -107,12 +109,16 @@ async function getIngress( } } -async function getIngressesWithCert(service: ContainerService, api: KubeApi): Promise { - return Bluebird.map(service.spec.ingresses, spec => getIngress(service, api, spec)) +async function getIngressesWithCert( + service: ContainerService, api: KubeApi, provider: KubernetesProvider, +): Promise { + return Bluebird.map(service.spec.ingresses, spec => getIngress(service, api, provider, spec)) } -export async function getIngresses(service: ContainerService, api: KubeApi): Promise { - return (await getIngressesWithCert(service, api)) +export async function getIngresses( + service: ContainerService, api: KubeApi, provider: KubernetesProvider, +): Promise { + return (await getIngressesWithCert(service, api, provider)) .map(ingress => ({ hostname: ingress.hostname, path: ingress.path, @@ -188,9 +194,9 @@ async function getCertificateHostnames(api: KubeApi, cert: IngressTlsCertificate } async function pickCertificate( - service: ContainerService, api: KubeApi, hostname: string, + service: ContainerService, api: KubeApi, provider: KubernetesProvider, hostname: string, ): Promise { - for (const cert of api.provider.config.tlsCertificates) { + for (const cert of provider.config.tlsCertificates) { const certHostnames = await getCertificateHostnames(api, cert) for (const certHostname of certHostnames) { @@ -203,7 +209,7 @@ async function pickCertificate( } } - if (api.provider.config.forceSsl) { + if (provider.config.forceSsl) { throw new ConfigurationError( `Could not find certificate for hostname '${hostname}' ` + `configured on service '${service.name}' and forceSsl flag is set.`, diff --git a/garden-service/src/plugins/kubernetes/container/logs.ts b/garden-service/src/plugins/kubernetes/container/logs.ts index 25e40c3e29..5fce876b09 100644 --- a/garden-service/src/plugins/kubernetes/container/logs.ts +++ b/garden-service/src/plugins/kubernetes/container/logs.ts @@ -10,11 +10,13 @@ import { GetServiceLogsParams } from "../../../types/plugin/params" import { ContainerModule } from "../../container/config" import { getAppNamespace } from "../namespace" import { getKubernetesLogs } from "../logs" +import { KubernetesPluginContext } from "../kubernetes" export async function getServiceLogs(params: GetServiceLogsParams) { const { ctx, service } = params - const context = ctx.provider.config.context - const namespace = await getAppNamespace(ctx, ctx.provider) + const k8sCtx = ctx + const context = k8sCtx.provider.config.context + const namespace = await getAppNamespace(k8sCtx, k8sCtx.provider) const selector = `service=${service.name}` return getKubernetesLogs({ ...params, context, namespace, selector }) diff --git a/garden-service/src/plugins/kubernetes/container/run.ts b/garden-service/src/plugins/kubernetes/container/run.ts index fa6f3c8ddb..c2c1d42f28 100644 --- a/garden-service/src/plugins/kubernetes/container/run.ts +++ b/garden-service/src/plugins/kubernetes/container/run.ts @@ -22,12 +22,15 @@ import { kubectl } from "../kubectl" import { getContainerServiceStatus } from "./status" import { runPod } from "../run" import { containerHelpers } from "../../container/helpers" +import { KubernetesPluginContext } from "../kubernetes" export async function execInService(params: ExecInServiceParams) { const { ctx, service, command, interactive } = params - const api = new KubeApi(ctx.provider) + const k8sCtx = ctx + const provider = k8sCtx.provider + const api = new KubeApi(provider.config.context) const status = await getContainerServiceStatus({ ...params, hotReload: false }) - const namespace = await getAppNamespace(ctx, ctx.provider) + const namespace = await getAppNamespace(k8sCtx, k8sCtx.provider) // TODO: this check should probably live outside of the plugin if (!includes(["ready", "outdated"], status.state)) { @@ -78,8 +81,9 @@ export async function runContainerModule( ctx, module, command, ignoreError = true, interactive, runtimeContext, timeout, }: RunModuleParams, ): Promise { - const context = ctx.provider.config.context - const namespace = await getAppNamespace(ctx, ctx.provider) + const k8sCtx = ctx + const context = k8sCtx.provider.config.context + const namespace = await getAppNamespace(k8sCtx, k8sCtx.provider) const image = await containerHelpers.getLocalImageId(module) return runPod({ diff --git a/garden-service/src/plugins/kubernetes/container/status.ts b/garden-service/src/plugins/kubernetes/container/status.ts index 64e381ff60..bdd1396a49 100644 --- a/garden-service/src/plugins/kubernetes/container/status.ts +++ b/garden-service/src/plugins/kubernetes/container/status.ts @@ -19,20 +19,23 @@ import { KubeApi } from "../api" import { compareDeployedObjects } from "../status" import { getIngresses } from "./ingress" import { getAppNamespace } from "../namespace" +import { KubernetesPluginContext } from "../kubernetes" export async function getContainerServiceStatus( { ctx, module, service, runtimeContext, log, hotReload }: GetServiceStatusParams, ): Promise { + const k8sCtx = ctx // TODO: hash and compare all the configuration files (otherwise internal changes don't get deployed) const version = module.version - const api = new KubeApi(ctx.provider) - const namespace = await getAppNamespace(ctx, ctx.provider) + const provider = k8sCtx.provider + const api = new KubeApi(provider.config.context) + const namespace = await getAppNamespace(k8sCtx, provider) // FIXME: [objects, matched] and ingresses can be run in parallel - const objects = await createContainerObjects(ctx, service, runtimeContext, hotReload) - const { state, remoteObjects } = await compareDeployedObjects(ctx, api, namespace, objects, log) - const ingresses = await getIngresses(service, api) + const objects = await createContainerObjects(k8sCtx, service, runtimeContext, hotReload) + const { state, remoteObjects } = await compareDeployedObjects(k8sCtx, api, namespace, objects, log) + const ingresses = await getIngresses(service, api, provider) return { ingresses, diff --git a/garden-service/src/plugins/kubernetes/helm/build.ts b/garden-service/src/plugins/kubernetes/helm/build.ts index eeaee2b315..2826dd2253 100644 --- a/garden-service/src/plugins/kubernetes/helm/build.ts +++ b/garden-service/src/plugins/kubernetes/helm/build.ts @@ -16,9 +16,11 @@ import { dumpYaml } from "../../../util/util" import { LogEntry } from "../../../logger/log-entry" import { getNamespace } from "../namespace" import { apply as jsonMerge } from "json-merge-patch" +import { KubernetesPluginContext } from "../kubernetes" export async function buildHelmModule({ ctx, module, log }: BuildModuleParams): Promise { - const namespace = await getNamespace({ ctx, provider: ctx.provider, skipCreate: true }) + const k8sCtx = ctx + const namespace = await getNamespace({ ctx: k8sCtx, provider: k8sCtx.provider, skipCreate: true }) const context = ctx.provider.config.context const baseModule = getBaseModule(module) diff --git a/garden-service/src/plugins/kubernetes/helm/common.ts b/garden-service/src/plugins/kubernetes/helm/common.ts index 6222663784..5dc8a43efc 100644 --- a/garden-service/src/plugins/kubernetes/helm/common.ts +++ b/garden-service/src/plugins/kubernetes/helm/common.ts @@ -25,6 +25,7 @@ import { Module } from "../../../types/module" import { findByName } from "../../../util/util" import { deline } from "../../../util/string" import { getAnnotation } from "../util" +import { KubernetesPluginContext } from "../kubernetes" /** * Returns true if the specified Helm module contains a template (as opposed to just referencing a remote template). @@ -38,9 +39,10 @@ export async function containsSource(config: HelmModuleConfig) { * Render the template in the specified Helm module (locally), and return all the resources in the chart. */ export async function getChartResources(ctx: PluginContext, module: Module, log: LogEntry) { + const k8sCtx = ctx const chartPath = await getChartPath(module) const valuesPath = getValuesPath(chartPath) - const namespace = await getNamespace({ ctx, provider: ctx.provider, skipCreate: true }) + const namespace = await getNamespace({ ctx: k8sCtx, provider: k8sCtx.provider, skipCreate: true }) const context = ctx.provider.config.context const releaseName = getReleaseName(module) @@ -266,7 +268,8 @@ async function renderHelmTemplateString( ): Promise { const tempFilePath = join(chartPath, "templates", cryptoRandomString(16)) const valuesPath = getValuesPath(chartPath) - const namespace = await getNamespace({ ctx, provider: ctx.provider, skipCreate: true }) + const k8sCtx = ctx + const namespace = await getNamespace({ ctx: k8sCtx, provider: k8sCtx.provider, skipCreate: true }) const releaseName = getReleaseName(module) const context = ctx.provider.config.context diff --git a/garden-service/src/plugins/kubernetes/helm/deployment.ts b/garden-service/src/plugins/kubernetes/helm/deployment.ts index 809013b475..c0bb33e3f8 100644 --- a/garden-service/src/plugins/kubernetes/helm/deployment.ts +++ b/garden-service/src/plugins/kubernetes/helm/deployment.ts @@ -23,7 +23,7 @@ import { import { getReleaseStatus, getServiceStatus } from "./status" import { configureHotReload, HotReloadableResource } from "../hot-reload" import { apply } from "../kubectl" -import { KubernetesProvider } from "../kubernetes" +import { KubernetesPluginContext } from "../kubernetes" import { ContainerHotReloadSpec } from "../../container/config" import { getHotReloadSpec } from "./hot-reload" @@ -41,10 +41,11 @@ export async function deployService( hotReloadSpec = getHotReloadSpec(service) } - const provider: KubernetesProvider = ctx.provider + const k8sCtx = ctx + const provider = k8sCtx.provider const chartPath = await getChartPath(module) const valuesPath = getValuesPath(chartPath) - const namespace = await getAppNamespace(ctx, provider) + const namespace = await getAppNamespace(k8sCtx, provider) const context = provider.config.context const releaseName = getReleaseName(module) @@ -101,8 +102,9 @@ export async function deployService( export async function deleteService(params: DeleteServiceParams): Promise { const { ctx, log, module } = params - const namespace = await getAppNamespace(ctx, ctx.provider) - const context = ctx.provider.config.context + const k8sCtx = ctx + const namespace = await getAppNamespace(k8sCtx, k8sCtx.provider) + const context = k8sCtx.provider.config.context const releaseName = getReleaseName(module) await helm(namespace, context, log, "delete", "--purge", releaseName) diff --git a/garden-service/src/plugins/kubernetes/helm/logs.ts b/garden-service/src/plugins/kubernetes/helm/logs.ts index 9b37bf1c8a..728bfcebc6 100644 --- a/garden-service/src/plugins/kubernetes/helm/logs.ts +++ b/garden-service/src/plugins/kubernetes/helm/logs.ts @@ -10,11 +10,13 @@ import { GetServiceLogsParams } from "../../../types/plugin/params" import { getAppNamespace } from "../namespace" import { getKubernetesLogs } from "../logs" import { HelmModule } from "./config" +import { KubernetesPluginContext } from "../kubernetes" export async function getServiceLogs(params: GetServiceLogsParams) { const { ctx, service } = params - const context = ctx.provider.config.context - const namespace = await getAppNamespace(ctx, ctx.provider) + const k8sCtx = ctx + const context = k8sCtx.provider.config.context + const namespace = await getAppNamespace(k8sCtx, k8sCtx.provider) const selector = `app.kubernetes.io/name=${service.name}` return getKubernetesLogs({ ...params, context, namespace, selector }) diff --git a/garden-service/src/plugins/kubernetes/helm/run.ts b/garden-service/src/plugins/kubernetes/helm/run.ts index 8d07595eb8..bb9cd7363b 100644 --- a/garden-service/src/plugins/kubernetes/helm/run.ts +++ b/garden-service/src/plugins/kubernetes/helm/run.ts @@ -15,14 +15,16 @@ import { findServiceResource, getChartResources, getResourceContainer, getServic import { PluginContext } from "../../../plugin-context" import { LogEntry } from "../../../logger/log-entry" import { ConfigurationError } from "../../../exceptions" +import { KubernetesPluginContext } from "../kubernetes" export async function runHelmModule( { ctx, module, command, ignoreError = true, interactive, runtimeContext, timeout, log, }: RunModuleParams, ): Promise { - const context = ctx.provider.config.context - const namespace = await getAppNamespace(ctx, ctx.provider) + const k8sCtx = ctx + const context = k8sCtx.provider.config.context + const namespace = await getAppNamespace(k8sCtx, k8sCtx.provider) const serviceResourceSpec = getServiceResourceSpec(module) if (!serviceResourceSpec) { @@ -33,7 +35,7 @@ export async function runHelmModule( ) } - const image = await getImage(ctx, module, log, serviceResourceSpec) + const image = await getImage(k8sCtx, module, log, serviceResourceSpec) return runPod({ context, @@ -51,11 +53,12 @@ export async function runHelmModule( export async function runHelmTask( { ctx, log, module, task, interactive, runtimeContext, timeout }: RunTaskParams, ): Promise { - const context = ctx.provider.config.context - const namespace = await getAppNamespace(ctx, ctx.provider) + const k8sCtx = ctx + const context = k8sCtx.provider.config.context + const namespace = await getAppNamespace(k8sCtx, k8sCtx.provider) const args = task.spec.args - const image = await getImage(ctx, module, log, task.spec.resource || getServiceResourceSpec(module)) + const image = await getImage(k8sCtx, module, log, task.spec.resource || getServiceResourceSpec(module)) const res = await runPod({ context, diff --git a/garden-service/src/plugins/kubernetes/helm/status.ts b/garden-service/src/plugins/kubernetes/helm/status.ts index 0e3641cefb..e36475c8ca 100644 --- a/garden-service/src/plugins/kubernetes/helm/status.ts +++ b/garden-service/src/plugins/kubernetes/helm/status.ts @@ -19,6 +19,7 @@ import { getChartResources, findServiceResource } from "./common" import { buildHelmModule } from "./build" import { configureHotReload } from "../hot-reload" import { getHotReloadSpec } from "./hot-reload" +import { KubernetesPluginContext } from "../kubernetes" const helmStatusCodeMap: { [code: number]: ServiceState } = { // see https://github.com/kubernetes/helm/blob/master/_proto/hapi/release/status.proto @@ -36,17 +37,18 @@ const helmStatusCodeMap: { [code: number]: ServiceState } = { export async function getServiceStatus( { ctx, module, service, log, hotReload }: GetServiceStatusParams, ): Promise { + const k8sCtx = ctx // need to build to be able to check the status - const buildStatus = await getExecModuleBuildStatus({ ctx, module, log }) + const buildStatus = await getExecModuleBuildStatus({ ctx: k8sCtx, module, log }) if (!buildStatus.ready) { - await buildHelmModule({ ctx, module, log }) + await buildHelmModule({ ctx: k8sCtx, module, log }) } // first check if the installed objects on the cluster match the current code - const chartResources = await getChartResources(ctx, module, log) + const chartResources = await getChartResources(k8sCtx, module, log) if (hotReload) { - const target = await findServiceResource({ ctx, log, chartResources, module }) + const target = await findServiceResource({ ctx: k8sCtx, log, chartResources, module }) const hotReloadSpec = getHotReloadSpec(service) const resourceSpec = module.spec.serviceResource! @@ -58,9 +60,10 @@ export async function getServiceStatus( }) } - const api = new KubeApi(ctx.provider) - const namespace = await getAppNamespace(ctx, ctx.provider) - let { state, remoteObjects } = await compareDeployedObjects(ctx, api, namespace, chartResources, log) + const provider = k8sCtx.provider + const api = new KubeApi(provider.config.context) + const namespace = await getAppNamespace(k8sCtx, provider) + let { state, remoteObjects } = await compareDeployedObjects(k8sCtx, api, namespace, chartResources, log) const detail = { remoteObjects } return { diff --git a/garden-service/src/plugins/kubernetes/helm/test.ts b/garden-service/src/plugins/kubernetes/helm/test.ts index 194ba89752..e77553ce1f 100644 --- a/garden-service/src/plugins/kubernetes/helm/test.ts +++ b/garden-service/src/plugins/kubernetes/helm/test.ts @@ -14,6 +14,7 @@ import { HelmModule } from "./config" import { getAppNamespace } from "../namespace" import { runPod } from "../run" import { findServiceResource, getChartResources, getResourceContainer, getServiceResourceSpec } from "./common" +import { KubernetesPluginContext } from "../kubernetes" export async function testHelmModule( { ctx, log, interactive, module, runtimeContext, testConfig }: @@ -24,12 +25,13 @@ export async function testHelmModule( runtimeContext.envVars = { ...runtimeContext.envVars, ...testConfig.spec.env } const timeout = testConfig.timeout || DEFAULT_TEST_TIMEOUT - const context = ctx.provider.config.context - const namespace = await getAppNamespace(ctx, ctx.provider) + const k8sCtx = ctx + const context = k8sCtx.provider.config.context + const namespace = await getAppNamespace(k8sCtx, k8sCtx.provider) - const chartResources = await getChartResources(ctx, module, log) + const chartResources = await getChartResources(k8sCtx, module, log) const resourceSpec = testConfig.spec.resource || getServiceResourceSpec(module) - const target = await findServiceResource({ ctx, log, chartResources, module, resourceSpec }) + const target = await findServiceResource({ ctx: k8sCtx, log, chartResources, module, resourceSpec }) const container = getResourceContainer(target, resourceSpec.containerName) const image = container.image @@ -45,5 +47,5 @@ export async function testHelmModule( timeout, }) - return storeTestResult({ ctx, module, testName, result }) + return storeTestResult({ ctx: k8sCtx, module, testName, result }) } diff --git a/garden-service/src/plugins/kubernetes/helm/tiller.ts b/garden-service/src/plugins/kubernetes/helm/tiller.ts index bd64e2fbc6..330380ddb6 100644 --- a/garden-service/src/plugins/kubernetes/helm/tiller.ts +++ b/garden-service/src/plugins/kubernetes/helm/tiller.ts @@ -22,7 +22,7 @@ import chalk from "chalk" const serviceAccountName = "garden-tiller" export async function checkTillerStatus(ctx: PluginContext, provider: KubernetesProvider, log: LogEntry) { - const api = new KubeApi(provider) + const api = new KubeApi(provider.config.context) const namespace = await getAppNamespace(ctx, provider) const resources = [ diff --git a/garden-service/src/plugins/kubernetes/hot-reload.ts b/garden-service/src/plugins/kubernetes/hot-reload.ts index 23d17c5b0a..d4bc8f6210 100644 --- a/garden-service/src/plugins/kubernetes/hot-reload.ts +++ b/garden-service/src/plugins/kubernetes/hot-reload.ts @@ -26,6 +26,7 @@ import { PluginContext } from "../../plugin-context" import { LogEntry } from "../../logger/log-entry" import { getResourceContainer } from "./helm/common" import { waitForContainerService } from "./container/status" +import { KubernetesPluginContext } from "./kubernetes" export const RSYNC_PORT = 873 export const RSYNC_PORT_NAME = "garden-rsync" @@ -283,7 +284,8 @@ async function getLocalRsyncPort(ctx: PluginContext, log: LogEntry, targetDeploy return rsyncLocalPort } - const namespace = await getAppNamespace(ctx, ctx.provider) + const k8sCtx = ctx + const namespace = await getAppNamespace(k8sCtx, k8sCtx.provider) // Forward random free local port to the remote rsync container. rsyncLocalPort = await getPort() @@ -292,7 +294,7 @@ async function getLocalRsyncPort(ctx: PluginContext, log: LogEntry, targetDeploy log.debug(`Forwarding local port ${rsyncLocalPort} to ${targetDeployment} sync container port ${RSYNC_PORT}`) // TODO: use the API directly instead of kubectl (need to reverse engineer kubectl a bit to get how that works) - const proc = kubectl(ctx.provider.config.context, namespace) + const proc = kubectl(k8sCtx.provider.config.context, namespace) .spawn(["port-forward", targetDeployment, portMapping]) return new Promise((resolve) => { diff --git a/garden-service/src/plugins/kubernetes/init.ts b/garden-service/src/plugins/kubernetes/init.ts index 67e98c0ed9..89f7f8eacd 100644 --- a/garden-service/src/plugins/kubernetes/init.ts +++ b/garden-service/src/plugins/kubernetes/init.ts @@ -30,7 +30,7 @@ import { createNamespace, } from "./namespace" import { KUBECTL_DEFAULT_TIMEOUT, kubectl } from "./kubectl" -import { name as providerName, KubernetesProvider } from "./kubernetes" +import { name as providerName, KubernetesProvider, KubernetesPluginContext } from "./kubernetes" import { isSystemGarden, getSystemGarden } from "./system" import { PluginContext } from "../../plugin-context" import { LogEntry } from "../../logger/log-entry" @@ -45,7 +45,8 @@ const SYSTEM_NAMESPACE_MIN_VERSION = "0.9.0" * Used by both the remote and local plugin */ async function prepareNamespaces({ ctx }: GetEnvironmentStatusParams) { - const kubeContext = ctx.provider.config.context + const k8sCtx = ctx + const kubeContext = k8sCtx.provider.config.context try { // TODO: use API instead of kubectl (I just couldn't find which API call to make) @@ -66,13 +67,14 @@ async function prepareNamespaces({ ctx }: GetEnvironmentStatusParams) { } await Bluebird.all([ - getMetadataNamespace(ctx, ctx.provider), - getAppNamespace(ctx, ctx.provider), + getMetadataNamespace(k8sCtx, k8sCtx.provider), + getAppNamespace(k8sCtx, k8sCtx.provider), ]) } export async function getRemoteEnvironmentStatus({ ctx, log }: GetEnvironmentStatusParams) { - const loggedIn = await getLoginStatus({ ctx, log }) + const k8sCtx = ctx + const loggedIn = await getLoginStatus({ ctx: k8sCtx, log }) if (!loggedIn) { return { @@ -81,11 +83,11 @@ export async function getRemoteEnvironmentStatus({ ctx, log }: GetEnvironmentSta } } - await prepareNamespaces({ ctx, log }) + await prepareNamespaces({ ctx: k8sCtx, log }) - let ready = (await checkTillerStatus(ctx, ctx.provider, log)) === "ready" + let ready = (await checkTillerStatus(k8sCtx, k8sCtx.provider, log)) === "ready" - const api = new KubeApi(ctx.provider) + const api = new KubeApi(k8sCtx.provider.config.context) const contextForLog = `Checking environment status for plugin "kubernetes"` const sysNamespaceUpToDate = await systemNamespaceUpToDate(api, log, contextForLog) if (!sysNamespaceUpToDate) { @@ -107,20 +109,23 @@ export async function getLocalEnvironmentStatus({ ctx, log }: GetEnvironmentStat await prepareNamespaces({ ctx, log }) - if (!isSystemGarden(ctx.provider)) { + const k8sCtx = ctx + const provider = k8sCtx.provider + + if (!isSystemGarden(provider)) { // Check if system services are deployed - const sysGarden = await getSystemGarden(ctx.provider) - const sysCtx = await sysGarden.getPluginContext(ctx.provider.name) + const sysGarden = await getSystemGarden(provider) + const sysCtx = await sysGarden.getPluginContext(provider.name) const sysStatus = await sysGarden.actions.getStatus({ log }) - const serviceStatuses = pick(sysStatus.services, getSystemServices(ctx.provider)) + const serviceStatuses = pick(sysStatus.services, getSystemServices(provider)) - const api = new KubeApi(ctx.provider) + const api = new KubeApi(provider.config.context) const servicesReady = every(values(serviceStatuses).map(s => s.state === "ready")) const contextForLog = `Checking environment status for plugin "local-kubernetes"` sysNamespaceUpToDate = await systemNamespaceUpToDate(api, log, contextForLog) - const systemReady = sysStatus.providers[ctx.provider.config.name].ready + const systemReady = sysStatus.providers[provider.config.name].ready && servicesReady && sysNamespaceUpToDate @@ -129,7 +134,7 @@ export async function getLocalEnvironmentStatus({ ctx, log }: GetEnvironmentStat } // Check Tiller status - if (await checkTillerStatus(ctx, ctx.provider, log) !== "ready") { + if (await checkTillerStatus(k8sCtx, provider, log) !== "ready") { ready = false } @@ -138,8 +143,8 @@ export async function getLocalEnvironmentStatus({ ctx, log }: GetEnvironmentStat } // Add the Kubernetes dashboard to the Garden dashboard - const namespace = await getAppNamespace(ctx, ctx.provider) - const defaultHostname = ctx.provider.config.defaultHostname + const namespace = await getAppNamespace(k8sCtx, provider) + const defaultHostname = provider.config.defaultHostname const dashboardStatus = sysStatus.services["kubernetes-dashboard"] const dashboardServiceResource = find( @@ -170,33 +175,36 @@ export async function getLocalEnvironmentStatus({ ctx, log }: GetEnvironmentStat } export async function prepareRemoteEnvironment({ ctx, log }: PrepareEnvironmentParams) { - const loggedIn = await getLoginStatus({ ctx, log }) + const k8sCtx = ctx + const loggedIn = await getLoginStatus({ ctx: k8sCtx, log }) if (!loggedIn) { - await login({ ctx, log }) + await login({ ctx: k8sCtx, log }) } - const api = new KubeApi(ctx.provider) + const provider = k8sCtx.provider + const api = new KubeApi(provider.config.context) const contextForLog = `Preparing environment for plugin "kubernetes"` if (!await systemNamespaceUpToDate(api, log, contextForLog)) { await recreateSystemNamespaces(api, log) } - await installTiller(ctx, ctx.provider, log) + await installTiller(k8sCtx, provider, log) return {} } export async function prepareLocalEnvironment({ ctx, log }: PrepareEnvironmentParams) { // make sure system services are deployed - if (!isSystemGarden(ctx.provider)) { - const api = new KubeApi(ctx.provider) + const k8sCtx = ctx + if (!isSystemGarden(k8sCtx.provider)) { + const api = new KubeApi(k8sCtx.provider.config.context) const contextForLog = `Preparing environment for plugin "local-kubernetes"` const outdated = !(await systemNamespaceUpToDate(api, log, contextForLog)) if (outdated) { await recreateSystemNamespaces(api, log) } - await configureSystemServices({ ctx, log, force: true }) - await installTiller(ctx, ctx.provider, log) + await configureSystemServices({ ctx: k8sCtx, log, force: true }) + await installTiller(k8sCtx, k8sCtx.provider, log) } return {} @@ -247,8 +255,9 @@ export async function recreateSystemNamespaces(api: KubeApi, log: LogEntry) { } export async function cleanupEnvironment({ ctx, log }: CleanupEnvironmentParams) { - const api = new KubeApi(ctx.provider) - const namespace = await getAppNamespace(ctx, ctx.provider) + const k8sCtx = ctx + const api = new KubeApi(k8sCtx.provider.config.context) + const namespace = await getAppNamespace(k8sCtx, k8sCtx.provider) const entry = log.info({ section: "kubernetes", msg: `Deleting namespace ${namespace} (this may take a while)`, @@ -256,7 +265,7 @@ export async function cleanupEnvironment({ ctx, log }: CleanupEnvironmentParams) }) await deleteNamespaces([namespace], api, entry) - await logout({ ctx, log }) + await logout({ ctx: k8sCtx, log }) return {} } @@ -389,14 +398,15 @@ async function configureSystemServices( { ctx, force, log }: { ctx: PluginContext, force: boolean, log: LogEntry }, ) { - const provider = ctx.provider + const k8sCtx = ctx + const provider = k8sCtx.provider const sysGarden = await getSystemGarden(provider) - const sysCtx = sysGarden.getPluginContext(provider.name) + const sysCtx = sysGarden.getPluginContext(provider.name) await installTiller(sysCtx, sysCtx.provider, log) // only deploy services if configured to do so (e.g. minikube bundles some required services as addons) - const systemServices = getSystemServices(ctx.provider) + const systemServices = getSystemServices(k8sCtx.provider) if (systemServices.length > 0) { const results = await sysGarden.actions.deployServices({ diff --git a/garden-service/src/plugins/kubernetes/kubernetes.ts b/garden-service/src/plugins/kubernetes/kubernetes.ts index 080ad217be..203b02b512 100644 --- a/garden-service/src/plugins/kubernetes/kubernetes.ts +++ b/garden-service/src/plugins/kubernetes/kubernetes.ts @@ -17,6 +17,7 @@ import { getSecret, setSecret, deleteSecret } from "./secrets" import { containerRegistryConfigSchema, ContainerRegistryConfig } from "../container/config" import { getRemoteEnvironmentStatus, prepareRemoteEnvironment, cleanupEnvironment } from "./init" import { containerHandlers } from "./container/handlers" +import { PluginContext } from "../../plugin-context" export const name = "kubernetes" @@ -49,6 +50,7 @@ export interface KubernetesConfig extends KubernetesBaseConfig { } export type KubernetesProvider = Provider +export type KubernetesPluginContext = PluginContext export const k8sContextSchema = Joi.string() .required() diff --git a/garden-service/src/plugins/kubernetes/namespace.ts b/garden-service/src/plugins/kubernetes/namespace.ts index 7aae1a97d9..53fbeb9e7d 100644 --- a/garden-service/src/plugins/kubernetes/namespace.ts +++ b/garden-service/src/plugins/kubernetes/namespace.ts @@ -85,7 +85,7 @@ export async function getNamespace( } if (!skipCreate) { - const api = new KubeApi(provider) + const api = new KubeApi(provider.config.context) await ensureNamespace(api, namespace) } diff --git a/garden-service/src/plugins/kubernetes/secrets.ts b/garden-service/src/plugins/kubernetes/secrets.ts index 7c24ae5282..a2a51fb95b 100644 --- a/garden-service/src/plugins/kubernetes/secrets.ts +++ b/garden-service/src/plugins/kubernetes/secrets.ts @@ -9,14 +9,15 @@ import { V1Secret } from "@kubernetes/client-node" import { KubeApi } from "./api" -import { SecretRef } from "./kubernetes" +import { SecretRef, KubernetesPluginContext } from "./kubernetes" import { ConfigurationError } from "../../exceptions" import { GetSecretParams, SetSecretParams, DeleteSecretParams } from "../../types/plugin/params" import { getMetadataNamespace } from "./namespace" export async function getSecret({ ctx, key }: GetSecretParams) { - const api = new KubeApi(ctx.provider) - const ns = await getMetadataNamespace(ctx, ctx.provider) + const k8sCtx = ctx + const api = new KubeApi(k8sCtx.provider.config.context) + const ns = await getMetadataNamespace(k8sCtx, k8sCtx.provider) try { const res = await api.core.readNamespacedSecret(key, ns) @@ -32,8 +33,9 @@ export async function getSecret({ ctx, key }: GetSecretParams) { export async function setSecret({ ctx, key, value }: SetSecretParams) { // we store configuration in a separate metadata namespace, so that configs aren't cleared when wiping the namespace - const api = new KubeApi(ctx.provider) - const ns = await getMetadataNamespace(ctx, ctx.provider) + const k8sCtx = ctx + const api = new KubeApi(k8sCtx.provider.config.context) + const ns = await getMetadataNamespace(k8sCtx, k8sCtx.provider) const body = { body: { apiVersion: "v1", @@ -63,8 +65,9 @@ export async function setSecret({ ctx, key, value }: SetSecretParams) { } export async function deleteSecret({ ctx, key }: DeleteSecretParams) { - const api = new KubeApi(ctx.provider) - const ns = await getMetadataNamespace(ctx, ctx.provider) + const k8sCtx = ctx + const api = new KubeApi(k8sCtx.provider.config.context) + const ns = await getMetadataNamespace(k8sCtx, k8sCtx.provider) try { await api.core.deleteNamespacedSecret(key, ns, {}) diff --git a/garden-service/src/plugins/kubernetes/status.ts b/garden-service/src/plugins/kubernetes/status.ts index cf75ea8b84..bb582f0d05 100644 --- a/garden-service/src/plugins/kubernetes/status.ts +++ b/garden-service/src/plugins/kubernetes/status.ts @@ -26,7 +26,7 @@ import { V1DeploymentStatus, } from "@kubernetes/client-node" import { some, zip, isArray, isPlainObject, pickBy, mapValues } from "lodash" -import { KubernetesProvider } from "./kubernetes" +import { KubernetesProvider, KubernetesPluginContext } from "./kubernetes" import { isSubset } from "../../util/is-subset" import { LogEntry } from "../../logger/log-entry" import { V1ReplicationController, V1ReplicaSet } from "@kubernetes/client-node" @@ -350,7 +350,7 @@ export async function waitForResources({ ctx, provider, serviceName, resources: msg: `Waiting for service to be ready...`, }) - const api = new KubeApi(provider) + const api = new KubeApi(provider.config.context) const namespace = await getAppNamespace(ctx, provider) let prevStatuses: WorkloadStatus[] = objects.map((obj) => ({ state: "unknown", @@ -420,7 +420,8 @@ export async function compareDeployedObjects( ctx: PluginContext, api: KubeApi, namespace: string, objects: KubernetesResource[], log: LogEntry, ): Promise { - const maybeDeployedObjects = await Bluebird.map(objects, obj => getDeployedObject(ctx, ctx.provider, obj)) + const k8sCtx = ctx + const maybeDeployedObjects = await Bluebird.map(objects, obj => getDeployedObject(k8sCtx, k8sCtx.provider, obj)) const deployedObjects = maybeDeployedObjects.filter(o => o !== null) const result: ComparisonResult = { @@ -523,7 +524,7 @@ export async function compareDeployedObjects( async function getDeployedObject( ctx: PluginContext, provider: KubernetesProvider, obj: KubernetesResource, ): Promise { - const api = new KubeApi(provider) + const api = new KubeApi(provider.config.context) const namespace = obj.metadata.namespace || await getAppNamespace(ctx, provider) try { diff --git a/garden-service/src/plugins/kubernetes/test.ts b/garden-service/src/plugins/kubernetes/test.ts index 746d0c25e6..59a8406293 100644 --- a/garden-service/src/plugins/kubernetes/test.ts +++ b/garden-service/src/plugins/kubernetes/test.ts @@ -16,12 +16,14 @@ import { Module } from "../../types/module" import { ModuleVersion } from "../../vcs/base" import { HelmModule } from "./helm/config" import { PluginContext } from "../../plugin-context" +import { KubernetesPluginContext } from "./kubernetes" export async function getTestResult( { ctx, module, testName, version }: GetTestResultParams, ) { - const api = new KubeApi(ctx.provider) - const ns = await getMetadataNamespace(ctx, ctx.provider) + const k8sCtx = ctx + const api = new KubeApi(k8sCtx.provider.config.context) + const ns = await getMetadataNamespace(k8sCtx, k8sCtx.provider) const resultKey = getTestResultKey(module, testName, version) try { @@ -49,14 +51,15 @@ export async function storeTestResult( { ctx, module, testName, result }: { ctx: PluginContext, module: Module, testName: string, result: RunResult }, ) { - const api = new KubeApi(ctx.provider) + const k8sCtx = ctx + const api = new KubeApi(k8sCtx.provider.config.context) const testResult: TestResult = { ...result, testName, } - const ns = await getMetadataNamespace(ctx, ctx.provider) + const ns = await getMetadataNamespace(k8sCtx, k8sCtx.provider) const resultKey = getTestResultKey(module, testName, result.version) const body = { apiVersion: "v1", diff --git a/garden-service/src/plugins/openfaas/openfaas.ts b/garden-service/src/plugins/openfaas/openfaas.ts index 78611a8144..c2bf964136 100644 --- a/garden-service/src/plugins/openfaas/openfaas.ts +++ b/garden-service/src/plugins/openfaas/openfaas.ts @@ -106,6 +106,7 @@ export const configSchema = providerConfigBaseSchema }) type OpenFaasProvider = Provider +type OpenFaasPluginContext = PluginContext export function gardenPlugin(): GardenPlugin { return { @@ -113,14 +114,15 @@ export function gardenPlugin(): GardenPlugin { modules: [join(STATIC_DIR, "openfaas", "templates")], actions: { async getEnvironmentStatus({ ctx, log }: GetEnvironmentStatusParams) { - const ofGarden = await getOpenFaasGarden(ctx) + const openFaasCtx = ctx + const ofGarden = await getOpenFaasGarden(openFaasCtx) const status = await ofGarden.actions.getStatus({ log }) const envReady = every(values(status.providers).map(s => s.ready)) const servicesReady = every(values(status.services).map(s => s.state === "ready")) // TODO: get rid of this convoluted nested Garden setup - const k8sProviderName = getK8sProvider(ctx).name - const ofCtx = await ofGarden.getPluginContext(k8sProviderName) + const k8sProviderName = getK8sProvider(openFaasCtx).name + const ofCtx = (await ofGarden.getPluginContext(k8sProviderName)) const ofK8sProvider = getK8sProvider(ofCtx) const tillerState = await checkTillerStatus(ofCtx, ofK8sProvider, log) @@ -131,14 +133,15 @@ export function gardenPlugin(): GardenPlugin { }, async prepareEnvironment({ ctx, force, log }: PrepareEnvironmentParams) { + const openFaasCtx = ctx // TODO: refactor to dedupe similar code in local-kubernetes - const ofGarden = await getOpenFaasGarden(ctx) + const ofGarden = await getOpenFaasGarden(openFaasCtx) await ofGarden.actions.prepareEnvironment({ force, log }) // TODO: avoid this coupling (requires work on plugin dependencies) - const k8sProviderName = getK8sProvider(ctx).name - const ofCtx = await ofGarden.getPluginContext(k8sProviderName) + const k8sProviderName = getK8sProvider(openFaasCtx).name + const ofCtx = (await ofGarden.getPluginContext(k8sProviderName)) const ofK8sProvider = getK8sProvider(ofCtx) await installTiller(ctx, ofK8sProvider, log) @@ -155,7 +158,7 @@ export function gardenPlugin(): GardenPlugin { }, async cleanupEnvironment({ ctx, log }: CleanupEnvironmentParams) { - const ofGarden = await getOpenFaasGarden(ctx) + const ofGarden = await getOpenFaasGarden(ctx) await ofGarden.actions.cleanupEnvironment({ log }) return {} }, @@ -197,7 +200,7 @@ export function gardenPlugin(): GardenPlugin { })) moduleConfig.outputs = { - endpoint: await getInternalServiceUrl(ctx, moduleConfig), + endpoint: await getInternalServiceUrl(ctx, moduleConfig), } return moduleConfig @@ -206,7 +209,7 @@ export function gardenPlugin(): GardenPlugin { getBuildStatus: getExecModuleBuildStatus, async build({ ctx, log, module }: BuildModuleParams) { - await writeStackFile(ctx, module, {}) + await writeStackFile(ctx, module, {}) const buildLog = await faasCli.stdout({ log, @@ -224,7 +227,7 @@ export function gardenPlugin(): GardenPlugin { async getServiceLogs(params: GetServiceLogsParams) { const { ctx, service } = params - const k8sProvider = getK8sProvider(ctx) + const k8sProvider = getK8sProvider(ctx) const context = k8sProvider.config.context const namespace = await getAppNamespace(ctx, k8sProvider) const selector = `faas_function=${service.name}` @@ -234,8 +237,10 @@ export function gardenPlugin(): GardenPlugin { async deployService(params: DeployServiceParams): Promise { const { ctx, module, service, log, runtimeContext } = params + const openFaasCtx = ctx + // write the stack file again with environment variables - await writeStackFile(ctx, module, runtimeContext.envVars) + await writeStackFile(openFaasCtx, module, runtimeContext.envVars) // use faas-cli to do the deployment await faasCli.stdout({ @@ -245,14 +250,14 @@ export function gardenPlugin(): GardenPlugin { }) // wait until deployment is ready - const k8sProvider = getK8sProvider(ctx) - const namespace = await getAppNamespace(ctx, k8sProvider) - const api = new KubeApi(k8sProvider) + const k8sProvider = getK8sProvider(openFaasCtx) + const namespace = await getAppNamespace(openFaasCtx, k8sProvider) + const api = new KubeApi(k8sProvider.config.context) const deployment = (await api.apps.readNamespacedDeployment(service.name, namespace)).body await waitForResources({ - ctx, + ctx: openFaasCtx, provider: k8sProvider, serviceName: service.name, log, @@ -265,12 +270,13 @@ export function gardenPlugin(): GardenPlugin { async deleteService(params: DeleteServiceParams): Promise { const { ctx, log, service, runtimeContext } = params + const openFaasCtx = ctx let status let found = true try { status = await getServiceStatus({ - ctx, + ctx: openFaasCtx, log, service, runtimeContext, @@ -302,7 +308,7 @@ export function gardenPlugin(): GardenPlugin { } async function writeStackFile( - ctx: PluginContext, module: OpenFaasModule, envVars: PrimitiveMap, + ctx: PluginContext, module: OpenFaasModule, envVars: PrimitiveMap, ) { const image = getImageName(module) @@ -311,7 +317,7 @@ async function writeStackFile( return dumpYaml(stackPath, { provider: { name: "faas", - gateway: getExternalGatewayUrl(ctx), + gateway: getExternalGatewayUrl(ctx), }, functions: { [module.name]: { @@ -325,17 +331,18 @@ async function writeStackFile( } async function getServiceStatus({ ctx, module, service }: GetServiceStatusParams) { - const k8sProvider = getK8sProvider(ctx) + const openFaasCtx = ctx + const k8sProvider = getK8sProvider(openFaasCtx) const ingresses: ServiceIngress[] = [{ - hostname: getExternalGatewayHostname(ctx.provider, k8sProvider), + hostname: getExternalGatewayHostname(openFaasCtx.provider, k8sProvider), path: getServicePath(module), port: k8sProvider.config.ingressHttpPort, protocol: "http", }] - const namespace = await getAppNamespace(ctx, k8sProvider) - const api = new KubeApi(k8sProvider) + const namespace = await getAppNamespace(openFaasCtx, k8sProvider) + const api = new KubeApi(k8sProvider.config.context) let deployment @@ -399,8 +406,8 @@ function getImageName(module: OpenFaasModule) { // } // } -function getK8sProvider(ctx: PluginContext): KubernetesProvider { - const provider = ctx.providers["local-kubernetes"] || ctx.providers.kubernetes +function getK8sProvider(ctx: PluginContext): KubernetesProvider { + const provider = (ctx.providers["local-kubernetes"] || ctx.providers.kubernetes) if (!provider) { throw new ConfigurationError(`openfaas requires a kubernetes (or local-kubernetes) provider to be configured`, { @@ -415,7 +422,7 @@ function getServicePath(config: OpenFaasModuleConfig) { return join("/", "function", config.name) } -async function getInternalGatewayUrl(ctx: PluginContext) { +async function getInternalGatewayUrl(ctx: PluginContext) { const k8sProvider = getK8sProvider(ctx) const namespace = await getOpenfaasNamespace(ctx, k8sProvider, true) return `http://gateway.${namespace}.svc.cluster.local:8080` @@ -436,22 +443,24 @@ function getExternalGatewayHostname(provider: OpenFaasProvider, k8sProvider: Kub return hostname } -function getExternalGatewayUrl(ctx: PluginContext) { +function getExternalGatewayUrl(ctx: PluginContext) { const k8sProvider = getK8sProvider(ctx) const hostname = getExternalGatewayHostname(ctx.provider, k8sProvider) const ingressPort = k8sProvider.config.ingressHttpPort return `http://${hostname}:${ingressPort}` } -async function getInternalServiceUrl(ctx: PluginContext, config: OpenFaasModuleConfig) { +async function getInternalServiceUrl(ctx: PluginContext, config: OpenFaasModuleConfig) { return urlResolve(await getInternalGatewayUrl(ctx), getServicePath(config)) } -async function getOpenfaasNamespace(ctx: PluginContext, k8sProvider: KubernetesProvider, skipCreate?: boolean) { +async function getOpenfaasNamespace( + ctx: PluginContext, k8sProvider: KubernetesProvider, skipCreate?: boolean, +) { return getNamespace({ ctx, provider: k8sProvider, skipCreate, suffix: "openfaas" }) } -export async function getOpenFaasGarden(ctx: PluginContext): Promise { +export async function getOpenFaasGarden(ctx: PluginContext): Promise { // TODO: figure out good way to retrieve namespace from kubernetes plugin through an exposed interface // (maybe allow plugins to expose arbitrary data on the Provider object?) const k8sProvider = getK8sProvider(ctx) diff --git a/garden-service/test/src/plugins/kubernetes/container/ingress.ts b/garden-service/test/src/plugins/kubernetes/container/ingress.ts index f76cbc3772..3acb8f1b02 100644 --- a/garden-service/test/src/plugins/kubernetes/container/ingress.ts +++ b/garden-service/test/src/plugins/kubernetes/container/ingress.ts @@ -375,8 +375,8 @@ describe("createIngressResources", () => { } } - function getKubeApi(provider) { - const api = new KubeApi(provider) + function getKubeApi(context: string) { + const api = new KubeApi(context) const core = td.replace(api, "core") td.when(core.readNamespacedSecret("somesecret", "somenamespace")).thenResolve({ @@ -401,8 +401,8 @@ describe("createIngressResources", () => { port: "http", }) - const api = getKubeApi(basicProvider) - const ingresses = await createIngressResources(api, namespace, service) + const api = getKubeApi(basicProvider.config.context) + const ingresses = await createIngressResources(api, basicProvider, namespace, service) expect(ingresses).to.eql([{ apiVersion: "extensions/v1beta1", @@ -443,8 +443,8 @@ describe("createIngressResources", () => { port: "http", }) - const api = getKubeApi(basicProvider) - const ingresses = await createIngressResources(api, namespace, service) + const api = getKubeApi(basicProvider.config.context) + const ingresses = await createIngressResources(api, basicProvider, namespace, service) expect(ingresses).to.eql([{ apiVersion: "extensions/v1beta1", @@ -494,8 +494,8 @@ describe("createIngressResources", () => { }, ) - const api = getKubeApi(basicProvider) - const ingresses = await createIngressResources(api, namespace, service) + const api = getKubeApi(basicProvider.config.context) + const ingresses = await createIngressResources(api, basicProvider, namespace, service) expect(ingresses).to.eql([ { @@ -566,8 +566,8 @@ describe("createIngressResources", () => { }, ) - const api = getKubeApi(singleTlsProvider) - const ingresses = await createIngressResources(api, namespace, service) + const api = getKubeApi(singleTlsProvider.config.context) + const ingresses = await createIngressResources(api, singleTlsProvider, namespace, service) td.verify(api.upsert("Secret", namespace, myDomainCertSecret)) @@ -615,19 +615,24 @@ describe("createIngressResources", () => { }, ) - const api = getKubeApi({ + const api = getKubeApi(basicConfig.context) + + const provider = { name: "kubernetes", config: { ...basicConfig, - tlsCertificates: [{ secretRef: { name: "foo", namespace: "default" } }], + tlsCertificates: [{ + name: "foo", + secretRef: { name: "foo", namespace: "default" }, + }], }, - }) + } const err: any = new Error("nope") err.code = 404 td.when(api.core.readNamespacedSecret("foo", "default")).thenReject(err) - await expectError(async () => await createIngressResources(api, namespace, service), "configuration") + await expectError(async () => await createIngressResources(api, provider, namespace, service), "configuration") }) it("should throw if a secret for a configured certificate doesn't contain a certificate", async () => { @@ -639,13 +644,18 @@ describe("createIngressResources", () => { }, ) - const api = getKubeApi({ + const provider = { name: "kubernetes", config: { ...basicConfig, - tlsCertificates: [{ secretRef: { name: "foo", namespace: "default" } }], + tlsCertificates: [{ + name: "foo", + secretRef: { name: "foo", namespace: "default" }, + }], }, - }) + } + + const api = getKubeApi(basicConfig.context) const err: any = new Error("nope") err.code = 404 @@ -655,7 +665,7 @@ describe("createIngressResources", () => { }, }) - await expectError(async () => await createIngressResources(api, namespace, service), "configuration") + await expectError(async () => await createIngressResources(api, provider, namespace, service), "configuration") }) it("should throw if a secret for a configured certificate contains an invalid certificate", async () => { @@ -667,13 +677,18 @@ describe("createIngressResources", () => { }, ) - const api = getKubeApi({ + const provider = { name: "kubernetes", config: { ...basicConfig, - tlsCertificates: [{ secretRef: { name: "foo", namespace: "default" } }], + tlsCertificates: [{ + name: "foo", + secretRef: { name: "foo", namespace: "default" }, + }], }, - }) + } + + const api = getKubeApi(basicConfig.context) const err: any = new Error("nope") err.code = 404 @@ -685,7 +700,7 @@ describe("createIngressResources", () => { }, }) - await expectError(async () => await createIngressResources(api, namespace, service), "configuration") + await expectError(async () => await createIngressResources(api, provider, namespace, service), "configuration") }) it("should correctly match an ingress to a wildcard certificate", async () => { @@ -698,8 +713,8 @@ describe("createIngressResources", () => { }, ) - const api = getKubeApi(multiTlsProvider) - const ingresses = await createIngressResources(api, namespace, service) + const api = getKubeApi(multiTlsProvider.config.context) + const ingresses = await createIngressResources(api, multiTlsProvider, namespace, service) td.verify(api.upsert("Secret", namespace, wildcardDomainCertSecret)) @@ -748,7 +763,9 @@ describe("createIngressResources", () => { }, ) - const api = getKubeApi({ + const api = getKubeApi(basicConfig.context) + + const provider = { name: "kubernetes", config: { ...basicConfig, @@ -758,12 +775,12 @@ describe("createIngressResources", () => { secretRef: { name: "somesecret", namespace: "somenamespace" }, }], }, - }) + } td.when(api.core.readNamespacedSecret("foo", "default")).thenResolve({ body: myDomainCertSecret, }) - const ingresses = await createIngressResources(api, namespace, service) + const ingresses = await createIngressResources(api, provider, namespace, service) td.verify(api.upsert("Secret", namespace, myDomainCertSecret))