mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-12 01:14:22 +00:00
Support mounting volumes to containers
This commit is contained in:
@@ -54,6 +54,9 @@ func GetRunFnRunner(name string) *RunFnRunner {
|
|||||||
&r.Network, "network", false, "enable network access for functions that declare it")
|
&r.Network, "network", false, "enable network access for functions that declare it")
|
||||||
r.Command.Flags().StringVar(
|
r.Command.Flags().StringVar(
|
||||||
&r.NetworkName, "network-name", "bridge", "the docker network to run the container in")
|
&r.NetworkName, "network-name", "bridge", "the docker network to run the container in")
|
||||||
|
r.Command.Flags().StringSliceVar(
|
||||||
|
&r.Volumes, "volume", []string{},
|
||||||
|
"the volumes to bind mount to the container.")
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +78,7 @@ type RunFnRunner struct {
|
|||||||
RunFns runfn.RunFns
|
RunFns runfn.RunFns
|
||||||
Network bool
|
Network bool
|
||||||
NetworkName string
|
NetworkName string
|
||||||
|
Volumes []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RunFnRunner) runE(c *cobra.Command, args []string) error {
|
func (r *RunFnRunner) runE(c *cobra.Command, args []string) error {
|
||||||
@@ -250,6 +254,7 @@ func (r *RunFnRunner) preRunE(c *cobra.Command, args []string) error {
|
|||||||
Network: r.Network,
|
Network: r.Network,
|
||||||
NetworkName: r.NetworkName,
|
NetworkName: r.NetworkName,
|
||||||
EnableStarlark: r.EnableStar,
|
EnableStarlark: r.EnableStar,
|
||||||
|
Volumes: r.Volumes,
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't consider args for the function
|
// don't consider args for the function
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ func TestRunFnCommand_preRunE(t *testing.T) {
|
|||||||
functionPaths []string
|
functionPaths []string
|
||||||
network bool
|
network bool
|
||||||
networkName string
|
networkName string
|
||||||
|
volumes []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "config map",
|
name: "config map",
|
||||||
@@ -213,6 +214,29 @@ metadata:
|
|||||||
data: {g: h, i: j=k}
|
data: {g: h, i: j=k}
|
||||||
kind: Foo
|
kind: Foo
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "volumes",
|
||||||
|
args: []string{"run", "dir", "--volume", "vol1", "--volume", "vol2"},
|
||||||
|
path: "dir",
|
||||||
|
volumes: []string{"vol1", "vol2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom kind with volumes",
|
||||||
|
args: []string{
|
||||||
|
"run", "dir", "--volume", "vol", "--image", "foo:bar", "--", "Foo", "g=h", "i=j=k"},
|
||||||
|
path: "dir",
|
||||||
|
volumes: []string{"vol"},
|
||||||
|
expected: `
|
||||||
|
metadata:
|
||||||
|
name: function-input
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/function: |
|
||||||
|
container: {image: 'foo:bar'}
|
||||||
|
data: {g: h, i: j=k}
|
||||||
|
kind: Foo
|
||||||
|
apiVersion: v1
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -303,6 +327,15 @@ apiVersion: v1
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if Volumes were set
|
||||||
|
if tt.volumes == nil {
|
||||||
|
// make Equal work against flag default
|
||||||
|
tt.volumes = []string{}
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, tt.volumes, r.RunFns.Volumes) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
// check if Functions were set
|
// check if Functions were set
|
||||||
if tt.expected != "" {
|
if tt.expected != "" {
|
||||||
if !assert.Len(t, r.RunFns.Functions, 1) {
|
if !assert.Len(t, r.RunFns.Functions, 1) {
|
||||||
|
|||||||
@@ -134,6 +134,9 @@ type ContainerFilter struct {
|
|||||||
// Network is the container network to use.
|
// Network is the container network to use.
|
||||||
Network string `yaml:"network,omitempty"`
|
Network string `yaml:"network,omitempty"`
|
||||||
|
|
||||||
|
// Volumes are the directories to mount as container volumes.
|
||||||
|
Volumes []string `yaml:"volumes,omitempty"`
|
||||||
|
|
||||||
// StorageMounts is a list of storage options that the container will have mounted.
|
// StorageMounts is a list of storage options that the container will have mounted.
|
||||||
StorageMounts []StorageMount
|
StorageMounts []StorageMount
|
||||||
|
|
||||||
@@ -335,6 +338,11 @@ func (c *ContainerFilter) getArgs() []string {
|
|||||||
args = append(args, "--mount", storageMount.String())
|
args = append(args, "--mount", storageMount.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// export volumes to the container
|
||||||
|
for _, volume := range c.Volumes {
|
||||||
|
args = append(args, "--volume", volume)
|
||||||
|
}
|
||||||
|
|
||||||
// export the local environment vars to the container
|
// export the local environment vars to the container
|
||||||
for _, pair := range os.Environ() {
|
for _, pair := range os.Environ() {
|
||||||
tokens := strings.Split(pair, "=")
|
tokens := strings.Split(pair, "=")
|
||||||
|
|||||||
@@ -141,6 +141,87 @@ metadata:
|
|||||||
assert.Equal(t, expected, cmd.Args)
|
assert.Equal(t, expected, cmd.Args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFilter_command_volume(t *testing.T) {
|
||||||
|
cfg, err := yaml.Parse(`apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
`)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
instance := &ContainerFilter{
|
||||||
|
Image: "example.com:version",
|
||||||
|
Volumes: []string{"/host-src:/container-dest:ro"},
|
||||||
|
Config: cfg,
|
||||||
|
}
|
||||||
|
cmd, err := instance.getCommand()
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{
|
||||||
|
"docker", "run",
|
||||||
|
"--rm",
|
||||||
|
"-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR",
|
||||||
|
"--network", "none",
|
||||||
|
"--user", "nobody",
|
||||||
|
"--security-opt=no-new-privileges",
|
||||||
|
"--volume", "/host-src:/container-dest:ro",
|
||||||
|
}
|
||||||
|
for _, e := range os.Environ() {
|
||||||
|
// the process env
|
||||||
|
tokens := strings.Split(e, "=")
|
||||||
|
if tokens[0] == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expected = append(expected, "-e", tokens[0])
|
||||||
|
}
|
||||||
|
expected = append(expected, "example.com:version")
|
||||||
|
assert.Equal(t, expected, cmd.Args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilter_command_volumes(t *testing.T) {
|
||||||
|
cfg, err := yaml.Parse(`apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
`)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
instance := &ContainerFilter{
|
||||||
|
Image: "example.com:version",
|
||||||
|
Volumes: []string{"/host-src1:/container-dest1:ro", "/host-src2:/container-dest2:rw"},
|
||||||
|
Config: cfg,
|
||||||
|
}
|
||||||
|
cmd, err := instance.getCommand()
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{
|
||||||
|
"docker", "run",
|
||||||
|
"--rm",
|
||||||
|
"-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR",
|
||||||
|
"--network", "none",
|
||||||
|
"--user", "nobody",
|
||||||
|
"--security-opt=no-new-privileges",
|
||||||
|
"--volume", "/host-src1:/container-dest1:ro",
|
||||||
|
"--volume", "/host-src2:/container-dest2:rw",
|
||||||
|
}
|
||||||
|
for _, e := range os.Environ() {
|
||||||
|
// the process env
|
||||||
|
tokens := strings.Split(e, "=")
|
||||||
|
if tokens[0] == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expected = append(expected, "-e", tokens[0])
|
||||||
|
}
|
||||||
|
expected = append(expected, "example.com:version")
|
||||||
|
assert.Equal(t, expected, cmd.Args)
|
||||||
|
}
|
||||||
|
|
||||||
func TestFilter_Filter(t *testing.T) {
|
func TestFilter_Filter(t *testing.T) {
|
||||||
cfg, err := yaml.Parse(`apiVersion: apps/v1
|
cfg, err := yaml.Parse(`apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@@ -355,6 +436,70 @@ container:
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "volume",
|
||||||
|
resource: `
|
||||||
|
apiVersion: v1beta1
|
||||||
|
kind: Example
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/function: |-
|
||||||
|
container:
|
||||||
|
image: foo:v1.0.0
|
||||||
|
volumes: ["/host-src:/container-dest:ro"]
|
||||||
|
`,
|
||||||
|
expectedFn: `
|
||||||
|
container:
|
||||||
|
image: foo:v1.0.0
|
||||||
|
volumes:
|
||||||
|
- /host-src:/container-dest:ro
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "volumes as array",
|
||||||
|
resource: `
|
||||||
|
apiVersion: v1beta1
|
||||||
|
kind: Example
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/function: |-
|
||||||
|
container:
|
||||||
|
image: foo:v1.0.0
|
||||||
|
volumes: ["/host-src1:/container-dest1:ro", "/host-src2:/container-dest2:rw"]
|
||||||
|
`,
|
||||||
|
expectedFn: `
|
||||||
|
container:
|
||||||
|
image: foo:v1.0.0
|
||||||
|
volumes:
|
||||||
|
- /host-src1:/container-dest1:ro
|
||||||
|
- /host-src2:/container-dest2:rw
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "volumes as list",
|
||||||
|
resource: `
|
||||||
|
apiVersion: v1beta1
|
||||||
|
kind: Example
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/function: |-
|
||||||
|
container:
|
||||||
|
image: foo:v1.0.0
|
||||||
|
volumes:
|
||||||
|
- "/host-src1:/container-dest1:ro"
|
||||||
|
- "/host-src2:/container-dest2:rw"
|
||||||
|
`,
|
||||||
|
expectedFn: `
|
||||||
|
container:
|
||||||
|
image: foo:v1.0.0
|
||||||
|
volumes:
|
||||||
|
- /host-src1:/container-dest1:ro
|
||||||
|
- /host-src2:/container-dest2:rw
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "path",
|
name: "path",
|
||||||
resource: `
|
resource: `
|
||||||
@@ -516,6 +661,76 @@ metadata:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_GetContainerVolumeRequired(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
volumes []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: `apiVersion: v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
configFn:
|
||||||
|
container:
|
||||||
|
image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
|
||||||
|
volumes: [ /host-src:/container-dest:ro ]
|
||||||
|
`,
|
||||||
|
volumes: []string{"/host-src:/container-dest:ro"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
input: `apiVersion: v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
configFn:
|
||||||
|
container:
|
||||||
|
image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
|
||||||
|
`,
|
||||||
|
volumes: []string(nil),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `apiVersion: v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/function: |
|
||||||
|
container:
|
||||||
|
image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
|
||||||
|
volumes: [ "/host-src1:/container-dest1:ro", "/host-src2:/container-dest2:rw" ]
|
||||||
|
`,
|
||||||
|
volumes: []string{"/host-src1:/container-dest1:ro", "/host-src2:/container-dest2:rw"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `apiVersion: v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/function: |
|
||||||
|
container:
|
||||||
|
image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
|
||||||
|
volumes:
|
||||||
|
- /host-src1:/container-dest1:ro
|
||||||
|
- /host-src2:/container-dest2:rw
|
||||||
|
`,
|
||||||
|
volumes: []string{"/host-src1:/container-dest1:ro", "/host-src2:/container-dest2:rw"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
cfg, err := yaml.Parse(tc.input)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fn := GetFunctionSpec(cfg)
|
||||||
|
assert.Equal(t, tc.volumes, fn.Container.Volumes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFilter_Filter_defaultNaming(t *testing.T) {
|
func TestFilter_Filter_defaultNaming(t *testing.T) {
|
||||||
cfg, err := yaml.Parse(`apiVersion: apps/v1
|
cfg, err := yaml.Parse(`apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ type FunctionSpec struct {
|
|||||||
|
|
||||||
// Starlark is the spec for running a function as a starlark script
|
// Starlark is the spec for running a function as a starlark script
|
||||||
Starlark StarlarkSpec `json:"starlark,omitempty" yaml:"starlark,omitempty"`
|
Starlark StarlarkSpec `json:"starlark,omitempty" yaml:"starlark,omitempty"`
|
||||||
|
|
||||||
|
// Volumes are the directories to mount as container volumes
|
||||||
|
Volumes []string `json:"volumes,omitempty" yaml:"volumes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerSpec defines a spec for running a function as a container
|
// ContainerSpec defines a spec for running a function as a container
|
||||||
@@ -37,6 +40,9 @@ type ContainerSpec struct {
|
|||||||
|
|
||||||
// Network defines network specific configuration
|
// Network defines network specific configuration
|
||||||
Network ContainerNetwork `json:"network,omitempty" yaml:"network,omitempty"`
|
Network ContainerNetwork `json:"network,omitempty" yaml:"network,omitempty"`
|
||||||
|
|
||||||
|
// Volumes are the directories to mount as container volumes
|
||||||
|
Volumes []string `json:"volumes,omitempty" yaml:"volumes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerNetwork
|
// ContainerNetwork
|
||||||
@@ -68,6 +74,7 @@ func GetFunctionSpec(n *yaml.RNode) *FunctionSpec {
|
|||||||
path := meta.Annotations[kioutil.PathAnnotation]
|
path := meta.Annotations[kioutil.PathAnnotation]
|
||||||
if fn := getFunctionSpecFromAnnotation(n, meta); fn != nil {
|
if fn := getFunctionSpecFromAnnotation(n, meta); fn != nil {
|
||||||
fn.Network = ""
|
fn.Network = ""
|
||||||
|
fn.Volumes = []string{}
|
||||||
fn.Path = path
|
fn.Path = path
|
||||||
return fn
|
return fn
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ type RunFns struct {
|
|||||||
// NetworkName is the name of the docker network to use for the container
|
// NetworkName is the name of the docker network to use for the container
|
||||||
NetworkName string
|
NetworkName string
|
||||||
|
|
||||||
|
// Volumes Volumes allows directories to be specified outside the configuration
|
||||||
|
// directory.
|
||||||
|
Volumes []string
|
||||||
|
|
||||||
// Output can be set to write the result to Output rather than back to the directory
|
// Output can be set to write the result to Output rather than back to the directory
|
||||||
Output io.Writer
|
Output io.Writer
|
||||||
|
|
||||||
@@ -133,6 +137,13 @@ func (r RunFns) getFilters(nodes []*yaml.RNode) ([]kio.Filter, error) {
|
|||||||
}
|
}
|
||||||
fltrs = append(fltrs, f...)
|
fltrs = append(fltrs, f...)
|
||||||
|
|
||||||
|
// directories from volumes specified on the struct
|
||||||
|
f, err = r.getDirectoriesFromVolumes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fltrs = append(fltrs, f...)
|
||||||
|
|
||||||
// explicit fns specified on the struct
|
// explicit fns specified on the struct
|
||||||
f, err = r.getFunctionsFromFunctions()
|
f, err = r.getFunctionsFromFunctions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -196,6 +207,24 @@ func (r RunFns) getFunctionsFromFunctionPaths() ([]kio.Filter, error) {
|
|||||||
return r.getFunctionFilters(true, buff.Nodes...)
|
return r.getFunctionFilters(true, buff.Nodes...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getDirectoriesFromVolumes returns the set of directories read from r.Volumes
|
||||||
|
// as a slice of Filters
|
||||||
|
func (r RunFns) getDirectoriesFromVolumes() ([]kio.Filter, error) {
|
||||||
|
buff := &kio.PackageBuffer{}
|
||||||
|
for i := range r.Volumes {
|
||||||
|
err := kio.Pipeline{
|
||||||
|
Inputs: []kio.Reader{
|
||||||
|
kio.LocalPackageReader{PackagePath: r.Volumes[i]},
|
||||||
|
},
|
||||||
|
Outputs: []kio.Writer{buff},
|
||||||
|
}.Execute()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.getFunctionFilters(true, buff.Nodes...)
|
||||||
|
}
|
||||||
|
|
||||||
// getFunctionsFromFunctions returns the set of explicitly provided functions as
|
// getFunctionsFromFunctions returns the set of explicitly provided functions as
|
||||||
// Filters
|
// Filters
|
||||||
func (r RunFns) getFunctionsFromFunctions() ([]kio.Filter, error) {
|
func (r RunFns) getFunctionsFromFunctions() ([]kio.Filter, error) {
|
||||||
|
|||||||
@@ -140,6 +140,16 @@ func TestRunFns_Execute__initDefault(t *testing.T) {
|
|||||||
FunctionPaths: []string{"foo"},
|
FunctionPaths: []string{"foo"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "explicit directories in volumes",
|
||||||
|
instance: RunFns{Volumes: []string{"vol"}},
|
||||||
|
expected: RunFns{
|
||||||
|
Output: os.Stdout,
|
||||||
|
Input: os.Stdin,
|
||||||
|
NoFunctionsFromInput: getFalse(),
|
||||||
|
Volumes: []string{"vol"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for i := range tests {
|
for i := range tests {
|
||||||
tt := tests[i]
|
tt := tests[i]
|
||||||
|
|||||||
Reference in New Issue
Block a user