test without system call

This commit is contained in:
Donny Xia
2020-09-16 12:15:35 -07:00
parent 11049fa0bb
commit 52016b22dd
4 changed files with 85 additions and 47 deletions

View File

@@ -5,7 +5,6 @@ package container
import ( import (
"fmt" "fmt"
"os/user"
runtimeexec "sigs.k8s.io/kustomize/kyaml/fn/runtime/exec" runtimeexec "sigs.k8s.io/kustomize/kyaml/fn/runtime/exec"
"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil" "sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
@@ -185,28 +184,9 @@ func (c *Filter) getCommand() (string, []string) {
return "docker", a return "docker", a
} }
// getUIDGID will return "nobody" if asCurrentUser is false. Otherwise
// return "uid:gid" according to current user who runs the command.
func getUIDGID(asCurrentUser bool) (string, error) {
if !asCurrentUser {
return "nobody", nil
}
u, err := user.Current()
if err != nil {
return "", err
}
return fmt.Sprintf("%s:%s", u.Uid, u.Gid), nil
}
// NewContainer returns a new container filter // NewContainer returns a new container filter
func NewContainer(spec runtimeutil.ContainerSpec, asCurrentUser bool) (Filter, error) { func NewContainer(spec runtimeutil.ContainerSpec, uidgid string) (Filter, error) {
f := Filter{ContainerSpec: spec} f := Filter{ContainerSpec: spec, UIDGID: uidgid}
u, err := getUIDGID(asCurrentUser)
if err != nil {
return f, err
}
f.UIDGID = u
return f, nil return f, nil
} }

View File

@@ -6,7 +6,6 @@ package container
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"os/user"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -16,16 +15,12 @@ import (
) )
func TestFilter_setupExec(t *testing.T) { func TestFilter_setupExec(t *testing.T) {
u, err := user.Current()
if err != nil {
t.Fatal(err)
}
var tests = []struct { var tests = []struct {
name string name string
functionConfig string functionConfig string
expectedArgs []string expectedArgs []string
containerSpec runtimeutil.ContainerSpec containerSpec runtimeutil.ContainerSpec
asCurrentUser bool UIDGID string
}{ }{
{ {
name: "command", name: "command",
@@ -45,6 +40,7 @@ metadata:
containerSpec: runtimeutil.ContainerSpec{ containerSpec: runtimeutil.ContainerSpec{
Image: "example.com:version", Image: "example.com:version",
}, },
UIDGID: "nobody",
}, },
{ {
@@ -62,12 +58,11 @@ metadata:
"--user", "nobody", "--user", "nobody",
"--security-opt=no-new-privileges", "--security-opt=no-new-privileges",
}, },
instance: NewContainer( containerSpec: runtimeutil.ContainerSpec{
runtimeutil.ContainerSpec{ Image: "example.com:version",
Image: "example.com:version", Network: true,
Network: true,
},
}, },
UIDGID: "nobody",
}, },
{ {
@@ -98,6 +93,7 @@ metadata:
{MountType: "tmpfs", Src: "", DstPath: "/local/"}, {MountType: "tmpfs", Src: "", DstPath: "/local/"},
}, },
}, },
UIDGID: "nobody",
}, },
{ {
name: "as current user", name: "as current user",
@@ -111,13 +107,13 @@ metadata:
"--rm", "--rm",
"-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR", "-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR",
"--network", "none", "--network", "none",
"--user", fmt.Sprintf("%s:%s", u.Uid, u.Gid), "--user", "1:2",
"--security-opt=no-new-privileges", "--security-opt=no-new-privileges",
}, },
containerSpec: runtimeutil.ContainerSpec{ containerSpec: runtimeutil.ContainerSpec{
Image: "example.com:version", Image: "example.com:version",
}, },
asCurrentUser: true, UIDGID: "1:2",
}, },
} }
@@ -129,7 +125,7 @@ metadata:
t.FailNow() t.FailNow()
} }
instance, err := NewContainer(tt.containerSpec, tt.asCurrentUser) instance, err := NewContainer(tt.containerSpec, tt.UIDGID)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"os/user"
"path" "path"
"path/filepath" "path/filepath"
"sort" "sort"
@@ -83,7 +84,7 @@ type RunFns struct {
// functionFilterProvider provides a filter to perform the function. // functionFilterProvider provides a filter to perform the function.
// this is a variable so it can be mocked in tests // this is a variable so it can be mocked in tests
functionFilterProvider func( functionFilterProvider func(
filter runtimeutil.FunctionSpec, api *yaml.RNode) (kio.Filter, error) filter runtimeutil.FunctionSpec, api *yaml.RNode, currentUser currentUserFunc) (kio.Filter, error)
// AsCurrentUser is a boolean to indicate whether docker container should use // AsCurrentUser is a boolean to indicate whether docker container should use
// the uid and gid that run the command // the uid and gid that run the command
@@ -303,7 +304,7 @@ func (r RunFns) getFunctionFilters(global bool, fns ...*yaml.RNode) (
// merge envs from imperative and declarative // merge envs from imperative and declarative
spec.Container.Env = r.mergeContainerEnv(spec.Container.Env) spec.Container.Env = r.mergeContainerEnv(spec.Container.Env)
c, err := r.functionFilterProvider(*spec, api) c, err := r.functionFilterProvider(*spec, api, user.Current)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -395,8 +396,24 @@ func (r *RunFns) init() {
} }
} }
type currentUserFunc func() (*user.User, error)
// getUIDGID will return "nobody" if asCurrentUser is false. Otherwise
// return "uid:gid" according to the return from currentUser function.
func getUIDGID(asCurrentUser bool, currentUser currentUserFunc) (string, error) {
if !asCurrentUser {
return "nobody", nil
}
u, err := currentUser()
if err != nil {
return "", err
}
return fmt.Sprintf("%s:%s", u.Uid, u.Gid), nil
}
// ffp provides function filters // ffp provides function filters
func (r *RunFns) ffp(spec runtimeutil.FunctionSpec, api *yaml.RNode) (kio.Filter, error) { func (r *RunFns) ffp(spec runtimeutil.FunctionSpec, api *yaml.RNode, currentUser currentUserFunc) (kio.Filter, error) {
var resultsFile string var resultsFile string
if r.ResultsDir != "" { if r.ResultsDir != "" {
resultsFile = filepath.Join(r.ResultsDir, fmt.Sprintf( resultsFile = filepath.Join(r.ResultsDir, fmt.Sprintf(
@@ -405,6 +422,10 @@ func (r *RunFns) ffp(spec runtimeutil.FunctionSpec, api *yaml.RNode) (kio.Filter
} }
if !r.DisableContainers && spec.Container.Image != "" { if !r.DisableContainers && spec.Container.Image != "" {
// TODO: Add a test for this behavior // TODO: Add a test for this behavior
uidgid, err := getUIDGID(r.AsCurrentUser, currentUser)
if err != nil {
return nil, err
}
c, err := container.NewContainer( c, err := container.NewContainer(
runtimeutil.ContainerSpec{ runtimeutil.ContainerSpec{
Image: spec.Container.Image, Image: spec.Container.Image,
@@ -412,7 +433,7 @@ func (r *RunFns) ffp(spec runtimeutil.FunctionSpec, api *yaml.RNode) (kio.Filter
StorageMounts: r.StorageMounts, StorageMounts: r.StorageMounts,
Env: spec.Container.Env, Env: spec.Container.Env,
}, },
r.AsCurrentUser, uidgid,
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/user"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
@@ -38,6 +39,13 @@ replace: StatefulSet
` `
) )
func currentUser() (*user.User, error) {
return &user.User{
Uid: "1",
Gid: "2",
}, nil
}
func TestRunFns_init(t *testing.T) { func TestRunFns_init(t *testing.T) {
instance := RunFns{} instance := RunFns{}
instance.init() instance.init()
@@ -59,8 +67,41 @@ kind:
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
return return
} }
filter, _ := instance.functionFilterProvider(spec, api) filter, _ := instance.functionFilterProvider(spec, api, currentUser)
c, err := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, false) c, err := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, "nobody")
if err != nil {
t.Fatal(err)
}
cf := &c
cf.Exec.FunctionConfig = api
assert.Equal(t, cf, filter)
}
func TestRunFns_initAsCurrentUser(t *testing.T) {
instance := RunFns{
AsCurrentUser: true,
}
instance.init()
if !assert.Equal(t, instance.Input, os.Stdin) {
t.FailNow()
}
if !assert.Equal(t, instance.Output, os.Stdout) {
t.FailNow()
}
api, err := yaml.Parse(`apiVersion: apps/v1
kind:
`)
spec := runtimeutil.FunctionSpec{
Container: runtimeutil.ContainerSpec{
Image: "example.com:version",
},
}
if !assert.NoError(t, err) {
return
}
filter, _ := instance.functionFilterProvider(spec, api, currentUser)
c, err := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, "1:2")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -93,8 +134,8 @@ kind:
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
return return
} }
filter, _ := instance.functionFilterProvider(spec, api) filter, _ := instance.functionFilterProvider(spec, api, currentUser)
c, err := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, false) c, err := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, "nobody")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -869,7 +910,7 @@ replace: StatefulSet
var fltrs []*TestFilter var fltrs []*TestFilter
instance := RunFns{ instance := RunFns{
Path: dir, Path: dir,
functionFilterProvider: func(f runtimeutil.FunctionSpec, node *yaml.RNode) (kio.Filter, error) { functionFilterProvider: func(f runtimeutil.FunctionSpec, node *yaml.RNode, currentUser currentUserFunc) (kio.Filter, error) {
tf := &TestFilter{ tf := &TestFilter{
Exit: errors.Errorf("message: %s", f.Container.Image), Exit: errors.Errorf("message: %s", f.Container.Image),
} }
@@ -1075,8 +1116,8 @@ func setupTest(t *testing.T) string {
// getFilterProvider fakes the creation of a filter, replacing the ContainerFiler with // getFilterProvider fakes the creation of a filter, replacing the ContainerFiler with
// a filter to s/kind: Deployment/kind: StatefulSet/g. // a filter to s/kind: Deployment/kind: StatefulSet/g.
// this can be used to simulate running a filter. // this can be used to simulate running a filter.
func getFilterProvider(t *testing.T) func(runtimeutil.FunctionSpec, *yaml.RNode) (kio.Filter, error) { func getFilterProvider(t *testing.T) func(runtimeutil.FunctionSpec, *yaml.RNode, currentUserFunc) (kio.Filter, error) {
return func(f runtimeutil.FunctionSpec, node *yaml.RNode) (kio.Filter, error) { return func(f runtimeutil.FunctionSpec, node *yaml.RNode, currentUser currentUserFunc) (kio.Filter, error) {
// parse the filter from the input // parse the filter from the input
filter := yaml.YFilter{} filter := yaml.YFilter{}
b := &bytes.Buffer{} b := &bytes.Buffer{}