From 8d3f366fa8df9b8f71c5bbc57119da6aa9fff597 Mon Sep 17 00:00:00 2001 From: Thorarinn Sigurdsson Date: Tue, 18 Sep 2018 18:53:00 +0200 Subject: [PATCH 1/2] fix: incl. ingresses & services in delete command. When deleting a container service via the delete command, also delete its k8s service and ingress. This fixes problems previously caused by dangling k8s ingresses/services. --- garden-cli/package-lock.json | 2 +- garden-cli/src/plugins/kubernetes/actions.ts | 4 ++- .../src/plugins/kubernetes/deployment.ts | 20 ++++++------- garden-cli/src/plugins/kubernetes/kubectl.ts | 30 +++++++++++++++++++ garden-cli/src/plugins/openfaas.ts | 5 +++- 5 files changed, 48 insertions(+), 13 deletions(-) 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..4ab287206c 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, deploymentOnly: false, logEntry }) return getContainerServiceStatus(params) } diff --git a/garden-cli/src/plugins/kubernetes/deployment.ts b/garden-cli/src/plugins/kubernetes/deployment.ts index 5a06f5aecb..6266124869 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,17 +322,19 @@ 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, deploymentOnly, logEntry }, +) { + let found = true + const context = provider.config.context + const api = new KubeApi(provider) try { await api.extensions.deleteNamespacedDeployment(serviceName, namespace, {}) + if (!deploymentOnly) { + await deleteObjectsByLabel(context, namespace, "service", serviceName) + } } catch (err) { if (err.code === 404) { found = false diff --git a/garden-cli/src/plugins/kubernetes/kubectl.ts b/garden-cli/src/plugins/kubernetes/kubectl.ts index 8484da38be..91d429bdfe 100644 --- a/garden-cli/src/plugins/kubernetes/kubectl.ts +++ b/garden-cli/src/plugins/kubernetes/kubectl.ts @@ -37,6 +37,11 @@ export interface ApplyOptions { namespace?: string, } +export interface DeleteOptions { + includeUninitialized?: boolean, + objectTypes?: string[] +} + export const KUBECTL_DEFAULT_TIMEOUT = 300 export class Kubectl { @@ -193,3 +198,28 @@ export async function applyMany( return result.output } } + +const defaultObjectTypesForDelete = ["deployment", "service", "ingress"] + +export async function deleteObjectsByLabel( + context: string, namespace: string, labelKey: string, labelValue: string, + { includeUninitialized = false, objectTypes = defaultObjectTypesForDelete }: DeleteOptions = {}, +) { + + 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.ts b/garden-cli/src/plugins/openfaas.ts index 4193eb7c48..ce9249b599 100644 --- a/garden-cli/src/plugins/openfaas.ts +++ b/garden-cli/src/plugins/openfaas.ts @@ -244,7 +244,10 @@ export function gardenPlugin({ config }: { config: OpenFaasConfig }): GardenPlug const provider = getK8sProvider(ctx) const namespace = await getAppNamespace(ctx, provider) - await deleteContainerService({ provider, logEntry, namespace, serviceName: service.name }) + await deleteContainerService({ + namespace, provider, serviceName: service.name, + deploymentOnly: true, logEntry, + }) return await getServiceStatus(params) }, From b38113ea73c7627468e34681254559a4812d722b Mon Sep 17 00:00:00 2001 From: Thorarinn Sigurdsson Date: Wed, 19 Sep 2018 18:10:01 +0200 Subject: [PATCH 2/2] fix: use faas-cli to delete OpenFAAS services --- garden-cli/src/plugins/kubernetes/actions.ts | 2 +- .../src/plugins/kubernetes/deployment.ts | 23 +++- garden-cli/src/plugins/kubernetes/kubectl.ts | 25 ++-- garden-cli/src/plugins/openfaas/faas-cli.ts | 44 +++++++ .../src/plugins/{ => openfaas}/openfaas.ts | 112 ++++++++++-------- garden-cli/src/plugins/plugins.ts | 2 +- 6 files changed, 144 insertions(+), 64 deletions(-) create mode 100644 garden-cli/src/plugins/openfaas/faas-cli.ts rename garden-cli/src/plugins/{ => openfaas}/openfaas.ts (83%) diff --git a/garden-cli/src/plugins/kubernetes/actions.ts b/garden-cli/src/plugins/kubernetes/actions.ts index 4ab287206c..8d8bcd6881 100644 --- a/garden-cli/src/plugins/kubernetes/actions.ts +++ b/garden-cli/src/plugins/kubernetes/actions.ts @@ -163,7 +163,7 @@ export async function deleteService(params: DeleteServiceParams): Promise{}) - if (!deploymentOnly) { - await deleteObjectsByLabel(context, namespace, "service", serviceName) - } } catch (err) { if (err.code === 404) { found = false diff --git a/garden-cli/src/plugins/kubernetes/kubectl.ts b/garden-cli/src/plugins/kubernetes/kubectl.ts index 91d429bdfe..58f9e12777 100644 --- a/garden-cli/src/plugins/kubernetes/kubectl.ts +++ b/garden-cli/src/plugins/kubernetes/kubectl.ts @@ -37,11 +37,6 @@ export interface ApplyOptions { namespace?: string, } -export interface DeleteOptions { - includeUninitialized?: boolean, - objectTypes?: string[] -} - export const KUBECTL_DEFAULT_TIMEOUT = 300 export class Kubectl { @@ -199,12 +194,24 @@ export async function applyMany( } } -const defaultObjectTypesForDelete = ["deployment", "service", "ingress"] +export interface DeleteObjectsParams { + context: string, + namespace: string, + labelKey: string, + labelValue: string, + objectTypes: string[], + includeUninitialized?: boolean, +} export async function deleteObjectsByLabel( - context: string, namespace: string, labelKey: string, labelValue: string, - { includeUninitialized = false, objectTypes = defaultObjectTypesForDelete }: DeleteOptions = {}, -) { + { + context, + namespace, + labelKey, + labelValue, + objectTypes, + includeUninitialized = false, + }: DeleteObjectsParams) { let args = [ "delete", 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 ce9249b599..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,16 +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({ - namespace, provider, serviceName: service.name, - deploymentOnly: true, logEntry, - }) + 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