Skip to content

Commit

Permalink
Adds build-server command for adding extensions. (fnproject#107)
Browse files Browse the repository at this point in the history
* adds build-server command for adding extensions.

* bug fix
  • Loading branch information
treeder authored Dec 6, 2017
1 parent 4b6e952 commit 52c4097
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 10 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# build stage
FROM golang:alpine AS build-env
FROM golang:1.9-alpine AS build-env
RUN apk --no-cache add build-base git bzr mercurial gcc
ENV D=/go/src/github.com/fnproject/cli
# If dep ever gets decent enough to use, try `dep ensure --vendor-only` from here: https://medium.com/travis-on-docker/triple-stage-docker-builds-with-go-and-angular-1b7d2006cb88
Expand Down
187 changes: 187 additions & 0 deletions build_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// This command builds a custom fn server with extensions compiled into it.
//
// NOTES:
// * We could just add extensions in the imports, but then there's no way to order them or potentially add extra config (although config should almost always be via env vars)

package main

import (
"errors"
"fmt"
"html/template"
"io/ioutil"
"os"

"github.com/urfave/cli"
yaml "gopkg.in/yaml.v2"
)

func buildServer() cli.Command {
cmd := buildServerCmd{}
flags := append([]cli.Flag{}, cmd.flags()...)
return cli.Command{
Name: "build-server",
Usage: "build custom fn server",
Flags: flags,
Action: cmd.buildServer,
}
}

type buildServerCmd struct {
verbose bool
noCache bool
}

func (b *buildServerCmd) flags() []cli.Flag {
return []cli.Flag{
cli.BoolFlag{
Name: "v",
Usage: "verbose mode",
Destination: &b.verbose,
},
cli.BoolFlag{
Name: "no-cache",
Usage: "Don't use docker cache",
Destination: &b.noCache,
},
cli.StringFlag{
Name: "tag,t",
Usage: "image name and optional tag",
},
}
}

// steps:
// • Yaml file with extensions listed
// • NO‎TE: All extensions should use env vars for config
// • ‎Generating main.go with extensions
// * Generate a Dockerfile that gets all the extensions (using dep)
// • ‎then generate a main.go with extensions
// • ‎compile, throw in another container like main dockerfile
func (b *buildServerCmd) buildServer(c *cli.Context) error {

if c.String("tag") == "" {
return errors.New("docker tag required")
}

// path, err := os.Getwd()
// if err != nil {
// return err
// }
fpath := "ext.yaml"
bb, err := ioutil.ReadFile(fpath)
if err != nil {
return fmt.Errorf("could not open %s for parsing. Error: %v", fpath, err)
}
ef := &extFile{}
err = yaml.Unmarshal(bb, ef)
if err != nil {
return err
}

err = os.MkdirAll("tmp", 0777)
if err != nil {
return err
}
err = os.Chdir("tmp")
if err != nil {
return err
}
err = generateMain(ef)
if err != nil {
return err
}
err = generateDockerfile()
if err != nil {
return err
}
dir, err := os.Getwd()
if err != nil {
return err
}
err = runBuild(dir, c.String("tag"), "Dockerfile", false)
if err != nil {
return err
}
fmt.Printf("Custom Fn server built successfully.\n")
return nil
}

func generateMain(ef *extFile) error {
tmpl, err := template.New("main").Parse(mainTmpl)
if err != nil {
return err
}
f, err := os.Create("main.go")
if err != nil {
return err
}
defer f.Close()
err = tmpl.Execute(f, ef)
if err != nil {
return err
}
return nil
}

func generateDockerfile() error {
if err := ioutil.WriteFile("Dockerfile", []byte(dockerFileTmpl), os.FileMode(0644)); err != nil {
return err
}
return nil
}

type extFile struct {
Extensions []*extInfo `yaml:"extensions"`
}

type extInfo struct {
Name string `yaml:"name"`
// will have version and other things down the road
}

var mainTmpl = `package main
import (
"context"
"github.com/fnproject/fn/api/server"
{{- range .Extensions }}
_ "{{ .Name }}"
{{- end}}
)
func main() {
ctx := context.Background()
funcServer := server.NewFromEnv(ctx)
{{- range .Extensions }}
funcServer.AddExtensionByName("{{ .Name }}")
{{- end}}
funcServer.Start(ctx)
}
`

// NOTE: Getting build errors with dep, probably because our vendor dir is wack. Might work again once we switch to dep.
// vendor/github.com/fnproject/fn/api/agent/drivers/docker/registry.go:93: too many arguments in call to client.NewRepository
// have ("context".Context, reference.Named, string, http.RoundTripper) want (reference.Named, string, http.RoundTripper)
// go build github.com/x/y/vendor/github.com/rdallman/migrate/database/mysql: no buildable Go source files in /go/src/github.com/x/y/vendor/github.com/rdallman/migrate/database/mysql
// # github.com/x/y/vendor/github.com/openzipkin/zipkin-go-opentracing/thrift/gen-go/scribe
// vendor/github.com/openzipkin/zipkin-go-opentracing/thrift/gen-go/scribe/scribe.go:210: undefined: thrift.TClient
var dockerFileTmpl = `# build stage
FROM golang:1.9-alpine AS build-env
RUN apk --no-cache add build-base git bzr mercurial gcc
# RUN go get -u github.com/golang/dep/cmd/dep
ENV D=/go/src/github.com/x/y
ADD main.go $D/
RUN cd $D && go get
# RUN cd $D && dep init && dep ensure
RUN cd $D && go build -o fnserver && cp fnserver /tmp/
# final stage
FROM alpine
RUN apk add --no-cache ca-certificates curl
WORKDIR /app
COPY --from=build-env /tmp/fnserver /app/fnserver
ENTRYPOINT ["./fnserver"]
`
24 changes: 16 additions & 8 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,21 @@ func dockerBuild(fpath string, ff *funcfile, noCache bool) error {

fmt.Printf("Building image %v\n", ff.ImageName())

err = runBuild(dir, ff.ImageName(), dockerfile, noCache)
if err != nil {
return err
}

if helper != nil {
err := helper.AfterBuild()
if err != nil {
return err
}
}
return nil
}

func runBuild(dir, imageName, dockerfile string, noCache bool) error {
cancel := make(chan os.Signal, 3)
signal.Notify(cancel, os.Interrupt) // and others perhaps
defer signal.Stop(cancel)
Expand All @@ -121,7 +136,7 @@ func dockerBuild(fpath string, ff *funcfile, noCache bool) error {
go func(done chan<- error) {
args := []string{
"build",
"-t", ff.ImageName(),
"-t", imageName,
"-f", dockerfile,
}
if noCache {
Expand All @@ -146,13 +161,6 @@ func dockerBuild(fpath string, ff *funcfile, noCache bool) error {
case signal := <-cancel:
return fmt.Errorf("build cancelled on signal %v", signal)
}

if helper != nil {
err := helper.AfterBuild()
if err != nil {
return err
}
}
return nil
}

Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ GLOBAL OPTIONS:
calls(),
logs(),
testfn(),
buildServer(),
}
app.Commands = append(app.Commands, aliasesFn()...)

Expand Down
2 changes: 1 addition & 1 deletion testfn.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (t *testcmd) flags() []cli.Flag {
// },
cli.StringFlag{
Name: "remote",
Usage: "run tests by calling the function on `appname`",
Usage: "run tests against a remote fn server",
Destination: &t.remote,
},
}
Expand Down

0 comments on commit 52c4097

Please sign in to comment.