diff --git a/garden-cli/package-lock.json b/garden-cli/package-lock.json index 751977726c..13cbea06fd 100644 --- a/garden-cli/package-lock.json +++ b/garden-cli/package-lock.json @@ -1,6 +1,6 @@ { "name": "garden-cli", - "version": "0.5.0", + "version": "0.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/garden-cli/src/plugins/kubernetes/actions.ts b/garden-cli/src/plugins/kubernetes/actions.ts index 5baf534074..8d8bcd6881 100644 --- a/garden-cli/src/plugins/kubernetes/actions.ts +++ b/garden-cli/src/plugins/kubernetes/actions.ts @@ -160,8 +160,10 @@ export async function cleanupEnvironment({ ctx, logEntry }: CleanupEnvironmentPa export async function deleteService(params: DeleteServiceParams): Promise { const { ctx, logEntry, service } = params const namespace = await getAppNamespace(ctx, ctx.provider) + const provider = ctx.provider - await deleteContainerService({ provider: ctx.provider, logEntry, namespace, serviceName: service.name }) + await deleteContainerService( + { provider, namespace, serviceName: service.name, logEntry }) return getContainerServiceStatus(params) } diff --git a/garden-cli/src/plugins/kubernetes/deployment.ts b/garden-cli/src/plugins/kubernetes/deployment.ts index 5a06f5aecb..8f615fcf7a 100644 --- a/garden-cli/src/plugins/kubernetes/deployment.ts +++ b/garden-cli/src/plugins/kubernetes/deployment.ts @@ -22,14 +22,12 @@ import { RuntimeContext, ServiceStatus } from "../../types/service" import { createIngresses, getIngresses } from "./ingress" import { createServices } from "./service" import { waitForObjects, compareDeployedObjects } from "./status" -import { applyMany } from "./kubectl" +import { applyMany, deleteObjectsByLabel } from "./kubectl" import { getAppNamespace } from "./namespace" import { KubernetesObject } from "./helm" import { PluginContext } from "../../plugin-context" -import { KubernetesProvider } from "./kubernetes" import { GARDEN_ANNOTATION_KEYS_VERSION } from "../../constants" import { KubeApi } from "./api" -import { LogEntry } from "../../logger/log-entry" export const DEFAULT_CPU_REQUEST = "10m" export const DEFAULT_CPU_LIMIT = "500m" @@ -324,14 +322,29 @@ export async function createDeployment( return deployment } -export async function deleteContainerService({ namespace, provider, serviceName, logEntry }: { - namespace: string, - provider: KubernetesProvider, - serviceName: string, - logEntry?: LogEntry, -}) { - const api = new KubeApi(provider) +export async function deleteContainerService( + { namespace, provider, serviceName, logEntry }, +) { + + const context = provider.config.context + await deleteContainerDeployment({ namespace, provider, serviceName, logEntry }) + await deleteObjectsByLabel({ + context, + namespace, + labelKey: "service", + labelValue: serviceName, + objectTypes: ["deployment", "service", "ingress"], + includeUninitialized: false, + }) + +} + +export async function deleteContainerDeployment( + { namespace, provider, serviceName, logEntry }, +) { + let found = true + const api = new KubeApi(provider) try { await api.extensions.deleteNamespacedDeployment(serviceName, namespace, {}) diff --git a/garden-cli/src/plugins/kubernetes/kubectl.ts b/garden-cli/src/plugins/kubernetes/kubectl.ts index 8484da38be..58f9e12777 100644 --- a/garden-cli/src/plugins/kubernetes/kubectl.ts +++ b/garden-cli/src/plugins/kubernetes/kubectl.ts @@ -193,3 +193,40 @@ export async function applyMany( return result.output } } + +export interface DeleteObjectsParams { + context: string, + namespace: string, + labelKey: string, + labelValue: string, + objectTypes: string[], + includeUninitialized?: boolean, +} + +export async function deleteObjectsByLabel( + { + context, + namespace, + labelKey, + labelValue, + objectTypes, + includeUninitialized = false, + }: DeleteObjectsParams) { + + let args = [ + "delete", + objectTypes.join(","), + "-l", + `${labelKey}=${labelValue}`, + ] + + includeUninitialized && args.push("--include-uninitialized") + + const result = await kubectl(context, namespace).call(args) + + try { + return JSON.parse(result.output) + } catch (_) { + return result.output + } +} diff --git a/garden-cli/src/plugins/openfaas/faas-cli.ts b/garden-cli/src/plugins/openfaas/faas-cli.ts new file mode 100644 index 0000000000..48b6458dd0 --- /dev/null +++ b/garden-cli/src/plugins/openfaas/faas-cli.ts @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 Garden Technologies, Inc. + * + * 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 { builderWorkDir, stackFilename } from "./openfaas" + +export interface FaasCliCmdParams { + buildPath: string, + imageId: string, + faasCmd: string, + faasOpts?: string[], + dockerOpts?: string[], +} + +export function faasCliCmd( + cmdParams: FaasCliCmdParams, +): string[] { + + return [ + "docker", + ...(faasCliDockerArgs(cmdParams)), + ] + +} + +export function faasCliDockerArgs( + { buildPath, imageId, faasCmd, faasOpts = [], dockerOpts = [] }: FaasCliCmdParams): string[] { + + return [ + "run", "-i", + "-v", `${buildPath}:${builderWorkDir}`, + "-v", "/var/run/docker.sock:/var/run/docker.sock", + "--workdir", builderWorkDir, + ...dockerOpts, + imageId, + "faas-cli", faasCmd, "-f", stackFilename, + ...faasOpts, + ] + +} diff --git a/garden-cli/src/plugins/openfaas.ts b/garden-cli/src/plugins/openfaas/openfaas.ts similarity index 83% rename from garden-cli/src/plugins/openfaas.ts rename to garden-cli/src/plugins/openfaas/openfaas.ts index 4193eb7c48..fb713be06d 100644 --- a/garden-cli/src/plugins/openfaas.ts +++ b/garden-cli/src/plugins/openfaas/openfaas.ts @@ -9,24 +9,24 @@ import * as Joi from "joi" import { join, resolve } from "path" import { resolve as urlResolve } from "url" -import { STATIC_DIR } from "../constants" -import { PluginError, ConfigurationError } from "../exceptions" -import { Garden } from "../garden" -import { PluginContext } from "../plugin-context" -import { joiArray, validate, PrimitiveMap } from "../config/common" -import { Module } from "../types/module" -import { ValidateModuleResult } from "../types/plugin/outputs" +import { STATIC_DIR } from "../../constants" +import { PluginError, ConfigurationError } from "../../exceptions" +import { Garden } from "../../garden" +import { PluginContext } from "../../plugin-context" +import { joiArray, validate, PrimitiveMap } from "../../config/common" +import { Module } from "../../types/module" +import { ValidateModuleResult } from "../../types/plugin/outputs" import { PrepareEnvironmentParams, GetEnvironmentStatusParams, ValidateModuleParams, DeleteServiceParams, -} from "../types/plugin/params" +} from "../../types/plugin/params" import { ServiceStatus, ServiceIngress, Service, -} from "../types/service" +} from "../../types/service" import { buildGenericModule, GenericModuleSpec, @@ -34,30 +34,31 @@ import { GenericTestSpec, testGenericModule, getGenericModuleBuildStatus, -} from "./generic" -import { KubernetesProvider } from "./kubernetes/kubernetes" -import { getNamespace, getAppNamespace } from "./kubernetes/namespace" +} from "../generic" +import { KubernetesProvider } from "../kubernetes/kubernetes" +import { getNamespace, getAppNamespace } from "../kubernetes/namespace" import { DeployServiceParams, GetServiceStatusParams, BuildModuleParams, GetServiceOutputsParams, -} from "../types/plugin/params" +} from "../../types/plugin/params" import { every, values } from "lodash" -import { dumpYaml, findByName } from "../util/util" +import { dumpYaml, findByName } from "../../util/util" import * as execa from "execa" -import { KubeApi } from "./kubernetes/api" -import { waitForObjects, checkDeploymentStatus } from "./kubernetes/status" -import { systemSymbol } from "./kubernetes/system" -import { BaseServiceSpec } from "../config/service" -import { GardenPlugin } from "../types/plugin/plugin" -import { deleteContainerService } from "./kubernetes/deployment" -import { Provider, providerConfigBaseSchema } from "../config/project" +import { KubeApi } from "../kubernetes/api" +import { waitForObjects, checkDeploymentStatus } from "../kubernetes/status" +import { systemSymbol } from "../kubernetes/system" +import { BaseServiceSpec } from "../../config/service" +import { GardenPlugin } from "../../types/plugin/plugin" +import { Provider, providerConfigBaseSchema } from "../../config/project" import dedent = require("dedent") +import { faasCliCmd, faasCliDockerArgs } from "./faas-cli" const systemProjectPath = join(STATIC_DIR, "openfaas", "system") -const stackFilename = "stack.yml" -const builderWorkDir = "/wd" +export const stackFilename = "stack.yml" +export const builderWorkDir = "/wd" +export const FAAS_CLI_IMAGE_ID = "openfaas/faas-cli:0.7.3" export interface OpenFaasModuleSpec extends GenericModuleSpec { handler: string @@ -151,14 +152,11 @@ export function gardenPlugin({ config }: { config: OpenFaasConfig }): GardenPlug ) // FIXME: this feels too magicky and convoluted, we should make this type of flow feel more natural - moduleConfig.build.command = [ - "docker", "run", "-i", - "-v", `\${modules.${moduleConfig.name}.buildPath}:${builderWorkDir}`, - "-v", "/var/run/docker.sock:/var/run/docker.sock", - "--workdir", builderWorkDir, - `openfaas--builder:\${modules.openfaas--builder.version}`, - "faas-cli", "build", "-f", stackFilename, - ] + moduleConfig.build.command = faasCliCmd({ + buildPath: `\${modules.${moduleConfig.name}.buildPath}`, + imageId: `openfaas--builder:\${modules.openfaas--builder.version}`, + faasCmd: "build", + }) moduleConfig.build.dependencies.push({ name: "builder", @@ -216,15 +214,12 @@ export function gardenPlugin({ config }: { config: OpenFaasConfig }): GardenPlug await writeStackFile(ctx, module, runtimeContext.envVars) // use faas-cli to do the deployment - await execa("docker", [ - "run", "-i", - "-v", `${module.buildPath}:${builderWorkDir}`, - "-v", "/var/run/docker.sock:/var/run/docker.sock", - "--workdir", builderWorkDir, - "--net", "host", - "openfaas/faas-cli:0.7.3", - "faas-cli", "deploy", "-f", stackFilename, - ]) + await execa("docker", faasCliDockerArgs({ + buildPath: module.buildPath, + imageId: FAAS_CLI_IMAGE_ID, + faasCmd: "deploy", + dockerOpts: ["--net", "host"], + })) // wait until deployment is ready const k8sProvider = getK8sProvider(ctx) @@ -240,13 +235,37 @@ export function gardenPlugin({ config }: { config: OpenFaasConfig }): GardenPlug }, async deleteService(params: DeleteServiceParams): Promise { - const { ctx, logEntry, service } = params - const provider = getK8sProvider(ctx) - const namespace = await getAppNamespace(ctx, provider) + const { ctx, logEntry, service, runtimeContext } = params + let status + let found = true + + try { + + status = await getServiceStatus({ + ctx, + service, + runtimeContext, + module: service.module, + }) + + found = !!status.state + + await execa("docker", faasCliDockerArgs({ + buildPath: service.module.buildPath, + imageId: FAAS_CLI_IMAGE_ID, + faasCmd: "remove", + dockerOpts: ["--net", "host"], + })) + + } catch (err) { + found = false + } - await deleteContainerService({ provider, logEntry, namespace, serviceName: service.name }) + if (logEntry) { + found ? logEntry.setSuccess("Service deleted") : logEntry.setWarn("Service not deployed") + } - return await getServiceStatus(params) + return status }, }, }, diff --git a/garden-cli/src/plugins/plugins.ts b/garden-cli/src/plugins/plugins.ts index 7c6e346706..1842a392d0 100644 --- a/garden-cli/src/plugins/plugins.ts +++ b/garden-cli/src/plugins/plugins.ts @@ -20,7 +20,7 @@ export const builtinPlugins: RegisterPluginParam[] = [ "./kubernetes/local", "./npm-package", "./google/google-app-engine", - "./openfaas", + "./openfaas/openfaas", ].map(p => resolve(__dirname, p)) // These plugins are always loaded