Skip to content

Commit

Permalink
Fixes openfaas#43 - refactor app.go into separate files/packages
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Ellis <[email protected]>
  • Loading branch information
alexellis committed Aug 21, 2017
1 parent 2bca54b commit 9621017
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 293 deletions.
308 changes: 17 additions & 291 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,19 @@ package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os/exec"
"strings"

yaml "gopkg.in/yaml.v2"

"net/http"

"encoding/json"

"bytes"
"os"

"net/url"
"github.com/alexellis/faas-cli/proxy"
"github.com/alexellis/faas-cli/stack"

"github.com/alexellis/faas/gateway/requests"
"github.com/alexellis/faas-cli/builder"
)

const providerName = "faas"
const defaultNetwork = "func_functions"

// GitCommit injected at build-time
var GitCommit string

func main() {
Expand Down Expand Up @@ -74,34 +65,16 @@ func main() {
yamlFile = yamlFileShort
}

var services Services
var services stack.Services
if len(yamlFile) > 0 {
var err error
var fileData []byte
urlParsed, err := url.Parse(yamlFile)
if err == nil && len(urlParsed.Scheme) > 0 {
fmt.Println("Parsed: " + urlParsed.String())
fileData, err = fetchYaml(urlParsed)
if err != nil {
fmt.Println(err.Error())
return
}
} else {
fileData, err = ioutil.ReadFile(yamlFile)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
return
}
}

err = yaml.Unmarshal(fileData, &services)
parsedServices, err := stack.ParseYAML(yamlFile)
if err != nil {
fmt.Printf("Error with YAML file: %s\n", err.Error())
log.Fatalln(err.Error())
return
}
if services.Provider.Name != providerName {
fmt.Printf("'%s' is the only valid provider for this tool - found: %s.\n", providerName, services.Provider.Name)
return

if parsedServices != nil {
services = *parsedServices
}
}

Expand All @@ -125,7 +98,7 @@ func main() {
function.Name = k
// fmt.Println(k, function)
fmt.Printf("Building: %s.\n", function.Name)
buildImage(function.Image, function.Handler, function.Name, function.Language, nocache, squash)
builder.BuildImage(function.Image, function.Handler, function.Name, function.Language, nocache, squash)
}
}
} else {
Expand All @@ -141,7 +114,7 @@ func main() {
fmt.Println("Please provide the deployed -name of your function.")
return
}
buildImage(image, handler, functionName, language, nocache, squash)
builder.BuildImage(image, handler, functionName, language, nocache, squash)
}
break
case "delete":
Expand All @@ -154,15 +127,15 @@ func main() {
function.Name = k
fmt.Printf("Deleting: %s.\n", function.Name)

deleteFunction(services.Provider.GatewayURL, function.Name)
proxy.DeleteFunction(services.Provider.GatewayURL, function.Name)
}
} else {
if len(functionName) == 0 {
fmt.Println("Please provide a -name for your function as it will be deployed on FaaS")
return
}
fmt.Printf("Deleting: %s.\n", functionName)
deleteFunction(gateway, functionName)
proxy.DeleteFunction(gateway, functionName)
}

break
Expand All @@ -177,7 +150,7 @@ func main() {
// fmt.Println(k, function)
fmt.Printf("Deploying: %s.\n", function.Name)

deployFunction(function.FProcess, services.Provider.GatewayURL, function.Name, function.Image, function.Language, replace, function.Environment, services.Provider.Network)
proxy.DeployFunction(function.FProcess, services.Provider.GatewayURL, function.Name, function.Image, function.Language, replace, function.Environment, services.Provider.Network)
}
} else {
if len(image) == 0 {
Expand All @@ -189,7 +162,7 @@ func main() {
return
}

deployFunction(fprocess, gateway, functionName, image, language, replace, map[string]string{}, defaultNetwork)
proxy.DeployFunction(fprocess, gateway, functionName, image, language, replace, map[string]string{}, defaultNetwork)
}
break
case "push":
Expand All @@ -209,255 +182,8 @@ func main() {
}
}

func deployFunction(fprocess string, gateway string, functionName string, image string, language string, replace bool, envVars map[string]string, network string) {

// Need to alter Gateway to allow nil/empty string as fprocess, to avoid this repetition.
var fprocessTemplate string
if len(fprocess) > 0 {
fprocessTemplate = fprocess
} else if language == "python" {
fprocessTemplate = "python index.py"
} else if language == "node" {
fprocessTemplate = "node index.js"
} else if language == "ruby" {
fprocessTemplate = "ruby index.rb"
} else if language == "csharp" {
fprocessTemplate = "dotnet ./bin/Debug/netcoreapp2.0/root.dll"
}

if replace {
deleteFunction(gateway, functionName)
}

// TODO: allow registry auth to be specified or read from local Docker credentials store
req := requests.CreateFunctionRequest{
EnvProcess: fprocessTemplate,
Image: image,
Network: "func_functions", // todo: specify network as an override
Service: functionName,
EnvVars: envVars,
}

reqBytes, _ := json.Marshal(&req)
reader := bytes.NewReader(reqBytes)
res, err := http.Post(gateway+"/system/functions", "application/json", reader)
if err != nil {
fmt.Println("Is FaaS deployed? Do you need to specify the -gateway flag?")
fmt.Println(err)
return
}

if res.Body != nil {
defer res.Body.Close()
}

switch res.StatusCode {
case 200, 201, 202:
fmt.Println("Deployed.")
default:
bytesOut, err := ioutil.ReadAll(res.Body)
if err == nil {
fmt.Println("Server returned unexpected status code", res.StatusCode, string(bytesOut))
}
}

fmt.Println(res.Status)

deployedURL := fmt.Sprintf("URL: %s/function/%s\n", gateway, functionName)
fmt.Println(deployedURL)
}

func deleteFunction(gateway string, functionName string) {
delReq := requests.DeleteFunctionRequest{FunctionName: functionName}
reqBytes, _ := json.Marshal(&delReq)
reader := bytes.NewReader(reqBytes)

c := http.Client{}
req, _ := http.NewRequest("DELETE", gateway+"/system/functions", reader)
req.Header.Set("Content-Type", "application/json")
delRes, delErr := c.Do(req)
if delErr != nil {
fmt.Printf("Error removing existing function: %s, gateway=%s, functionName=%s\n", delErr.Error(), gateway, functionName)
return
}

if delRes.Body != nil {
defer delRes.Body.Close()
}

switch delRes.StatusCode {
case 200, 201, 202:
fmt.Println("Removing old service.")
case 404:
fmt.Println("No existing service to remove")
default:
bytesOut, err := ioutil.ReadAll(delRes.Body)
if err == nil {
fmt.Println("Server returned unexpected status code", delRes.StatusCode, string(bytesOut))
}
}
}

func pushImage(image string) {
execBuild("./", []string{"docker", "push", image})
}

func buildImage(image string, handler string, functionName string, language string, nocache bool, squash bool) {

switch language {
case "node", "python", "ruby", "csharp":
tempPath := createBuildTemplate(functionName, handler, language)

fmt.Printf("Building: %s with Docker. Please wait..\n", image)

flagStr := buildFlagString(nocache, squash, os.Getenv("http_proxy"), os.Getenv("https_proxy"))

builder := strings.Split(fmt.Sprintf("docker build %s-t %s .", flagStr, image), " ")
fmt.Println(strings.Join(builder, " "))
execBuild(tempPath, builder)
default:
log.Fatalf("Language template: %s not supported. Build a custom Dockerfile instead.", language)
}

fmt.Printf("Image: %s built.\n", image)
}

// createBuildTemplate creates temporary build folder to perform a Docker build with Node template
func createBuildTemplate(functionName string, handler string, language string) string {
tempPath := fmt.Sprintf("./build/%s/", functionName)
fmt.Printf("Clearing temporary build folder: %s\n", tempPath)

clearErr := os.RemoveAll(tempPath)
if clearErr != nil {
fmt.Printf("Error clearing temporary build folder %s\n", tempPath)
}

fmt.Printf("Preparing %s %s\n", handler+"/", tempPath+"function")

functionPath := tempPath + "/function"
mkdirErr := os.MkdirAll(functionPath, 0700)
if mkdirErr != nil {
fmt.Printf("Error creating path %s - %s.\n", functionPath, mkdirErr.Error())
}

// Drop in directory tree from template
copyFiles("./template/"+language, tempPath, true)

// Overlay in user-function
copyFiles(handler, tempPath+"function/", false)

return tempPath
}

func copyFiles(src string, destination string, recursive bool) {

files, err := ioutil.ReadDir(src)
if err != nil {
log.Fatal(err)
}

for _, file := range files {

if file.IsDir() == false {

cp(src+"/"+file.Name(), destination+file.Name())

} else {

//make new destination dir
newDir := destination + file.Name() + "/"
if !pathExists(newDir) {

newDirErr := os.Mkdir(newDir, 0700)

if err != nil {
fmt.Printf("Error creating path %s - %s.\n", newDir, newDirErr.Error())
}
}

//did the call ask to recurse into sub directories?
if recursive == true {
//call copyTree to copy the contents
copyFiles(src+"/"+file.Name(), newDir, true)
}
}
}
}

func pathExists(path string) bool {
exists := true

if _, err := os.Stat(path); os.IsNotExist(err) {
exists = false
}

return exists
}

func cp(src string, destination string) error {

if val, exists := os.LookupEnv("debug"); exists && (val == "1" || val == "true") {
fmt.Printf("cp - %s %s\n", src, destination)
}

memoryBuffer, readErr := ioutil.ReadFile(src)
if readErr != nil {
return fmt.Errorf("Error reading source file: %s\n" + readErr.Error())
}
writeErr := ioutil.WriteFile(destination, memoryBuffer, 0660)
if writeErr != nil {
return fmt.Errorf("Error writing file: %s\n" + writeErr.Error())
}

return nil
}

func execBuild(tempPath string, builder []string) {
targetCmd := exec.Command(builder[0], builder[1:]...)
targetCmd.Dir = tempPath
targetCmd.Stdout = os.Stdout
targetCmd.Stderr = os.Stderr
targetCmd.Start()
targetCmd.Wait()
}

func fetchYaml(address *url.URL) ([]byte, error) {
req, err := http.NewRequest("GET", address.String(), nil)
if err != nil {
return nil, err
}
c := http.Client{}
res, err := c.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()

resBytes, err := ioutil.ReadAll(res.Body)

return resBytes, err
}

func buildFlagString(nocache bool, squash bool, httpProxy string, httpsProxy string) string {

buildFlags := ""

if nocache {
buildFlags += "--no-cache "
}
if squash {
buildFlags += "--squash "
}

if len(httpProxy) > 0 {
buildFlags += fmt.Sprintf("--build-arg http_proxy=%s ", httpProxy)
}

if len(httpsProxy) > 0 {
buildFlags += fmt.Sprintf("--build-arg https_proxy=%s ", httpsProxy)
}

return buildFlags
builder.ExecCommand("./", []string{"docker", "push", image})
}

func pullTemplates() error {
Expand Down
2 changes: 1 addition & 1 deletion build_samples.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/sh

./faas-cli -action build -yaml ./samples.yml
./faas-cli -action build -yaml ./samples.yml # -squash=true

docker images |head -n 4
Loading

0 comments on commit 9621017

Please sign in to comment.