Skip to content
Open
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
2 changes: 1 addition & 1 deletion commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (b *buildcmd) build(c *cli.Context) error {
buildArgs := c.StringSlice("build-arg")

// Passing empty shape for build command
ff, err = common.BuildFuncV20180708(common.IsVerbose(), fpath, ff, buildArgs, b.noCache, "")
ff, err = common.BuildFuncV20180708(common.IsVerbose(), fpath, ff, buildArgs, b.noCache, "", false)
if err != nil {
return err
}
Expand Down
26 changes: 16 additions & 10 deletions commands/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,15 @@ func DeployCommand() cli.Command {
type deploycmd struct {
clientV2 *v2Client.Fn

appName string
createApp bool
wd string
local bool
noCache bool
registry string
all bool
noBump bool
appName string
createApp bool
wd string
local bool
localDebug bool
noCache bool
registry string
all bool
noBump bool
}

func (p *deploycmd) flags() []cli.Flag {
Expand Down Expand Up @@ -147,6 +148,11 @@ func (p *deploycmd) flags() []cli.Flag {
Usage: "Do not push Docker built images onto Docker Hub - useful for local development.",
Destination: &p.local,
},
cli.BoolFlag{
Name: "local-debug",
Usage: "Build function image with remote debug options and deploy to local. It won't push Docker built images onto Docker Hub - useful for debugging in local development.",
Destination: &p.localDebug,
},
cli.StringFlag{
Name: "registry",
Usage: "Set the Docker owner for images and optionally the registry. This will be prefixed to your function name for pushing to Docker registries.\r eg: `--registry username` will set your Docker Hub owner. `--registry registry.hub.docker.com/username` will set the registry and owner. ",
Expand Down Expand Up @@ -387,7 +393,7 @@ func (p *deploycmd) deployFuncV20180708(c *cli.Context, app *models.App, funcfil

// In case of local ignore the architectures parameter
shape := ""
if !p.local {
if !p.local && !p.localDebug {
// fetch the architectures
shape = app.Shape
if shape == "" {
Expand All @@ -400,7 +406,7 @@ func (p *deploycmd) deployFuncV20180708(c *cli.Context, app *models.App, funcfil
}
}

_, err := common.BuildFuncV20180708(common.IsVerbose(), funcfilePath, funcfile, buildArgs, p.noCache, shape)
_, err := common.BuildFuncV20180708(common.IsVerbose(), funcfilePath, funcfile, buildArgs, p.noCache, shape, p.localDebug)
if err != nil {
return err
}
Expand Down
11 changes: 11 additions & 0 deletions commands/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ func StartCommand() cli.Command {
Usage: "Directory or container-engine volume where Fn Server creates the UNIX socket for " +
"cross-container communication. Defaults to named volume \"fnserversocket\"",
},
cli.BoolFlag{
Name: "local-debug",
Usage: "Start up function container with local-debug-port exposed for debugger to attach to the function container.",
},
cli.IntFlag{
Name: "local-debug-port",
Value: common.DefaultLocalDebugPort,
Usage: "Specify port number for function code local debugging.",
},
},
}
}
Expand Down Expand Up @@ -105,6 +114,8 @@ func start(c *cli.Context) error {
"-v", fmt.Sprintf("%s:/iofs:z", iofsDir),
"-e", fmt.Sprintf("FN_IOFS_DOCKER_PATH=%s", iofsDir),
"-e", "FN_IOFS_PATH=/iofs",
"-e", fmt.Sprintf("FN_LOCAL_DEBUG=%v", c.Bool("local-debug")),
"-e", fmt.Sprintf("FN_LOCAL_DEBUG_PORT=%d", c.Int("local-debug-port")),
"-v", fmt.Sprintf("%s/data:/app/data", fnDir),
"-v", "/var/run/docker.sock:/var/run/docker.sock",
"--privileged",
Expand Down
135 changes: 107 additions & 28 deletions common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/fnproject/cli/utils"
"io"
"io/ioutil"
"log"
Expand All @@ -33,6 +34,7 @@ import (
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
"unicode"

Expand All @@ -58,6 +60,7 @@ const (
BuildxBuilderInstance = "oci_fn_builder"
DefaultAppShape = modelsv2.AppShapeGENERICX86
containerEngineTypeDocker = "docker"
DefaultLocalDebugPort = 5678
)

var GlobalVerbose bool
Expand Down Expand Up @@ -139,7 +142,7 @@ func BuildFunc(verbose bool, fpath string, funcfile *FuncFile, buildArg []string
}

// BuildFunc bumps version and builds function.
func BuildFuncV20180708(verbose bool, fpath string, funcfile *FuncFileV20180708, buildArg []string, noCache bool, shape string) (*FuncFileV20180708, error) {
func BuildFuncV20180708(verbose bool, fpath string, funcfile *FuncFileV20180708, buildArg []string, noCache bool, shape string, localDebug bool) (*FuncFileV20180708, error) {
var err error

if funcfile.Version == "" {
Expand All @@ -157,7 +160,7 @@ func BuildFuncV20180708(verbose bool, fpath string, funcfile *FuncFileV20180708,
if err := localBuild(fpath, funcfile.Build); err != nil {
return nil, err
}
if err := containerEngineBuildV20180708(verbose, fpath, funcfile, buildArg, noCache, shape); err != nil {
if err := containerEngineBuildV20180708(verbose, fpath, funcfile, buildArg, noCache, shape, localDebug); err != nil {
return nil, err
}

Expand Down Expand Up @@ -365,7 +368,7 @@ func containerEngineBuild(verbose bool, fpath string, ff *FuncFile, buildArgs []
return nil
}

func containerEngineBuildV20180708(verbose bool, fpath string, ff *FuncFileV20180708, buildArgs []string, noCache bool, shape string) error {
func containerEngineBuildV20180708(verbose bool, fpath string, ff *FuncFileV20180708, buildArgs []string, noCache bool, shape string, localDebug bool) error {
containerEngineType, err := GetContainerEngineType()
if err != nil {
return err
Expand All @@ -390,7 +393,7 @@ func containerEngineBuildV20180708(verbose bool, fpath string, ff *FuncFileV2018
if helper == nil {
return fmt.Errorf("Cannot build, no language helper found for %v", ff.Runtime)
}
dockerfile, err = writeTmpDockerfileV20180708(helper, dir, ff)
dockerfile, err = writeTmpDockerfileV20180708(helper, dir, ff, localDebug)
if err != nil {
return err
}
Expand Down Expand Up @@ -762,7 +765,7 @@ func writeTmpDockerfile(helper langs.LangHelper, dir string, ff *FuncFile) (stri
dfLines = append(dfLines, fmt.Sprintf("FROM %s", bi))
}
dfLines = append(dfLines, "WORKDIR /function")
dfLines = append(dfLines, helper.DockerfileBuildCmds()...)
dfLines = append(dfLines, helper.DockerfileBuildCmds(false)...)
if helper.IsMultiStage() {
// final stage
ri := ff.RunImage
Expand All @@ -780,13 +783,13 @@ func writeTmpDockerfile(helper langs.LangHelper, dir string, ff *FuncFile) (stri
}
dfLines = append(dfLines, fmt.Sprintf("FROM %s", ri))
dfLines = append(dfLines, "WORKDIR /function")
dfLines = append(dfLines, helper.DockerfileCopyCmds()...)
dfLines = append(dfLines, helper.DockerfileCopyCmds(false)...)
}
if ff.Entrypoint != "" {
dfLines = append(dfLines, fmt.Sprintf("ENTRYPOINT [%s]", stringToSlice(ff.Entrypoint)))
dfLines = append(dfLines, fmt.Sprintf("ENTRYPOINT [%s]", utils.StringToSlice(ff.Entrypoint)))
}
if ff.Cmd != "" {
dfLines = append(dfLines, fmt.Sprintf("CMD [%s]", stringToSlice(ff.Cmd)))
dfLines = append(dfLines, fmt.Sprintf("CMD [%s]", utils.StringToSlice(ff.Cmd)))
}
err = writeLines(fd, dfLines)
if err != nil {
Expand All @@ -795,7 +798,7 @@ func writeTmpDockerfile(helper langs.LangHelper, dir string, ff *FuncFile) (stri
return fd.Name(), err
}

func writeTmpDockerfileV20180708(helper langs.LangHelper, dir string, ff *FuncFileV20180708) (string, error) {
func writeTmpDockerfileV20180708(helper langs.LangHelper, dir string, ff *FuncFileV20180708, localDebug bool) (string, error) {
if ff.Entrypoint == "" && ff.Cmd == "" {
return "", errors.New("entrypoint and cmd are missing, you must provide one or the other")
}
Expand Down Expand Up @@ -823,10 +826,11 @@ func writeTmpDockerfileV20180708(helper langs.LangHelper, dir string, ff *FuncFi
dfLines = append(dfLines, fmt.Sprintf("FROM %s", bi))
}
dfLines = append(dfLines, "WORKDIR /function")
dfLines = append(dfLines, helper.DockerfileBuildCmds()...)
dfLines = append(dfLines, helper.DockerfileBuildCmds(localDebug)...)

ri := ff.Run_image
if helper.IsMultiStage() {
// final stage
ri := ff.Run_image
if ri == "" {
ri, err = helper.RunFromImage()
if err != nil {
Expand All @@ -835,13 +839,36 @@ func writeTmpDockerfileV20180708(helper langs.LangHelper, dir string, ff *FuncFi
}
dfLines = append(dfLines, fmt.Sprintf("FROM %s", ri))
dfLines = append(dfLines, "WORKDIR /function")
dfLines = append(dfLines, helper.DockerfileCopyCmds()...)
dfLines = append(dfLines, helper.DockerfileCopyCmds(localDebug)...)
}
// if localDebug,
// if entrypoint is defined, add the debug options in the entry point
// else
// if the cmd is defined, add the debug options in the cmd
isDebugOptionInjected := false
// FDK java provides default entrypoint in fdk runtime image so we need to handle that
fdkDefaultEntrypoint, err := getEntrypointFromImage(ri)
if err != nil {
return "", err
}
finalEntrypoint := fdkDefaultEntrypoint
if ff.Entrypoint != "" {
dfLines = append(dfLines, fmt.Sprintf("ENTRYPOINT [%s]", stringToSlice(ff.Entrypoint)))
finalEntrypoint = ff.Entrypoint
}
if finalEntrypoint != "" {
if localDebug {
dfLines = append(dfLines, fmt.Sprintf("ENTRYPOINT [%s]", utils.StringToSlice(helper.DebugEntrypoint(finalEntrypoint))))
isDebugOptionInjected = true
} else {
dfLines = append(dfLines, fmt.Sprintf("ENTRYPOINT [%s]", utils.StringToSlice(finalEntrypoint)))
}
}
if ff.Cmd != "" {
dfLines = append(dfLines, fmt.Sprintf("CMD [%s]", stringToSlice(ff.Cmd)))
if localDebug && !isDebugOptionInjected {
dfLines = append(dfLines, fmt.Sprintf("CMD [%s]", utils.StringToSlice(helper.DebugCmd(ff.Cmd))))
} else {
dfLines = append(dfLines, fmt.Sprintf("CMD [%s]", utils.StringToSlice(ff.Cmd)))
}
}
err = writeLines(fd, dfLines)
if err != nil {
Expand All @@ -862,20 +889,6 @@ func writeLines(w io.Writer, lines []string) error {
return nil
}

func stringToSlice(in string) string {
epvals := strings.Fields(in)
var buffer bytes.Buffer
for i, s := range epvals {
if i > 0 {
buffer.WriteString(", ")
}
buffer.WriteString("\"")
buffer.WriteString(s)
buffer.WriteString("\"")
}
return buffer.String()
}

// ExtractConfig parses key-value configuration into a map
func ExtractConfig(configs []string) map[string]string {
c := make(map[string]string)
Expand Down Expand Up @@ -1240,3 +1253,69 @@ func untarStream(r io.Reader) error {
}
}
}

func PullImage(image string) error {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function was in objects/server/server.go. Moving it to common.

containerEngineType, err := GetContainerEngineType()
if err != nil {
return err
}

args := []string{"pull", image}
cmd := ShellCommander(containerEngineType, args...)
cmd.SetStdOut(os.Stdout)
cmd.SetStdErr(os.Stderr)
err = cmd.Start()
if err != nil {
log.Fatalln("Starting command failed:", err)
}

done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
// catch ctrl-c and kill
sigC := make(chan os.Signal, 2)
signal.Notify(sigC, os.Interrupt, syscall.SIGTERM)
select {
case <-sigC:
log.Println("Interrupt caught, exiting")
err = cmd.Kill()
if err != nil {
log.Println("Error: could not kill process")
}
case err := <-done:
if err != nil {
log.Println("Processed finished with error:", err)
} else {
log.Println("Process finished gracefully")
}
}
return nil
}

func getEntrypointFromImage(image string) (string, error) {
containerEngineType, err := GetContainerEngineType()
if err != nil {
return "", err
}
// Need to pull image before we can inspect entry point
err = PullImage(image)
if err != nil {
return "", err
}
cmd := ShellCommander(containerEngineType, "inspect", "-f", "'{{.Config.Entrypoint}}'", image)

// Capture stdout and stderr
var stdout, stderr bytes.Buffer

cmd.SetStdOut(&stdout)
cmd.SetStdErr(&stderr)

// Execute the command
if err := cmd.Run(); err != nil {
// Include Docker's stderr output to give more context
return "", fmt.Errorf("docker inspect failed: %v – %s", err, stderr.String())
}

return stdout.String(), nil
}
Loading