From f62db2f14a8fde0b4630f267dcab92a817019628 Mon Sep 17 00:00:00 2001
From: Jon Edvald <edvald@gmail.com>
Date: Mon, 24 Sep 2018 17:03:42 +0200
Subject: [PATCH] fix(openfaas): fix issues with openfaas builds

Closes #298
---
 .../src/plugins/openfaas/faas-cli.ts          | 24 +++++++++++
 .../src/plugins/openfaas/openfaas.ts          | 41 ++++++++++---------
 garden-service/src/util/ext-tools.ts          | 21 +++++++---
 .../static/openfaas/builder/garden.yml        | 12 +++++-
 4 files changed, 70 insertions(+), 28 deletions(-)
 create mode 100644 garden-service/src/plugins/openfaas/faas-cli.ts

diff --git a/garden-service/src/plugins/openfaas/faas-cli.ts b/garden-service/src/plugins/openfaas/faas-cli.ts
new file mode 100644
index 0000000000..d8dbf7dfda
--- /dev/null
+++ b/garden-service/src/plugins/openfaas/faas-cli.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2018 Garden Technologies, Inc. <info@garden.io>
+ *
+ * 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 { BinaryCmd } from "../../util/ext-tools"
+
+export const faasCli = new BinaryCmd({
+  name: "faas-cli",
+  specs: {
+    darwin: {
+      url: "https://github.com/openfaas/faas-cli/releases/download/0.7.3/faas-cli-darwin",
+    },
+    linux: {
+      url: "https://github.com/openfaas/faas-cli/releases/download/0.7.3/faas-cli",
+    },
+    win32: {
+      url: "https://github.com/openfaas/faas-cli/releases/download/0.7.3/faas-cli.exe",
+    },
+  },
+})
diff --git a/garden-service/src/plugins/openfaas/openfaas.ts b/garden-service/src/plugins/openfaas/openfaas.ts
index 13b2db3216..b94aafff83 100644
--- a/garden-service/src/plugins/openfaas/openfaas.ts
+++ b/garden-service/src/plugins/openfaas/openfaas.ts
@@ -28,7 +28,6 @@ import {
   Service,
 } from "../../types/service"
 import {
-  buildGenericModule,
   GenericModuleSpec,
   genericModuleSpecSchema,
   GenericTestSpec,
@@ -45,18 +44,18 @@ import {
 } from "../../types/plugin/params"
 import { every, values } from "lodash"
 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 { Provider, providerConfigBaseSchema } from "../../config/project"
+import { faasCli } from "./faas-cli"
+import { CleanupEnvironmentParams } from "../../types/plugin/params"
 import dedent = require("dedent")
 
 const systemProjectPath = join(STATIC_DIR, "openfaas", "system")
 export const stackFilename = "stack.yml"
-export const FAAS_CLI_IMAGE_ID = "openfaas/faas-cli:0.7.3"
 
 export interface OpenFaasModuleSpec extends GenericModuleSpec {
   handler: string
@@ -128,7 +127,7 @@ export function gardenPlugin({ config }: { config: OpenFaasConfig }): GardenPlug
 
         await ofGarden.actions.prepareEnvironment({ force })
 
-        const results = await ofGarden.actions.deployServices({})
+        const results = await ofGarden.actions.deployServices({ force })
         const failed = values(results.taskResults).filter(r => !!r.error).length
 
         if (failed) {
@@ -140,9 +139,8 @@ export function gardenPlugin({ config }: { config: OpenFaasConfig }): GardenPlug
         return {}
       },
 
-      async cleanupEnvironment({ ctx }) {
+      async cleanupEnvironment({ ctx }: CleanupEnvironmentParams) {
         const ofGarden = await getOpenFaasGarden(ctx)
-
         return ofGarden.actions.cleanupEnvironment({})
       },
     },
@@ -155,17 +153,11 @@ export function gardenPlugin({ config }: { config: OpenFaasConfig }): GardenPlug
             { context: `module ${moduleConfig.name}` },
           )
 
-          moduleConfig.build.command = [
-            "faas-cli",
-            "build",
-            "-f", stackFilename,
-          ]
-
           moduleConfig.build.dependencies.push({
             name: "builder",
             plugin: "openfaas",
             copy: [{
-              source: "*",
+              source: "templates/template",
               target: ".",
             }],
           })
@@ -193,13 +185,16 @@ export function gardenPlugin({ config }: { config: OpenFaasConfig }): GardenPlug
 
         getBuildStatus: getGenericModuleBuildStatus,
 
-        async build(params: BuildModuleParams<OpenFaasModule>) {
-          const { ctx, module } = params
-
+        async build({ ctx, module }: BuildModuleParams<OpenFaasModule>) {
           // prepare the stack.yml file, before handing off the build to the generic handler
           await writeStackFile(ctx, module, {})
 
-          return buildGenericModule(params)
+          const buildLog = await faasCli.stdout({
+            cwd: module.buildPath,
+            args: ["build", "-f", stackFilename],
+          })
+
+          return { fresh: true, buildLog }
         },
 
         // TODO: design and implement a proper test flow for openfaas functions
@@ -214,13 +209,16 @@ export function gardenPlugin({ config }: { config: OpenFaasConfig }): GardenPlug
         },
 
         async deployService(params: DeployServiceParams<OpenFaasModule>): Promise<ServiceStatus> {
-          const { ctx, module, service, logEntry, runtimeContext, buildDependencies } = params
+          const { ctx, module, service, logEntry, runtimeContext } = params
 
           // write the stack file again with environment variables
           await writeStackFile(ctx, module, runtimeContext.envVars)
 
           // use faas-cli to do the deployment
-          await execa("faas-cli", ["deploy", "-f", stackFilename], { cwd: module.buildPath })
+          await faasCli.stdout({
+            cwd: module.buildPath,
+            args: ["deploy", "-f", stackFilename],
+          })
 
           // wait until deployment is ready
           const k8sProvider = getK8sProvider(ctx)
@@ -251,7 +249,10 @@ export function gardenPlugin({ config }: { config: OpenFaasConfig }): GardenPlug
 
             found = !!status.state
 
-            await execa("faas-cli", ["remove", "-f", stackFilename], { cwd: service.module.buildPath })
+            await faasCli.stdout({
+              cwd: service.module.buildPath,
+              args: ["remove", "-f", stackFilename],
+            })
 
           } catch (err) {
             found = false
diff --git a/garden-service/src/util/ext-tools.ts b/garden-service/src/util/ext-tools.ts
index b612ae6885..61939cfcc8 100644
--- a/garden-service/src/util/ext-tools.ts
+++ b/garden-service/src/util/ext-tools.ts
@@ -7,9 +7,9 @@
  */
 
 import { platform, homedir } from "os"
-import { pathExists, createWriteStream, ensureDir } from "fs-extra"
+import { pathExists, createWriteStream, ensureDir, chmod, remove } from "fs-extra"
 import { ConfigurationError, ParameterError, GardenBaseError } from "../exceptions"
-import { join } from "path"
+import { join, dirname } from "path"
 import { hashString } from "./util"
 import Axios from "axios"
 import * as execa from "execa"
@@ -69,6 +69,7 @@ export class BinaryCmd extends Cmd {
   private targetFilename: string
   private downloadPath: string
   private executablePath: string
+  private defaultCwd: string
 
   constructor(spec: BinaryCmdSpec) {
     super()
@@ -93,6 +94,7 @@ export class BinaryCmd extends Cmd {
       ? this.spec.extract.executablePath
       : [this.name]
     this.executablePath = join(this.downloadPath, ...executableSubpath)
+    this.defaultCwd = dirname(this.executablePath)
   }
 
   private async download(logEntry?: LogEntry) {
@@ -174,9 +176,16 @@ export class BinaryCmd extends Cmd {
             ))
           }
 
-          debug && debug.setSuccess("Done")
-          logEntry && logEntry.setSuccess(`Fetched ${this.name}`)
-          resolve()
+          chmod(this.executablePath, 0o755, (chmodErr) => {
+            if (chmodErr) {
+              remove(this.downloadPath, () => reject(chmodErr))
+              return
+            }
+
+            debug && debug.setSuccess("Done")
+            logEntry && logEntry.setSuccess(`Fetched ${this.name}`)
+            resolve()
+          })
         })
       })
     })
@@ -184,7 +193,7 @@ export class BinaryCmd extends Cmd {
 
   async exec({ cwd, args, logEntry }: ExecParams) {
     await this.download(logEntry)
-    return execa(this.executablePath, args || [], { cwd })
+    return execa(this.executablePath, args || [], { cwd: cwd || this.defaultCwd })
   }
 
   async stdout(params: ExecParams) {
diff --git a/garden-service/static/openfaas/builder/garden.yml b/garden-service/static/openfaas/builder/garden.yml
index ff033983d2..33724ee6bd 100644
--- a/garden-service/static/openfaas/builder/garden.yml
+++ b/garden-service/static/openfaas/builder/garden.yml
@@ -1,6 +1,14 @@
 module:
-  description: Base image used for building openfaas modules
+  description: Used for pre-fetching templates, before building containers
   name: builder
   type: generic
   build:
-    command: ["faas-cli", "template", "pull"]
+    command: [
+      "rm", "-rf", "templates",
+      "&&",
+      "git", "clone", "https://github.com/openfaas/templates.git",
+      "&&",
+      "cd templates",
+      "&&",
+      "git", "checkout", "85fca87",
+    ]