Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create faas-cli add-template --url <URL> command #87

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package builder

import (
"fmt"
"github.com/openfaas/faas-cli/stack"
"io/ioutil"
"log"
"os"
Expand All @@ -14,29 +15,26 @@ import (
// BuildImage construct Docker image from function parameters
func BuildImage(image string, handler string, functionName string, language string, nocache bool, squash bool) {

switch language {
case "node", "python", "ruby", "csharp", "python3", "go":
tempPath := createBuildTemplate(functionName, handler, language)
if stack.IsValidTemplate(language) {

fmt.Printf("Building: %s with %s template. Please wait..\n", image, language)
var tempPath string
if strings.ToLower(language) == "dockerfile" {

flagStr := buildFlagString(nocache, squash, os.Getenv("http_proxy"), os.Getenv("https_proxy"))
tempPath = handler
if _, err := os.Stat(handler); err != nil {
fmt.Printf("Unable to build %s, %s is an invalid path\n", image, handler)
fmt.Printf("Image: %s not built.\n", image)

builder := strings.Split(fmt.Sprintf("docker build %s-t %s .", flagStr, image), " ")
fmt.Println(strings.Join(builder, " "))
ExecCommand(tempPath, builder)
fmt.Printf("Image: %s built.\n", image)
return
}
fmt.Printf("Building: %s with Dockerfile. Please wait..\n", image)

} else {

break
case "Dockerfile", "dockerfile":
tempPath := handler
if _, err := os.Stat(handler); err != nil {
fmt.Printf("Unable to build %s, %s is an invalid path\n", image, handler)
fmt.Printf("Image: %s not built.\n", image)
tempPath = createBuildTemplate(functionName, handler, language)
fmt.Printf("Building: %s with %s template. Please wait..\n", image, language)

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

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

Expand All @@ -45,7 +43,7 @@ func BuildImage(image string, handler string, functionName string, language stri
ExecCommand(tempPath, builder)
fmt.Printf("Image: %s built.\n", image)

default:
} else {
log.Fatalf("Language template: %s not supported. Build a custom Dockerfile instead.", language)
}
}
Expand Down
80 changes: 80 additions & 0 deletions commands/add_template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) Alex Ellis, Eric Stoekl 2017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package commands

import (
"errors"

"fmt"

"os"

"regexp"

"github.com/spf13/cobra"
)

// Args and Flags that are to be added to commands

const (
repositoryRegexpMockedServer = `^http://127.0.0.1:\d+/([a-z0-9-]+)/([a-z0-9-]+)$`
repositoryRegexpGithub = `^https://github.com/([a-z0-9-]+)/([a-z0-9-]+)/?$`
)

var (
repository string
overwrite bool
)

var supportedVerbs = [...]string{"pull"}

func init() {
addTemplateCmd.Flags().BoolVar(&overwrite, "overwrite", false, "Overwrite existing templates?")

faasCmd.AddCommand(addTemplateCmd)
}

// addTemplateCmd allows the user to fetch a template from a repository
var addTemplateCmd = &cobra.Command{
Use: "template pull <repository URL>",
Args: func(cmd *cobra.Command, args []string) error {
msg := fmt.Sprintf(`Must use a supported verb for 'faas-cli template'
Currently supported verbs: %v`, supportedVerbs)

if len(args) == 0 {
return errors.New(msg)
}

if args[0] != "pull" {
return errors.New(msg)
}

if len(args) > 1 {
var validURL = regexp.MustCompile(repositoryRegexpGithub + "|" + repositoryRegexpMockedServer)

if !validURL.MatchString(args[1]) {
return errors.New("The repository URL must be in the format https://github.com/<owner>/<repository>")
}
}
return nil
},
Short: "Downloads templates from the specified github repo",
Long: `Downloads the compressed github repo specified by [URL], and extracts the 'template'
directory from the root of the repo, if it exists.`,
Example: "faas-cli template pull https://github.com/openfaas/faas-cli",
Run: runAddTemplate,
}

func runAddTemplate(cmd *cobra.Command, args []string) {
repository := ""
if len(args) > 1 {
repository = args[1]
}

fmt.Println("Fetch templates from repository: " + repository)
if err := fetchTemplates(repository, overwrite); err != nil {
fmt.Println(err)

os.Exit(1)
}
}
144 changes: 144 additions & 0 deletions commands/add_template_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright (c) Alex Ellis, Eric Stoekl 2017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package commands

import (
"bytes"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"os"
"regexp"
"strings"
"testing"
)

func Test_addTemplate(t *testing.T) {
defer tearDown_fetch_templates(t)

ts := httpTestServer(t)
defer ts.Close()

repository = ts.URL + "/owner/repo"
faasCmd.SetArgs([]string{"template", "pull", repository})
faasCmd.Execute()

// Verify created directories
if _, err := os.Stat("template"); err != nil {
t.Fatalf("The directory %s was not created", "template")
}
}

func Test_addTemplate_with_overwriting(t *testing.T) {
defer tearDown_fetch_templates(t)

ts := httpTestServer(t)
defer ts.Close()

repository = ts.URL + "/owner/repo"
faasCmd.SetArgs([]string{"template", "pull", repository})
faasCmd.Execute()

var buf bytes.Buffer
log.SetOutput(&buf)

r := regexp.MustCompile(`(?m:Cannot overwrite the following \d+ directories:)`)

faasCmd.SetArgs([]string{"template", "pull", repository})
faasCmd.Execute()

if !r.MatchString(buf.String()) {
t.Fatal(buf.String())
}

buf.Reset()

faasCmd.SetArgs([]string{"template", "pull", repository, "--overwrite"})
faasCmd.Execute()

str := buf.String()
if r.MatchString(str) {
t.Fatal()
}

// Verify created directories
if _, err := os.Stat("template"); err != nil {
t.Fatalf("The directory %s was not created", "template")
}
}

func Test_addTemplate_no_arg(t *testing.T) {
defer tearDown_fetch_templates(t)
var buf bytes.Buffer

faasCmd.SetArgs([]string{"template", "pull"})
faasCmd.SetOutput(&buf)
faasCmd.Execute()

if strings.Contains(buf.String(), "Error: A repository URL must be specified") {
t.Fatal("Output does not contain the required string")
}
}

func Test_addTemplate_error_not_valid_url(t *testing.T) {
var buf bytes.Buffer

faasCmd.SetArgs([]string{"template", "pull", "git@github.com:openfaas/faas-cli.git"})
faasCmd.SetOutput(&buf)
faasCmd.Execute()

if !strings.Contains(buf.String(), "Error: The repository URL must be in the format https://github.com/<owner>/<repository>") {
t.Fatal("Output does not contain the required string", buf.String())
}
}

// httpTestServer returns a testing http server
func httpTestServer(t *testing.T) *httptest.Server {
const sampleMasterZipPath string = "testdata/master_test.zip"
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

if _, err := os.Stat(sampleMasterZipPath); os.IsNotExist(err) {
t.Error(err)
}

fileData, err := ioutil.ReadFile(sampleMasterZipPath)
if err != nil {
t.Error(err)
}

w.Write(fileData)
}))

return ts
}

func Test_repositoryUrlRegExp(t *testing.T) {
var url string
r := regexp.MustCompile(repositoryRegexpGithub)

url = "http://github.com/owner/repo"
if r.MatchString(url) {
t.Errorf("Url %s must start with https", url)
}

url = "https://github.com/owner/repo.git"
if r.MatchString(url) {
t.Errorf("Url %s must not end with .git or must start with https", url)
}

url = "https://github.com/owner/repo//"
if r.MatchString(url) {
t.Errorf("Url %s must end with no or one slash", url)
}

url = "https://github.com/owner/repo"
if !r.MatchString(url) {
t.Errorf("Url %s must be valid", url)
}

url = "https://github.com/owner/repo/"
if !r.MatchString(url) {
t.Errorf("Url %s must be valid", url)
}
}
6 changes: 3 additions & 3 deletions commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func runBuild(cmd *cobra.Command, args []string) error {
}
}

if pullErr := PullTemplates(""); pullErr != nil {
if pullErr := PullTemplates(); pullErr != nil {
return fmt.Errorf("could not pull templates for OpenFaaS: %v", pullErr)
}

Expand Down Expand Up @@ -139,13 +139,13 @@ func build(services *stack.Services, queueDepth int) {
}

// PullTemplates pulls templates from Github from the master zip download file.
func PullTemplates(templateUrl string) error {
func PullTemplates() error {
var err error
exists, err := os.Stat("./template")
if err != nil || exists == nil {
log.Println("No templates found in current directory.")

err = fetchTemplates(templateUrl)
err = fetchTemplates("", false)
if err != nil {
log.Println("Unable to download templates from Github.")
return err
Expand Down
24 changes: 23 additions & 1 deletion commands/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ var deployCmd = &cobra.Command{
[--handler HANDLER_DIR]
[--fprocess PROCESS]
[--env ENVVAR=VALUE ...]
[--label LABEL=VALUE ...]
[--label LABEL=VALUE ...]
[--replace=false]
[--update=false]
[--constraint PLACEMENT_CONSTRAINT ...]
Expand Down Expand Up @@ -160,6 +160,28 @@ func runDeploy(cmd *cobra.Command, args []string) {
log.Fatalln(envErr)
}

// Get FProcess to use from the ./template/template.yml, if a template is being used
if function.Language != "" && function.Language != "Dockerfile" && function.Language != "dockerfile" {
pathToTemplateYAML := "./template/" + function.Language + "/template.yml"
if _, err := os.Stat(pathToTemplateYAML); os.IsNotExist(err) {
log.Fatalln(err.Error())
return
}

var langTemplate stack.LanguageTemplate
parsedLangTemplate, err := stack.ParseYAMLForLanguageTemplate(pathToTemplateYAML)

if err != nil {
log.Fatalln(err.Error())
return
}

if parsedLangTemplate != nil {
langTemplate = *parsedLangTemplate
function.FProcess = langTemplate.FProcess
}
}

proxy.DeployFunction(function.FProcess, services.Provider.GatewayURL, function.Name, function.Image, function.Language, replace, allEnvironment, services.Provider.Network, constraints, update, secrets, allLabels)
}
} else {
Expand Down
Loading