github.com/fnproject/cli@v0.0.0-20240508150455-e5d88bd86117/commands/build_server.go (about)

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