Skip to content

Commit db0945e

Browse files
authored
fix: func run now uses Docker API, not binary
Signed-off-by: Matej Vasek <mvasek@redhat.com>
1 parent 8daa725 commit db0945e

File tree

10 files changed

+153
-47
lines changed

10 files changed

+153
-47
lines changed

client.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package function
22

33
import (
4+
"context"
45
"fmt"
56
"io"
67
"io/ioutil"
@@ -54,7 +55,7 @@ type Deployer interface {
5455
// Runner runs the Function locally.
5556
type Runner interface {
5657
// Run the Function locally.
57-
Run(Function) error
58+
Run(context.Context, Function) error
5859
}
5960

6061
// Remover of deployed services.
@@ -444,7 +445,7 @@ func (c *Client) Route(path string) (err error) {
444445
}
445446

446447
// Run the Function whose code resides at root.
447-
func (c *Client) Run(root string) error {
448+
func (c *Client) Run(ctx context.Context, root string) error {
448449

449450
// Create an instance of a Function representation at the given root.
450451
f, err := NewFunction(root)
@@ -458,7 +459,7 @@ func (c *Client) Run(root string) error {
458459
}
459460

460461
// delegate to concrete implementation of runner entirely.
461-
return c.runner.Run(f)
462+
return c.runner.Run(ctx, f)
462463
}
463464

464465
// List currently deployed Functions.
@@ -527,7 +528,7 @@ func (n *noopDeployer) Deploy(_ Function) error { return nil }
527528

528529
type noopRunner struct{ output io.Writer }
529530

530-
func (n *noopRunner) Run(_ Function) error { return nil }
531+
func (n *noopRunner) Run(_ context.Context, _ Function) error { return nil }
531532

532533
type noopRemover struct{ output io.Writer }
533534

client_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package function_test
44

55
import (
6+
"context"
67
"fmt"
78
"io/ioutil"
89
"os"
@@ -429,7 +430,7 @@ func TestRun(t *testing.T) {
429430
}
430431

431432
// Run the newly created function
432-
if err := client.Run(root); err != nil {
433+
if err := client.Run(context.Background(), root); err != nil {
433434
t.Fatal(err)
434435
}
435436

cmd/func/main.go

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,29 @@
11
package main
22

33
import (
4+
"context"
45
"github.com/boson-project/func/cmd"
6+
"os"
7+
"os/signal"
8+
"syscall"
59
)
610

711
// Statically-populated build metadata set
812
// by `make build`.
913
var date, vers, hash string
1014

1115
func main() {
16+
ctx, cancel := context.WithCancel(context.Background())
17+
defer cancel()
18+
19+
sigs := make(chan os.Signal, 1)
20+
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
21+
22+
go func() {
23+
<-sigs
24+
cancel()
25+
}()
26+
1227
cmd.SetMeta(date, vers, hash)
13-
cmd.Execute()
28+
cmd.Execute(ctx)
1429
}

cmd/root.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
"context"
45
"fmt"
56
"os"
67
"path/filepath"
@@ -71,12 +72,11 @@ func init() {
7172
// Execute the command tree by executing the root command, which runs
7273
// according to the context defined by: the optional config file,
7374
// Environment Variables, command arguments and flags.
74-
func Execute() {
75+
func Execute(ctx context.Context) {
7576
// Sets version to a string partially populated by compile-time flags.
7677
root.Version = version.String()
77-
7878
// Execute the root of the command tree.
79-
if err := root.Execute(); err != nil {
79+
if err := root.ExecuteContext(ctx); err != nil {
8080
// Errors are printed to STDERR output and the process exits with code of 1.
8181
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
8282
os.Exit(1)

cmd/run.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package cmd
22

33
import (
4+
"context"
45
"fmt"
6+
"github.com/pkg/errors"
57

68
"github.com/ory/viper"
79
"github.com/spf13/cobra"
@@ -64,7 +66,11 @@ func runRun(cmd *cobra.Command, args []string) (err error) {
6466
bosonFunc.WithRunner(runner),
6567
bosonFunc.WithVerbose(config.Verbose))
6668

67-
return client.Run(config.Path)
69+
err = client.Run(cmd.Context(), config.Path)
70+
if errors.Cause(err) == context.Canceled {
71+
err = nil
72+
}
73+
return
6874
}
6975

7076
type runConfig struct {

docker/runner.go

+95-33
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package docker
22

33
import (
4-
"errors"
4+
"context"
55
"fmt"
6+
"github.com/docker/docker/api/types"
7+
"github.com/docker/docker/api/types/container"
8+
"github.com/docker/docker/pkg/stdcopy"
9+
"github.com/docker/go-connections/nat"
10+
"github.com/pkg/errors"
611
"os"
7-
"os/exec"
812
"strings"
13+
"time"
14+
15+
"github.com/docker/docker/client"
916

1017
bosonFunc "github.com/boson-project/func"
1118
)
@@ -22,52 +29,107 @@ func NewRunner() *Runner {
2229
}
2330

2431
// Run the function at path
25-
func (n *Runner) Run(f bosonFunc.Function) error {
26-
// Check for the docker binary explicitly so that we can return
27-
// an extra-friendly error message.
28-
_, err := exec.LookPath("docker")
32+
func (n *Runner) Run(ctx context.Context, f bosonFunc.Function) error {
33+
ctx, cancel := context.WithCancel(ctx)
34+
defer cancel()
35+
36+
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
2937
if err != nil {
30-
return errors.New("please install 'docker'")
38+
return errors.Wrap(err, "failed to create docker api client")
3139
}
3240

3341
if f.Image == "" {
3442
return errors.New("Function has no associated Image. Has it been built?")
3543
}
3644

37-
// Extra arguments to docker
38-
args := []string{"run", "--rm", "-t", "-p=8080:8080"}
39-
45+
envs := make([]string, 0, len(f.EnvVars)+1)
4046
for name, value := range f.EnvVars {
4147
if !strings.HasSuffix(name, "-") {
42-
args = append(args, fmt.Sprintf("-e%s=%s", name, value))
48+
envs = append(envs, fmt.Sprintf("%s=%s", name, value))
4349
}
4450
}
45-
46-
// If verbosity is enabled, pass along as an environment variable to the Function.
4751
if n.Verbose {
48-
args = append(args, "-e VERBOSE=true")
52+
envs = append(envs, "VERBOSE=true")
4953
}
50-
args = append(args, f.Image)
5154

52-
// Set up the command with extra arguments and to run rooted at path
53-
cmd := exec.Command("docker", args...)
54-
cmd.Dir = f.Root
55+
ports := map[nat.Port][]nat.PortBinding{
56+
nat.Port("8080/tcp"): {
57+
nat.PortBinding{
58+
HostPort: "8080",
59+
HostIP: "127.0.0.1",
60+
},
61+
},
62+
}
5563

56-
// If verbose logging is enabled, echo command
57-
if n.Verbose {
58-
fmt.Println(cmd)
64+
conf := &container.Config{
65+
Env: envs,
66+
Tty: false,
67+
AttachStderr: true,
68+
AttachStdout: true,
69+
AttachStdin: false,
70+
Image: f.Image,
71+
}
72+
73+
hostConf := &container.HostConfig{
74+
PortBindings: ports,
75+
}
76+
77+
cont, err := cli.ContainerCreate(ctx, conf, hostConf, nil, nil, "")
78+
if err != nil {
79+
return errors.Wrap(err, "failed to create container")
80+
}
81+
defer func() {
82+
err := cli.ContainerRemove(context.Background(), cont.ID, types.ContainerRemoveOptions{})
83+
if err != nil {
84+
fmt.Fprintf(os.Stderr, "failed to remove container: %v", err)
85+
}
86+
}()
87+
88+
attachOptions := types.ContainerAttachOptions{
89+
Stdout: true,
90+
Stderr: true,
91+
Stdin: false,
92+
Stream: true,
93+
}
94+
95+
resp, err := cli.ContainerAttach(ctx, cont.ID, attachOptions)
96+
if err != nil {
97+
return errors.Wrap(err, "failed to attach container")
98+
}
99+
defer resp.Close()
100+
101+
copyErrChan := make(chan error, 1)
102+
go func() {
103+
_, err := stdcopy.StdCopy(os.Stdout, os.Stderr, resp.Reader)
104+
copyErrChan <- err
105+
}()
106+
107+
waitBodyChan, waitErrChan := cli.ContainerWait(ctx, cont.ID, container.WaitConditionNextExit)
108+
109+
err = cli.ContainerStart(ctx, cont.ID, types.ContainerStartOptions{})
110+
if err != nil {
111+
return errors.Wrap(err, "failed to start container")
112+
}
113+
defer func() {
114+
t := time.Second * 10
115+
err := cli.ContainerStop(context.Background(), cont.ID, &t)
116+
if err != nil {
117+
fmt.Fprintf(os.Stderr, "failed to stop container: %v", err)
118+
}
119+
}()
120+
121+
select {
122+
case body := <-waitBodyChan:
123+
if body.StatusCode != 0 {
124+
return fmt.Errorf("failed with status code: %d", body.StatusCode)
125+
}
126+
case err := <-waitErrChan:
127+
return err
128+
case err := <-copyErrChan:
129+
return err
130+
case <-ctx.Done():
131+
return ctx.Err()
59132
}
60133

61-
// We need to show the user all output, so a method to squelch
62-
// docker's chattiness is not immediately apparent.
63-
cmd.Stdout = os.Stdout
64-
cmd.Stderr = os.Stderr
65-
66-
// Run the command, echoing captured stderr as well ass the cmd internal error.
67-
// Will run until explicitly canceled.
68-
// TODO: this runner is current stubbed pending an architectural discussion
69-
// on how closely we would like to emulate the previous funcitonality, and
70-
// if we can use Grid as a localhost integraiton events fabric.
71-
fmt.Println(cmd)
72-
return cmd.Run()
134+
return nil
73135
}

docker/runner_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package docker_test
44

55
import (
6+
"context"
67
"flag"
78
"fmt"
89
"os"
@@ -35,7 +36,7 @@ func TestDockerRun(t *testing.T) {
3536

3637
runner := docker.NewRunner()
3738
runner.Verbose = true
38-
if err = runner.Run(f); err != nil {
39+
if err = runner.Run(context.Background(), f); err != nil {
3940
t.Fatal(err)
4041
}
4142
/* TODO

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ go 1.14
44

55
require (
66
github.com/buildpacks/pack v0.17.1-0.20210221000942-0c84b4ae7a30
7+
github.com/docker/docker v20.10.0-beta1.0.20201110211921-af34b94a78a1+incompatible
8+
github.com/docker/go-connections v0.4.0
79
github.com/markbates/pkger v0.17.1
810
github.com/mitchellh/go-homedir v1.1.0
911
github.com/ory/viper v1.7.4
12+
github.com/pkg/errors v0.9.1
1013
github.com/spf13/cobra v1.1.3
1114
gopkg.in/yaml.v2 v2.4.0
1215
k8s.io/api v0.18.12

mock/runner.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package mock
22

3-
import bosonFunc "github.com/boson-project/func"
3+
import (
4+
"context"
5+
bosonFunc "github.com/boson-project/func"
6+
)
47

58
type Runner struct {
69
RunInvoked bool
@@ -11,7 +14,7 @@ func NewRunner() *Runner {
1114
return &Runner{}
1215
}
1316

14-
func (r *Runner) Run(f bosonFunc.Function) error {
17+
func (r *Runner) Run(ctx context.Context, f bosonFunc.Function) error {
1518
r.RunInvoked = true
1619
r.RootRequested = f.Root
1720
return nil

plugin/plugin.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package plugin
22

33
import (
4+
"context"
45
"os"
6+
"os/signal"
57
"runtime/debug"
68
"strings"
9+
"syscall"
710

811
"knative.dev/client/pkg/kn/plugin"
912

@@ -21,6 +24,17 @@ func (f *funcPlugin) Name() string {
2124
}
2225

2326
func (f *funcPlugin) Execute(args []string) error {
27+
ctx, cancel := context.WithCancel(context.Background())
28+
defer cancel()
29+
30+
sigs := make(chan os.Signal, 1)
31+
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
32+
33+
go func() {
34+
<-sigs
35+
cancel()
36+
}()
37+
2438
rootCmd := cmd.NewRootCmd()
2539
info, _ := debug.ReadBuildInfo()
2640
for _, dep := range info.Deps {
@@ -33,7 +47,7 @@ func (f *funcPlugin) Execute(args []string) error {
3347
os.Args = oldArgs
3448
})()
3549
os.Args = append([]string{"kn-func"}, args...)
36-
return rootCmd.Execute()
50+
return rootCmd.ExecuteContext(ctx)
3751
}
3852

3953
// Description for function subcommand visible in 'kn --help'

0 commit comments

Comments
 (0)