mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-11 17:12:51 +00:00
Add documentation and simplify framework
This commit is contained in:
@@ -1,54 +1,120 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package framework contains a framework for writing functions in go.
|
||||
// Package framework contains a framework for writing functions in go. The function spec
|
||||
// is defined at: https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md
|
||||
//
|
||||
// Example
|
||||
// Examples
|
||||
//
|
||||
// Example function implementation to set an annotation on each resource.
|
||||
// Example function implementation using framework.ResourceList with functionConfig
|
||||
//
|
||||
// type Spec struct {
|
||||
// Value string `yaml:"value,omitempty"`
|
||||
// }
|
||||
// type Example struct {
|
||||
// Spec Spec `yaml:"spec,omitempty"`
|
||||
// }
|
||||
// functionConfig := &Example{}
|
||||
//
|
||||
// rl := framework.ResourceList{FunctionConfig: functionConfig}
|
||||
// if err := rl.Read(); err != nil { return err }
|
||||
//
|
||||
// for i := range rl.Items {
|
||||
// // modify the items...
|
||||
// }
|
||||
// if err := rl.Write(); err != nil { return err }
|
||||
//
|
||||
// Example function implementation using framework.Command with flags
|
||||
//
|
||||
// var value string
|
||||
// cmd := framework.Command(nil, func(items []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
// for i := range items {
|
||||
// if err := items[i].PipeE(yaml.SetAnnotation("value", value)); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// // modify the items...
|
||||
// }
|
||||
// return items, nil
|
||||
// })
|
||||
// cmd.Flags().StringVar(&value, "value", "", "annotation value")
|
||||
// if err := cmd.Execute(); err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// if err := cmd.Execute(); err != nil { return err }
|
||||
//
|
||||
// Architecture
|
||||
//
|
||||
// Functions are implemented as a go function which accept a slice of resources (items)
|
||||
// and returns a modified slice of resources (items).
|
||||
// Functions modify a slice of resources (ResourceList.items) which are read as input and written
|
||||
// as output. The function itself may be configured through a functionConfig
|
||||
// (ResourceList.functionConfig).
|
||||
//
|
||||
// Example Function Input:
|
||||
//
|
||||
// kind: ResourceList
|
||||
// items:
|
||||
// - kind: Deployment
|
||||
// ...
|
||||
// - kind: Service
|
||||
// ....
|
||||
// functionConfig:
|
||||
// kind: Example
|
||||
// spec:
|
||||
// value: foo
|
||||
//
|
||||
// The functionConfig may be specified declaratively and run with
|
||||
//
|
||||
// config run DIR/
|
||||
//
|
||||
// Declarative function declaration:
|
||||
//
|
||||
// kind: Example
|
||||
// metadata:
|
||||
// annotations:
|
||||
// # run the function by creating this container and providing this
|
||||
// # Example as the functionConfig
|
||||
// config.kubernetes.io/function: |
|
||||
// image: image/containing/fuction:impl
|
||||
// spec:
|
||||
// value: foo
|
||||
//
|
||||
// The framework takes care of serializing and deserializing the ResourceList.
|
||||
//
|
||||
// Generated ResourceList.functionConfig -- ConfigMaps
|
||||
//
|
||||
// Functions may also be specified imperatively and run using:
|
||||
//
|
||||
// config run DIR/ --image image/containing/fuction:impl -- value=foo
|
||||
//
|
||||
// When run imperatively, a ConfigMap is generated for the functionConfig, and the command
|
||||
// arguments are set as ConfigMap data entries.
|
||||
//
|
||||
// kind: ConfigMap
|
||||
// data:
|
||||
// value: foo
|
||||
//
|
||||
// To write a function that can be run imperatively on the commandline, have it take a
|
||||
// ConfigMap as its functionConfig.
|
||||
//
|
||||
// Mutator and Generator Functions
|
||||
//
|
||||
// Functions may add, delete or modify resources for the returned slice.
|
||||
// Functions may add, delete or modify resources by modifying the items slice.
|
||||
// When using framework.Command this is done through returning the new items slice.
|
||||
// When using framework.ResourceList this is done through modifying ResourceList.Items in place.
|
||||
//
|
||||
// Validator Functions
|
||||
//
|
||||
// Functions may validate resources, returning results as go errors. results may contain
|
||||
// different items for different validation failures.
|
||||
// A function may validate resources by providing a Result.
|
||||
// When using framework.Command this is done through returning a framework.Result as an error.
|
||||
// WHen using framework.ResourceList this is done through setting ResourceList.Result.
|
||||
//
|
||||
// Configuring Functions
|
||||
//
|
||||
// Functions may be configured through a functionConfig (i.e. a client side custom resource),
|
||||
// or through flags (which the framework parses from a ConfigMap provided as input).
|
||||
// Any flags registered on the cobra.Command will be parsed from the functionConfig input
|
||||
// if they are defined as functionConfig.data entries.
|
||||
//
|
||||
// When using framework.Command, any flags registered on the cobra.Command will be parsed
|
||||
// from the functionConfig input if they are defined as functionConfig.data entries.
|
||||
//
|
||||
// When using framework.ResourceList, any flags set on the ResourceList.Flags will be
|
||||
// parsed from the functionConfig input if they are defined as functionConfig.data entries.
|
||||
//
|
||||
// Functions may also access environment variables set by the caller.
|
||||
//
|
||||
// Function Input
|
||||
//
|
||||
// The framework parses the function ResourceList.items into a slice of yaml.RNodes, and
|
||||
// parses the ResourceList.functionConfig into a passed in struct (optional).
|
||||
//
|
||||
// Building the Container
|
||||
// Building a container image for the function
|
||||
//
|
||||
// The go program must be built into a container to be run as a function. The framework
|
||||
// can be used to generate a Dockerfile to build the function container.
|
||||
|
||||
@@ -7,10 +7,82 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"sigs.k8s.io/kustomize/kyaml/fn/framework"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
const service = "Service"
|
||||
|
||||
// ExampleResourceList_modify implements a function that sets an annotation on each resource.
|
||||
// The annotation value is configured via a flag value parsed from ResourceList.functionConfig.data
|
||||
func ExampleResourceList_modify() {
|
||||
// for testing purposes only -- normally read from stdin when Executing
|
||||
input := bytes.NewBufferString(`
|
||||
apiVersion: config.kubernetes.io/v1alpha1
|
||||
kind: ResourceList
|
||||
# items are provided as nodes
|
||||
items:
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo
|
||||
- apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: foo
|
||||
# functionConfig is parsed into flags by framework.Command
|
||||
functionConfig:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
value: baz
|
||||
`)
|
||||
|
||||
// configure the annotation value using a flag parsed from
|
||||
// ResourceList.functionConfig.data.value
|
||||
fs := pflag.NewFlagSet("tests", pflag.ContinueOnError)
|
||||
value := fs.String("value", "", "annotation value")
|
||||
rl := framework.ResourceList{
|
||||
Flags: fs,
|
||||
Reader: input, // for testing only
|
||||
}
|
||||
if err := rl.Read(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for i := range rl.Items {
|
||||
// set the annotation on each resource item
|
||||
if err := rl.Items[i].PipeE(yaml.SetAnnotation("value", *value)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
if err := rl.Write(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: config.kubernetes.io/v1alpha1
|
||||
// kind: ResourceList
|
||||
// items:
|
||||
// - apiVersion: apps/v1
|
||||
// kind: Deployment
|
||||
// metadata:
|
||||
// name: foo
|
||||
// annotations:
|
||||
// value: 'baz'
|
||||
// - apiVersion: v1
|
||||
// kind: Service
|
||||
// metadata:
|
||||
// name: foo
|
||||
// annotations:
|
||||
// value: 'baz'
|
||||
// functionConfig:
|
||||
// apiVersion: v1
|
||||
// kind: ConfigMap
|
||||
// data:
|
||||
// value: baz
|
||||
}
|
||||
|
||||
// ExampleCommand_modify implements a function that sets an annotation on each resource.
|
||||
// The annotation value is configured via a flag value parsed from
|
||||
// ResourceList.functionConfig.data
|
||||
@@ -102,7 +174,7 @@ func ExampleCommand_generateReplace() {
|
||||
|
||||
// something we already generated, remove it from the list so we regenerate it
|
||||
if meta.Name == functionConfig.Spec.Name &&
|
||||
meta.Kind == "Service" &&
|
||||
meta.Kind == service &&
|
||||
meta.APIVersion == "v1" {
|
||||
continue
|
||||
}
|
||||
@@ -163,6 +235,95 @@ functionConfig:
|
||||
// name: bar
|
||||
}
|
||||
|
||||
// ExampleResourceList_generateReplace generates a resource from a functionConfig.
|
||||
// If the resource already exist s, it replaces the resource with a new copy.
|
||||
func ExampleResourceList_generateReplace() {
|
||||
input := bytes.NewBufferString(`
|
||||
apiVersion: config.kubernetes.io/v1alpha1
|
||||
kind: ResourceList
|
||||
# items are provided as nodes
|
||||
items:
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo
|
||||
# functionConfig is parsed into flags by framework.Command
|
||||
functionConfig:
|
||||
apiVersion: example.com/v1alpha1
|
||||
kind: ExampleServiceGenerator
|
||||
spec:
|
||||
name: bar
|
||||
`)
|
||||
|
||||
// function API definition which will be parsed from the ResourceList.functionConfig
|
||||
// read from stdin
|
||||
type Spec struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
}
|
||||
type ExampleServiceGenerator struct {
|
||||
Spec Spec `yaml:"spec,omitempty"`
|
||||
}
|
||||
functionConfig := &ExampleServiceGenerator{}
|
||||
|
||||
rl := framework.ResourceList{
|
||||
FunctionConfig: functionConfig,
|
||||
Reader: input, // for testing only
|
||||
}
|
||||
if err := rl.Read(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// remove the last generated resource
|
||||
var newNodes []*yaml.RNode
|
||||
for i := range rl.Items {
|
||||
meta, err := rl.Items[i].GetMeta()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// something we already generated, remove it from the list so we regenerate it
|
||||
if meta.Name == functionConfig.Spec.Name &&
|
||||
meta.Kind == service &&
|
||||
meta.APIVersion == "v1" {
|
||||
continue
|
||||
}
|
||||
newNodes = append(newNodes, rl.Items[i])
|
||||
}
|
||||
rl.Items = newNodes
|
||||
|
||||
// generate the resource again
|
||||
n, err := yaml.Parse(fmt.Sprintf(`apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: %s
|
||||
`, functionConfig.Spec.Name))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rl.Items = append(rl.Items, n)
|
||||
|
||||
if err := rl.Write(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: config.kubernetes.io/v1alpha1
|
||||
// kind: ResourceList
|
||||
// items:
|
||||
// - apiVersion: apps/v1
|
||||
// kind: Deployment
|
||||
// metadata:
|
||||
// name: foo
|
||||
// - apiVersion: v1
|
||||
// kind: Service
|
||||
// metadata:
|
||||
// name: bar
|
||||
// functionConfig:
|
||||
// apiVersion: example.com/v1alpha1
|
||||
// kind: ExampleServiceGenerator
|
||||
// spec:
|
||||
// name: bar
|
||||
}
|
||||
|
||||
// ExampleCommand_generateUpdate generates a resource, updating the previously generated
|
||||
// copy rather than replacing it.
|
||||
//
|
||||
@@ -191,7 +352,7 @@ func ExampleCommand_generateUpdate() {
|
||||
// something we already generated, reconcile it to make sure it matches what
|
||||
// is specified by the functionConfig
|
||||
if meta.Name == functionConfig.Spec.Name &&
|
||||
meta.Kind == "Service" &&
|
||||
meta.Kind == service &&
|
||||
meta.APIVersion == "v1" {
|
||||
// set some values
|
||||
for k, v := range functionConfig.Spec.Annotations {
|
||||
|
||||
@@ -5,22 +5,176 @@ package framework
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// Command provides a cobra.Command for running the function.
|
||||
// ResourceList reads the function input and writes the function output.
|
||||
//
|
||||
// If functionConfig is nil, the function may be configured with flags parsed from
|
||||
// the ResourceList.functionConfig by creating flags on the returned command.
|
||||
// Adheres to the spec: https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md
|
||||
type ResourceList struct {
|
||||
// FunctionConfig is the ResourceList.functionConfig input value. If FunctionConfig
|
||||
// is set to a value such as a struct or map[string]interface{} before ResourceList.Read()
|
||||
// is called, then the functionConfig will be parsed into that value.
|
||||
// If it is nil, the functionConfig will be set to a map[string]interface{}
|
||||
// before it is parsed.
|
||||
//
|
||||
// e.g. given the function input:
|
||||
//
|
||||
// kind: ResourceList
|
||||
// functionConfig:
|
||||
// kind: Example
|
||||
// spec:
|
||||
// foo: var
|
||||
//
|
||||
// FunctionConfig will contain the Example unmarshalled into its value.
|
||||
FunctionConfig interface{}
|
||||
|
||||
// Items is the ResourceList.items input and output value. Items will be set by
|
||||
// ResourceList.Read() and written by ResourceList.Write().
|
||||
//
|
||||
// e.g. given the function input:
|
||||
//
|
||||
// kind: ResourceList
|
||||
// items:
|
||||
// - kind: Deployment
|
||||
// ...
|
||||
// - kind: Service
|
||||
// ...
|
||||
//
|
||||
// Items will be a slice containing the Deployment and Service resources
|
||||
Items []*yaml.RNode
|
||||
|
||||
// Result is ResourceList.result output value. Result will be written by
|
||||
// ResourceList.Write()
|
||||
Result *Result
|
||||
|
||||
// Flags are an optional set of flags to parse the ResourceList.functionConfig.data.
|
||||
// If non-nil, ResourceList.Read() will set the flag value for each flag name matching
|
||||
// a ResourceList.functionConfig.data map entry.
|
||||
//
|
||||
// e.g. given the function input:
|
||||
//
|
||||
// kind: ResourceList
|
||||
// functionConfig:
|
||||
// data:
|
||||
// foo: bar
|
||||
// a: b
|
||||
//
|
||||
// The flags --a=b and --foo=bar will be set in Flags.
|
||||
Flags *pflag.FlagSet
|
||||
|
||||
// Reader is used to read the function input (ResourceList).
|
||||
// Defaults to os.Stdin.
|
||||
Reader io.Reader
|
||||
|
||||
// Writer is used to write the function output (ResourceList)
|
||||
// Defaults to os.Stdout.
|
||||
Writer io.Writer
|
||||
|
||||
// rw reads function input and writes function output
|
||||
rw *kio.ByteReadWriter
|
||||
}
|
||||
|
||||
// Read reads the ResourceList
|
||||
func (r *ResourceList) Read() error {
|
||||
if r.Reader == nil {
|
||||
r.Reader = os.Stdin
|
||||
}
|
||||
if r.Writer == nil {
|
||||
r.Writer = os.Stdout
|
||||
}
|
||||
r.rw = &kio.ByteReadWriter{
|
||||
Reader: r.Reader,
|
||||
Writer: r.Writer,
|
||||
KeepReaderAnnotations: true,
|
||||
}
|
||||
|
||||
var err error
|
||||
r.Items, err = r.rw.Read()
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
// parse the functionConfig
|
||||
return func() error {
|
||||
if r.rw.FunctionConfig == nil {
|
||||
// no function config exists
|
||||
return nil
|
||||
}
|
||||
if r.FunctionConfig == nil {
|
||||
// set directly from r.rw
|
||||
r.FunctionConfig = r.rw.FunctionConfig
|
||||
} else {
|
||||
// unmarshal the functionConfig into the provided value
|
||||
err := yaml.Unmarshal([]byte(r.rw.FunctionConfig.MustString()), r.FunctionConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
// set the functionConfig values as flags so they are easy to access
|
||||
if r.Flags == nil || !r.Flags.HasFlags() {
|
||||
return nil
|
||||
}
|
||||
// flags are always set from the "data" field
|
||||
data, err := r.rw.FunctionConfig.Pipe(yaml.Lookup("data"))
|
||||
if err != nil || data == nil {
|
||||
return err
|
||||
}
|
||||
return data.VisitFields(func(node *yaml.MapNode) error {
|
||||
f := r.Flags.Lookup(node.Key.YNode().Value)
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
return f.Value.Set(node.Value.YNode().Value)
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
// Write writes the ResourceList
|
||||
func (r *ResourceList) Write() error {
|
||||
// set the ResourceList.results for validating functions
|
||||
if r.Result != nil {
|
||||
if len(r.Result.Items) > 0 {
|
||||
b, err := yaml.Marshal(r.Result)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
y, err := yaml.Parse(string(b))
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
r.rw.Results = y
|
||||
}
|
||||
}
|
||||
|
||||
// write the results
|
||||
return r.rw.Write(r.Items)
|
||||
}
|
||||
|
||||
// Command returns a cobra.Command to run a function.
|
||||
//
|
||||
// The cobra.Command will use a ResourceList to Read() the input, run the provided function,
|
||||
// and Write() the output.
|
||||
//
|
||||
// If functionConfig is non-nil, the ResourceList.functionConfig will be unmarshalled into it.
|
||||
//
|
||||
// The returned cobra.Command will have a "gen" subcommand which can be used to generate
|
||||
// a Dockerfile to build the function into a container image
|
||||
//
|
||||
// go run main.go gen DIR/
|
||||
func Command(functionConfig interface{}, function Function) cobra.Command {
|
||||
cmd := cobra.Command{}
|
||||
addGenerate(&cmd)
|
||||
AddGenerateDockerfile(&cmd)
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
err := execute(function, functionConfig, cmd)
|
||||
if err != nil {
|
||||
@@ -33,7 +187,9 @@ func Command(functionConfig interface{}, function Function) cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func addGenerate(cmd *cobra.Command) {
|
||||
// AddGenerateDockerfile adds a "gen" subcommand to create a Dockerfile for building
|
||||
// the function as a container.
|
||||
func AddGenerateDockerfile(cmd *cobra.Command) {
|
||||
gen := &cobra.Command{
|
||||
Use: "gen",
|
||||
Args: cobra.ExactArgs(1),
|
||||
@@ -54,89 +210,33 @@ CMD ["function"]
|
||||
}
|
||||
|
||||
func execute(function Function, functionConfig interface{}, cmd *cobra.Command) error {
|
||||
rw := &kio.ByteReadWriter{
|
||||
Reader: cmd.InOrStdin(),
|
||||
Writer: cmd.OutOrStdout(),
|
||||
KeepReaderAnnotations: true,
|
||||
}
|
||||
nodes, err := rw.Read()
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
rl := ResourceList{
|
||||
FunctionConfig: functionConfig,
|
||||
Flags: cmd.Flags(),
|
||||
Writer: cmd.OutOrStdout(),
|
||||
Reader: cmd.InOrStdin(),
|
||||
}
|
||||
|
||||
// parse the functionConfig
|
||||
if rw.FunctionConfig != nil {
|
||||
if functionConfig == nil {
|
||||
functionConfig = map[string]interface{}{}
|
||||
}
|
||||
|
||||
// unmarshal into the provided structure
|
||||
err := yaml.Unmarshal([]byte(rw.FunctionConfig.MustString()), functionConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
// set the functionConfig values as flags so they are easy to access
|
||||
err = func() error {
|
||||
if !cmd.HasFlags() {
|
||||
return nil
|
||||
}
|
||||
// kpt serializes function arguments as a ConfigMap, read them from
|
||||
// the data field.
|
||||
fc, ok := functionConfig.(map[string]interface{})
|
||||
if !ok {
|
||||
// serialized as something else
|
||||
return nil
|
||||
}
|
||||
if fc["data"] == nil {
|
||||
return nil
|
||||
}
|
||||
data := fc["data"].(map[string]interface{})
|
||||
// set the value of each flag from the ResourceList.function config input
|
||||
// values
|
||||
for k, v := range data {
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if err = cmd.Flag(k).Value.Set(s); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := rl.Read(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// run the function implementation
|
||||
nodes, err = function(nodes)
|
||||
var err error
|
||||
rl.Items, err = function(rl.Items)
|
||||
|
||||
// set the ResourceList.results for validating functions
|
||||
var result *Result
|
||||
if err != nil {
|
||||
if val, ok := err.(Result); ok {
|
||||
if len(val.Items) > 0 {
|
||||
result = &val
|
||||
b, err := yaml.Marshal(val)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
y, err := yaml.Parse(string(b))
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
rw.Results = y
|
||||
}
|
||||
rl.Result = &val
|
||||
} else {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
// write the results
|
||||
if err := rw.Write(nodes); err != nil {
|
||||
return errors.Wrap(err)
|
||||
if err := rl.Write(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result != nil && result.ExitCode() != 0 {
|
||||
|
||||
@@ -39,8 +39,6 @@ type FunctionSpec struct {
|
||||
|
||||
type ExecSpec struct {
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
|
||||
Args []string `json:"args,omitempty" yaml:"args,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerSpec defines a spec for running a function as a container
|
||||
|
||||
Reference in New Issue
Block a user