diff --git a/cmd/config/internal/commands/run-fns.go b/cmd/config/internal/commands/run-fns.go index fe24a97fd..12558f044 100644 --- a/cmd/config/internal/commands/run-fns.go +++ b/cmd/config/internal/commands/run-fns.go @@ -11,6 +11,7 @@ import ( "github.com/spf13/cobra" "sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands" "sigs.k8s.io/kustomize/kyaml/errors" + "sigs.k8s.io/kustomize/kyaml/kio/filters" "sigs.k8s.io/kustomize/kyaml/runfn" "sigs.k8s.io/kustomize/kyaml/yaml" ) @@ -54,6 +55,9 @@ func GetRunFnRunner(name string) *RunFnRunner { &r.Network, "network", false, "enable network access for functions that declare it") r.Command.Flags().StringVar( &r.NetworkName, "network-name", "bridge", "the docker network to run the container in") + r.Command.Flags().StringArrayVar( + &r.Mounts, "mount", []string{}, + "a list of storage options read from the filesystem") return r } @@ -75,6 +79,7 @@ type RunFnRunner struct { RunFns runfn.RunFns Network bool NetworkName string + Mounts []string } func (r *RunFnRunner) runE(c *cobra.Command, args []string) error { @@ -199,6 +204,14 @@ data: {} return []*yaml.RNode{rc}, nil } +func toStorageMounts(mounts []string) []filters.StorageMount { + var sms []filters.StorageMount + for _, mount := range mounts { + sms = append(sms, filters.StringToStorageMount(mount)) + } + return sms +} + func (r *RunFnRunner) preRunE(c *cobra.Command, args []string) error { if r.EnableStar != (r.StarPath != "") { return errors.Errorf("must specify --star-path with --enable-star") @@ -240,6 +253,9 @@ func (r *RunFnRunner) preRunE(c *cobra.Command, args []string) error { path = args[0] } + // parse mounts to set storageMounts + storageMounts := toStorageMounts(r.Mounts) + r.RunFns = runfn.RunFns{ FunctionPaths: r.FnPaths, GlobalScope: r.GlobalScope, @@ -250,6 +266,7 @@ func (r *RunFnRunner) preRunE(c *cobra.Command, args []string) error { Network: r.Network, NetworkName: r.NetworkName, EnableStarlark: r.EnableStar, + StorageMounts: storageMounts, } // don't consider args for the function diff --git a/cmd/config/internal/commands/run_test.go b/cmd/config/internal/commands/run_test.go index 4a2062072..33a400df6 100644 --- a/cmd/config/internal/commands/run_test.go +++ b/cmd/config/internal/commands/run_test.go @@ -27,6 +27,7 @@ func TestRunFnCommand_preRunE(t *testing.T) { functionPaths []string network bool networkName string + mount []string }{ { name: "config map", @@ -213,6 +214,26 @@ metadata: data: {g: h, i: j=k} kind: Foo apiVersion: v1 +`, + }, + { + name: "custom kind with storage mounts", + args: []string{ + "run", "dir", "--mount", "type=bind,src=/mount/path,dst=/local/", + "--mount", "type=volume,src=myvol,dst=/local/", + "--mount", "type=tmpfs,dst=/local/", + "--image", "foo:bar", "--", "Foo", "g=h", "i=j=k"}, + path: "dir", + mount: []string{"type=bind,src=/mount/path,dst=/local/", "type=volume,src=myvol,dst=/local/", "type=tmpfs,dst=/local/"}, + 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 +324,10 @@ apiVersion: v1 t.FailNow() } + if !assert.Equal(t, toStorageMounts(tt.mount), r.RunFns.StorageMounts) { + t.FailNow() + } + // check if Functions were set if tt.expected != "" { if !assert.Len(t, r.RunFns.Functions, 1) { diff --git a/functions/examples/validator-kubeval/local-resource/example-use.yaml b/functions/examples/validator-kubeval/local-resource/example-use.yaml index d18722b62..e1067b543 100644 --- a/functions/examples/validator-kubeval/local-resource/example-use.yaml +++ b/functions/examples/validator-kubeval/local-resource/example-use.yaml @@ -12,8 +12,7 @@ spec: strict: true ignoreMissingSchemas: true - # TODO: Remove these once function container network/volumes features are - # stabilized. + # TODO: Update this to use network/volumes features. # Relevant issues: # - https://github.com/kubernetes-sigs/kustomize/issues/1901 # - https://github.com/kubernetes-sigs/kustomize/issues/1902 diff --git a/kyaml/kio/filters/container.go b/kyaml/kio/filters/container.go index 6e3dee11f..f4294d3b8 100644 --- a/kyaml/kio/filters/container.go +++ b/kyaml/kio/filters/container.go @@ -135,7 +135,7 @@ type ContainerFilter struct { Network string `yaml:"network,omitempty"` // StorageMounts is a list of storage options that the container will have mounted. - StorageMounts []StorageMount + StorageMounts []StorageMount `yaml:"mounts,omitempty"` // Config is the API configuration for the container and passed through the // API_CONFIG env var to the container. @@ -156,25 +156,31 @@ func (c ContainerFilter) String() string { return c.Image } -// StorageMount represents a container's mounted storage option(s) -type StorageMount struct { - // Type of mount e.g. bind mount, local volume, etc. - MountType string - - // Source for the storage to be mounted. - // For named volumes, this is the name of the volume. - // For anonymous volumes, this field is omitted (empty string). - // For bind mounts, this is the path to the file or directory on the host. - Src string - - // The path where the file or directory is mounted in the container. - DstPath string -} - func (s *StorageMount) String() string { return fmt.Sprintf("type=%s,src=%s,dst=%s:ro", s.MountType, s.Src, s.DstPath) } +func StringToStorageMount(s string) StorageMount { + m := make(map[string]string) + options := strings.Split(s, ",") + for _, option := range options { + keyVal := strings.SplitN(option, "=", 2) + m[keyVal[0]] = keyVal[1] + } + var sm StorageMount + for key, value := range m { + switch { + case key == "type": + sm.MountType = value + case key == "src": + sm.Src = value + case key == "dst": + sm.DstPath = value + } + } + return sm +} + // functionsDirectoryName is keyword directory name for functions scoped 1 directory higher const functionsDirectoryName = "functions" diff --git a/kyaml/kio/filters/container_test.go b/kyaml/kio/filters/container_test.go index 3ae8d901a..319a1a535 100644 --- a/kyaml/kio/filters/container_test.go +++ b/kyaml/kio/filters/container_test.go @@ -334,6 +334,68 @@ container: image: foo:v1.0.0`, }, + { + name: "storage mounts json style", + resource: ` +apiVersion: v1beta1 +kind: Example +metadata: + annotations: + config.kubernetes.io/function: |- + container: + image: foo:v1.0.0 + mounts: [ {type: bind, src: /mount/path, dst: /local/}, {src: myvol, dst: /local/, type: volume}, {dst: /local/, type: tmpfs} ] +`, + expectedFn: ` +container: + image: foo:v1.0.0 + mounts: + - type: bind + src: /mount/path + dst: /local/ + - type: volume + src: myvol + dst: /local/ + - type: tmpfs + dst: /local/ +`, + }, + + { + name: "storage mounts yaml style", + resource: ` +apiVersion: v1beta1 +kind: Example +metadata: + annotations: + config.kubernetes.io/function: |- + container: + image: foo:v1.0.0 + mounts: + - src: /mount/path + type: bind + dst: /local/ + - dst: /local/ + src: myvol + type: volume + - type: tmpfs + dst: /local/ +`, + expectedFn: ` +container: + image: foo:v1.0.0 + mounts: + - type: bind + src: /mount/path + dst: /local/ + - type: volume + src: myvol + dst: /local/ + - type: tmpfs + dst: /local/ +`, + }, + { name: "network", resource: ` diff --git a/kyaml/kio/filters/functiontypes.go b/kyaml/kio/filters/functiontypes.go index 83f6f647c..db5a8adb3 100644 --- a/kyaml/kio/filters/functiontypes.go +++ b/kyaml/kio/filters/functiontypes.go @@ -28,6 +28,9 @@ type FunctionSpec struct { // Starlark is the spec for running a function as a starlark script Starlark StarlarkSpec `json:"starlark,omitempty" yaml:"starlark,omitempty"` + + // Mounts are the storage or directories to mount into the container + StorageMounts []StorageMount `json:"mounts,omitempty" yaml:"mounts,omitempty"` } // ContainerSpec defines a spec for running a function as a container @@ -37,6 +40,9 @@ type ContainerSpec struct { // Network defines network specific configuration Network ContainerNetwork `json:"network,omitempty" yaml:"network,omitempty"` + + // Mounts are the storage or directories to mount into the container + StorageMounts []StorageMount `json:"mounts,omitempty" yaml:"mounts,omitempty"` } // ContainerNetwork @@ -53,6 +59,21 @@ type StarlarkSpec struct { Path string `json:"path,omitempty" yaml:"path,omitempty"` } +// StorageMount represents a container's mounted storage option(s) +type StorageMount struct { + // Type of mount e.g. bind mount, local volume, etc. + MountType string `json:"type,omitempty" yaml:"type,omitempty"` + + // Source for the storage to be mounted. + // For named volumes, this is the name of the volume. + // For anonymous volumes, this field is omitted (empty string). + // For bind mounts, this is the path to the file or directory on the host. + Src string `json:"src,omitempty" yaml:"src,omitempty"` + + // The path where the file or directory is mounted in the container. + DstPath string `json:"dst,omitempty" yaml:"dst,omitempty"` +} + // GetFunctionSpec returns the FunctionSpec for a resource. Returns // nil if the resource does not have a FunctionSpec. // @@ -68,6 +89,7 @@ func GetFunctionSpec(n *yaml.RNode) *FunctionSpec { path := meta.Annotations[kioutil.PathAnnotation] if fn := getFunctionSpecFromAnnotation(n, meta); fn != nil { fn.Network = "" + fn.StorageMounts = []StorageMount{} fn.Path = path return fn } diff --git a/kyaml/runfn/runfn_test.go b/kyaml/runfn/runfn_test.go index f88349194..236f5610c 100644 --- a/kyaml/runfn/runfn_test.go +++ b/kyaml/runfn/runfn_test.go @@ -140,6 +140,16 @@ func TestRunFns_Execute__initDefault(t *testing.T) { FunctionPaths: []string{"foo"}, }, }, + { + name: "explicit directories in mounts", + instance: RunFns{StorageMounts: []filters.StorageMount{{MountType: "volume", Src: "myvol", DstPath: "/local/"}}}, + expected: RunFns{ + Output: os.Stdout, + Input: os.Stdin, + NoFunctionsFromInput: getFalse(), + StorageMounts: []filters.StorageMount{{MountType: "volume", Src: "myvol", DstPath: "/local/"}}, + }, + }, } for i := range tests { tt := tests[i]