github.com/tonto/cli@v0.0.0-20180104210444-aec958fa47db/build_server.go (about)

     1  // This command builds a custom fn server with extensions compiled into it.
     2  //
     3  // NOTES:
     4  // * 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)
     5  
     6  package main
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"html/template"
    12  	"io/ioutil"
    13  	"os"
    14  
    15  	"github.com/urfave/cli"
    16  	yaml "gopkg.in/yaml.v2"
    17  )
    18  
    19  func buildServer() cli.Command {
    20  	cmd := buildServerCmd{}
    21  	flags := append([]cli.Flag{}, cmd.flags()...)
    22  	return cli.Command{
    23  		Name:   "build-server",
    24  		Usage:  "build custom fn server",
    25  		Flags:  flags,
    26  		Action: cmd.buildServer,
    27  	}
    28  }
    29  
    30  type buildServerCmd struct {
    31  	verbose bool
    32  	noCache bool
    33  }
    34  
    35  func (b *buildServerCmd) flags() []cli.Flag {
    36  	return []cli.Flag{
    37  		cli.BoolFlag{
    38  			Name:        "v",
    39  			Usage:       "verbose mode",
    40  			Destination: &b.verbose,
    41  		},
    42  		cli.BoolFlag{
    43  			Name:        "no-cache",
    44  			Usage:       "Don't use docker cache",
    45  			Destination: &b.noCache,
    46  		},
    47  		cli.StringFlag{
    48  			Name:  "tag,t",
    49  			Usage: "image name and optional tag",
    50  		},
    51  	}
    52  }
    53  
    54  // steps:
    55  // • Yaml file with extensions listed
    56  // • NO‎TE: All extensions should use env vars for config
    57  // • ‎Generating main.go with extensions
    58  // * Generate a Dockerfile that gets all the extensions (using dep)
    59  // • ‎then generate a main.go with extensions
    60  // • ‎compile, throw in another container like main dockerfile
    61  func (b *buildServerCmd) buildServer(c *cli.Context) error {
    62  
    63  	if c.String("tag") == "" {
    64  		return errors.New("docker tag required")
    65  	}
    66  
    67  	// path, err := os.Getwd()
    68  	// if err != nil {
    69  	// 	return err
    70  	// }
    71  	fpath := "ext.yaml"
    72  	bb, err := ioutil.ReadFile(fpath)
    73  	if err != nil {
    74  		return fmt.Errorf("could not open %s for parsing. Error: %v", fpath, err)
    75  	}
    76  	ef := &extFile{}
    77  	err = yaml.Unmarshal(bb, ef)
    78  	if err != nil {
    79  		return err
    80  	}
    81  
    82  	err = os.MkdirAll("tmp", 0777)
    83  	if err != nil {
    84  		return err
    85  	}
    86  	err = os.Chdir("tmp")
    87  	if err != nil {
    88  		return err
    89  	}
    90  	err = generateMain(ef)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	err = generateDockerfile()
    95  	if err != nil {
    96  		return err
    97  	}
    98  	dir, err := os.Getwd()
    99  	if err != nil {
   100  		return err
   101  	}
   102  	err = runBuild(c, dir, c.String("tag"), "Dockerfile", b.noCache)
   103  	if err != nil {
   104  		return err
   105  	}
   106  	fmt.Printf("Custom Fn server built successfully.\n")
   107  	return nil
   108  }
   109  
   110  func generateMain(ef *extFile) error {
   111  	tmpl, err := template.New("main").Parse(mainTmpl)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	f, err := os.Create("main.go")
   116  	if err != nil {
   117  		return err
   118  	}
   119  	defer f.Close()
   120  	err = tmpl.Execute(f, ef)
   121  	if err != nil {
   122  		return err
   123  	}
   124  	return nil
   125  }
   126  
   127  func generateDockerfile() error {
   128  	if err := ioutil.WriteFile("Dockerfile", []byte(dockerFileTmpl), os.FileMode(0644)); err != nil {
   129  		return err
   130  	}
   131  	return nil
   132  }
   133  
   134  type extFile struct {
   135  	Extensions []*extInfo `yaml:"extensions"`
   136  }
   137  
   138  type extInfo struct {
   139  	Name string `yaml:"name"`
   140  	// will have version and other things down the road
   141  }
   142  
   143  var mainTmpl = `package main
   144  
   145  import (
   146  	"context"
   147  
   148  	"github.com/fnproject/fn/api/server"
   149  	
   150  	{{- range .Extensions }}
   151  		_ "{{ .Name }}"
   152  	{{- end}}
   153  )
   154  
   155  func main() {
   156  	ctx := context.Background()
   157  	funcServer := server.NewFromEnv(ctx)
   158  	{{- range .Extensions }}
   159  		funcServer.AddExtensionByName("{{ .Name }}")
   160  	{{- end}}
   161  	funcServer.Start(ctx)
   162  }
   163  `
   164  
   165  // NOTE: Getting build errors with dep, probably because our vendor dir is wack. Might work again once we switch to dep.
   166  // vendor/github.com/fnproject/fn/api/agent/drivers/docker/registry.go:93: too many arguments in call to client.NewRepository
   167  // have ("context".Context, reference.Named, string, http.RoundTripper) want (reference.Named, string, http.RoundTripper)
   168  // 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
   169  // # github.com/x/y/vendor/github.com/openzipkin/zipkin-go-opentracing/thrift/gen-go/scribe
   170  // vendor/github.com/openzipkin/zipkin-go-opentracing/thrift/gen-go/scribe/scribe.go:210: undefined: thrift.TClient
   171  var dockerFileTmpl = `# build stage
   172  FROM golang:1.9-alpine AS build-env
   173  RUN apk --no-cache add build-base git bzr mercurial gcc
   174  # RUN go get -u github.com/golang/dep/cmd/dep
   175  ENV D=/go/src/github.com/x/y
   176  ADD main.go $D/
   177  RUN cd $D && go get
   178  # RUN cd $D && dep init && dep ensure
   179  RUN cd $D && go build -o fnserver && cp fnserver /tmp/
   180  
   181  # final stage
   182  FROM fnproject/dind
   183  RUN apk add --no-cache ca-certificates
   184  WORKDIR /app
   185  COPY --from=build-env /tmp/fnserver /app/fnserver
   186  CMD ["./fnserver"]
   187  `