diff --git a/.gitignore b/.gitignore index 609d955..1395071 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ lambda-builder lambda.zip response.json +Procfile \ No newline at end of file diff --git a/builders/dotnet.go b/builders/dotnet.go index 25323ca..96bea31 100644 --- a/builders/dotnet.go +++ b/builders/dotnet.go @@ -23,10 +23,6 @@ func NewDotnetBuilder(config Config) (DotnetBuilder, error) { }, nil } -func (b DotnetBuilder) BuildImage() string { - return b.Config.BuilderBuildImage -} - func (b DotnetBuilder) Detect() bool { if io.FileExistsInDirectory(b.Config.WorkingDirectory, "Function.cs") { return true @@ -36,13 +32,22 @@ func (b DotnetBuilder) Detect() bool { } func (b DotnetBuilder) Execute() error { + b.Config.HandlerMap = b.GetHandlerMap() return executeBuilder(b.script(), b.GetTaskBuildDir(), b.Config) } +func (b DotnetBuilder) GetBuildImage() string { + return b.Config.BuilderBuildImage +} + func (b DotnetBuilder) GetConfig() Config { return b.Config } +func (b DotnetBuilder) GetHandlerMap() map[string]string { + return map[string]string{} +} + func (b DotnetBuilder) GetTaskBuildDir() string { return "/var/task" } diff --git a/builders/go.go b/builders/go.go index 7676b34..1ab475b 100644 --- a/builders/go.go +++ b/builders/go.go @@ -23,10 +23,6 @@ func NewGoBuilder(config Config) (GoBuilder, error) { }, nil } -func (b GoBuilder) BuildImage() string { - return b.Config.BuilderBuildImage -} - func (b GoBuilder) Detect() bool { if io.FileExistsInDirectory(b.Config.WorkingDirectory, "go.sum") { return true @@ -36,13 +32,24 @@ func (b GoBuilder) Detect() bool { } func (b GoBuilder) Execute() error { + b.Config.HandlerMap = b.GetHandlerMap() return executeBuilder(b.script(), b.GetTaskBuildDir(), b.Config) } +func (b GoBuilder) GetBuildImage() string { + return b.Config.BuilderBuildImage +} + func (b GoBuilder) GetConfig() Config { return b.Config } +func (b GoBuilder) GetHandlerMap() map[string]string { + return map[string]string{ + "bootstrap": "bootstrap", + } +} + func (b GoBuilder) GetTaskBuildDir() string { return "/go/src/handler" } diff --git a/builders/handler.go b/builders/handler.go new file mode 100644 index 0000000..0f475ad --- /dev/null +++ b/builders/handler.go @@ -0,0 +1,27 @@ +package builders + +import ( + "fmt" + "lambda-builder/io" + "os" + "path/filepath" +) + +func getFunctionHandler(directory string, config Config) string { + if config.Handler != "" { + return config.Handler + } + + for file, handler := range config.HandlerMap { + if io.FileExistsInDirectory(directory, file) { + return handler + } + } + + return "" +} + +func writeProcfile(handler string, directory string) error { + b := []byte(fmt.Sprintf("web: %s\n", handler)) + return os.WriteFile(filepath.Join(directory, "Procfile"), b, 0644) +} diff --git a/builders/main.go b/builders/main.go index ece3398..c13ac5b 100644 --- a/builders/main.go +++ b/builders/main.go @@ -14,11 +14,12 @@ import ( ) type Builder interface { - GetConfig() Config - GetTaskBuildDir() string Detect() bool Execute() error - BuildImage() string + GetBuildImage() string + GetConfig() Config + GetHandlerMap() map[string]string + GetTaskBuildDir() string Name() string } @@ -26,10 +27,13 @@ type Config struct { BuildImage bool BuilderBuildImage string BuilderRunImage string + Handler string + HandlerMap map[string]string Identifier string ImageLabels []string ImageTag string RunQuiet bool + WriteProcfile bool WorkingDirectory string } @@ -62,10 +66,29 @@ func executeBuilder(script string, taskBuildDir string, config Config) error { return err } + handler := getFunctionHandler(tmp, config) + if config.WriteProcfile && !io.FileExistsInDirectory(tmp, "Procfile") { + if handler == "" { + fmt.Printf(" ! Unable to detect handler in build directory\n") + } else { + fmt.Printf("=====> Writing Procfile from handler: %s\n", handler) + + fmt.Printf(" Writing to working directory\n") + if err := writeProcfile(handler, config.WorkingDirectory); err != nil { + return fmt.Errorf("error writing Procfile to working directory: %s", err.Error()) + } + + fmt.Printf(" Writing to build directory\n") + if err := writeProcfile(handler, tmp); err != nil { + return fmt.Errorf("error writing Procfile to temporary build directory: %s", err.Error()) + } + } + } + if config.BuildImage { fmt.Printf("=====> Building image\n") fmt.Printf(" Generating temporary Dockerfile\n") - if err := generateDockerfile(tmp, config); err != nil { + if err := generateDockerfile(handler, tmp, config); err != nil { return err } @@ -111,8 +134,8 @@ func executeBuildContainer(tmp string, script string, taskBuildDir string, confi return nil } -func generateDockerfile(tmp string, config Config) error { - dockerfileName := filepath.Join(tmp, fmt.Sprintf("%s.Dockerfile", config.Identifier)) +func generateDockerfile(cmd string, directory string, config Config) error { + dockerfileName := filepath.Join(directory, fmt.Sprintf("%s.Dockerfile", config.Identifier)) f, err := os.Create(dockerfileName) if err != nil { return fmt.Errorf("error creating Dockerfile: %s", err) @@ -120,6 +143,9 @@ func generateDockerfile(tmp string, config Config) error { tpl, err := template.New("t1").Parse(` FROM {{ .run_image }} +{{ if ne .command "" }} +CMD ["{{ .cmd }}"] +{{ end }} COPY . /var/task `) if err != nil { @@ -127,6 +153,7 @@ COPY . /var/task } data := map[string]string{ + "cmd": cmd, "run_image": config.BuilderRunImage, } @@ -137,11 +164,11 @@ COPY . /var/task return nil } -func buildDockerImage(tmp string, config Config) error { +func buildDockerImage(directory string, config Config) error { args := []string{ "image", "build", - "--file", filepath.Join(tmp, fmt.Sprintf("%s.Dockerfile", config.Identifier)), + "--file", filepath.Join(directory, fmt.Sprintf("%s.Dockerfile", config.Identifier)), "--progress", "plain", "--tag", config.GetImageTag(), } @@ -150,7 +177,7 @@ func buildDockerImage(tmp string, config Config) error { args = append(args, "--label", label) } - args = append(args, tmp) + args = append(args, directory) cmd := execute.ExecTask{ Args: args, diff --git a/builders/nodejs.go b/builders/nodejs.go index a12bfc9..b808a93 100644 --- a/builders/nodejs.go +++ b/builders/nodejs.go @@ -23,10 +23,6 @@ func NewNodejsBuilder(config Config) (NodejsBuilder, error) { }, nil } -func (b NodejsBuilder) BuildImage() string { - return b.Config.BuilderBuildImage -} - func (b NodejsBuilder) Detect() bool { if io.FileExistsInDirectory(b.Config.WorkingDirectory, "package-lock.json") { return true @@ -36,13 +32,25 @@ func (b NodejsBuilder) Detect() bool { } func (b NodejsBuilder) Execute() error { + b.Config.HandlerMap = b.GetHandlerMap() return executeBuilder(b.script(), b.GetTaskBuildDir(), b.Config) } +func (b NodejsBuilder) GetBuildImage() string { + return b.Config.BuilderBuildImage +} + func (b NodejsBuilder) GetConfig() Config { return b.Config } +func (b NodejsBuilder) GetHandlerMap() map[string]string { + return map[string]string{ + "function.js": "function.handler", + "lambda_function.js": "lambda_function.handler", + } +} + func (b NodejsBuilder) GetTaskBuildDir() string { return "/var/task" } diff --git a/builders/python.go b/builders/python.go index 8635c6c..5d5e1e3 100644 --- a/builders/python.go +++ b/builders/python.go @@ -43,10 +43,6 @@ func NewPythonBuilder(config Config) (PythonBuilder, error) { }, nil } -func (b PythonBuilder) BuildImage() string { - return b.Config.BuilderBuildImage -} - func (b PythonBuilder) Detect() bool { if io.FileExistsInDirectory(b.Config.WorkingDirectory, "requirements.txt") { return true @@ -64,13 +60,27 @@ func (b PythonBuilder) Detect() bool { } func (b PythonBuilder) Execute() error { + b.Config.HandlerMap = b.GetHandlerMap() return executeBuilder(b.script(), b.GetTaskBuildDir(), b.Config) } +func (b PythonBuilder) GetBuildImage() string { + return b.Config.BuilderBuildImage +} + func (b PythonBuilder) GetConfig() Config { return b.Config } +func (b PythonBuilder) GetHandlerMap() map[string]string { + return map[string]string{ + "app.py": "app.handler", + "function.py": "function.handler", + "lambda_function.py": "lambda_function.handler", + "main.py": "main.handler", + } +} + func (b PythonBuilder) GetTaskBuildDir() string { return "/var/task" } diff --git a/builders/ruby.go b/builders/ruby.go index d464fde..47d850e 100644 --- a/builders/ruby.go +++ b/builders/ruby.go @@ -23,10 +23,6 @@ func NewRubyBuilder(config Config) (RubyBuilder, error) { }, nil } -func (b RubyBuilder) BuildImage() string { - return b.Config.BuilderBuildImage -} - func (b RubyBuilder) Detect() bool { if io.FileExistsInDirectory(b.Config.WorkingDirectory, "Gemfile.lock") { return true @@ -35,15 +31,27 @@ func (b RubyBuilder) Detect() bool { return false } +func (b RubyBuilder) GetBuildImage() string { + return b.Config.BuilderBuildImage +} + func (b RubyBuilder) GetConfig() Config { return b.Config } +func (b RubyBuilder) GetHandlerMap() map[string]string { + return map[string]string{ + "function.rb": "function.handler", + "lambda_function.rb": "lambda_function.handler", + } +} + func (b RubyBuilder) GetTaskBuildDir() string { return "/var/task" } func (b RubyBuilder) Execute() error { + b.Config.HandlerMap = b.GetHandlerMap() return executeBuilder(b.script(), b.GetTaskBuildDir(), b.Config) } diff --git a/commands/build.go b/commands/build.go index 71894df..1cb446e 100644 --- a/commands/build.go +++ b/commands/build.go @@ -20,10 +20,12 @@ type BuildCommand struct { command.Meta buildImage bool + handler string imageTag string labels []string quiet bool workingDirectory string + writeProcfile bool } func (c *BuildCommand) Name() string { @@ -65,8 +67,10 @@ func (c *BuildCommand) FlagSet() *flag.FlagSet { } f := c.Meta.FlagSet(c.Name(), command.FlagSetClient) - f.BoolVar(&c.quiet, "quiet", false, "run builder in quiet mode") f.BoolVar(&c.buildImage, "build-image", false, "build a docker image") + f.BoolVar(&c.quiet, "quiet", false, "run builder in quiet mode") + f.BoolVar(&c.writeProcfile, "write-procfile", false, "writes a Procfile if a handler is specified or detected") + f.StringVar(&c.handler, "handler", "", "handler override to specify as the default command to run in a built image") f.StringVarP(&c.imageTag, "tag", "t", "", "name and optionally a tag in the 'name:tag' format") f.StringVar(&c.workingDirectory, "working-directory", workingDirectory, "working directory") f.StringArrayVar(&c.labels, "label", []string{}, " set metadata for an image") @@ -77,8 +81,9 @@ func (c *BuildCommand) AutocompleteFlags() complete.Flags { return command.MergeAutocompleteFlags( c.Meta.AutocompleteFlags(command.FlagSetClient), complete.Flags{ - "--build-image": complete.PredictNothing, - "--quiet": complete.PredictNothing, + "--build-image": complete.PredictNothing, + "--quiet": complete.PredictNothing, + "--write-procfile": complete.PredictNothing, }, ) } @@ -122,6 +127,7 @@ func (c *BuildCommand) Run(args []string) int { ImageLabels: c.labels, ImageTag: c.imageTag, RunQuiet: c.quiet, + WriteProcfile: c.writeProcfile, WorkingDirectory: c.workingDirectory, } @@ -134,7 +140,7 @@ func (c *BuildCommand) Run(args []string) int { c.Ui.Info(fmt.Sprintf("Detected %s builder", builder.Name())) - logger.LogHeader1(fmt.Sprintf("Building app with image %s", builder.BuildImage())) + logger.LogHeader1(fmt.Sprintf("Building app with image %s", builder.GetBuildImage())) if err := builder.Execute(); err != nil { c.Ui.Error(err.Error()) return 1 diff --git a/test.bats b/test.bats index adef60b..8c2c9e7 100644 --- a/test.bats +++ b/test.bats @@ -11,6 +11,15 @@ teardown_file() { make clean } +@test "[build] write procfile" { + skip "This test does not run correctly in Github Actions due to use of embedded docker" + run $LAMBDA_BUILDER_BIN build --working-directory tests/go --write-procfile + echo "output: $output" + echo "status: $status" + [[ "$status" -eq 0 ]] + [[ -f tests/go/Procfile ]] +} + @test "[build] dotnet6" { run $LAMBDA_BUILDER_BIN build --working-directory tests/dotnet6 echo "output: $output"