diff --git a/internal/fnruntime/runner.go b/internal/fnruntime/runner.go index 902d3c21f..90049be35 100644 --- a/internal/fnruntime/runner.go +++ b/internal/fnruntime/runner.go @@ -574,6 +574,42 @@ func newFnConfig(fsys filesys.FileSystem, f *kptfilev1.Function, pkgPath types.U var node *yaml.RNode switch { + case f.ResourceRef != nil: + // Handle resource reference + resources, err := fsys.ReadFile(string(pkgPath)) + if err != nil { + return nil, errors.E(op, fn, + fmt.Errorf("failed to read package resources: %w", err)) + } + + nodes, err := yaml.Parse(string(resources)) + if err != nil { + return nil, errors.E(op, fn, + fmt.Errorf("failed to parse package resources: %w", err)) + } + + // Find the referenced resource + elements, err := nodes.Elements() + if err != nil { + return nil, errors.E(op, fn, err) + } + + for _, res := range elements { + meta, err := res.GetMeta() + if err != nil { + continue + } + + if meta.Name == f.ResourceRef.Name && + meta.Namespace == f.ResourceRef.Namespace && + meta.Kind == f.ResourceRef.Kind { + return res, nil + } + } + return nil, errors.E(op, fn, + fmt.Errorf("resource not found: %s/%s/%s", + f.ResourceRef.Kind, f.ResourceRef.Namespace, f.ResourceRef.Name)) + case f.ConfigPath != "": path := filepath.Join(string(pkgPath), f.ConfigPath) file, err := fsys.Open(path) diff --git a/pkg/api/kptfile/v1/types.go b/pkg/api/kptfile/v1/types.go index 57563c256..2196d0cea 100644 --- a/pkg/api/kptfile/v1/types.go +++ b/pkg/api/kptfile/v1/types.go @@ -266,6 +266,14 @@ type Pipeline struct { Validators []Function `yaml:"validators,omitempty" json:"validators,omitempty"` } +// ResourceReference allows referencing a resource in the package +type ResourceReference struct { + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` +} + + // String returns the string representation of Pipeline struct // The string returned is the struct content in Go default format. func (p *Pipeline) String() string { @@ -327,6 +335,8 @@ type Function struct { // `Exclude` are used to specify resources on which the function should NOT be executed. // If not specified, all resources selected by `Selectors` are selected. Exclusions []Selector `yaml:"exclude,omitempty" json:"exclude,omitempty"` + + ResourceRef *ResourceReference `json:"resourceRef,omitempty" yaml:"resourceRef,omitempty"` } // Selector specifies the selection criteria diff --git a/pkg/api/kptfile/v1/validation.go b/pkg/api/kptfile/v1/validation.go index a9dff697e..6ae15bea7 100644 --- a/pkg/api/kptfile/v1/validation.go +++ b/pkg/api/kptfile/v1/validation.go @@ -65,7 +65,6 @@ func (p *Pipeline) validate(fsys filesys.FileSystem, pkgPath types.UniquePath) e } return nil } - func (f *Function) validate(fsys filesys.FileSystem, fnType string, idx int, pkgPath types.UniquePath) error { if f.Image == "" && f.Exec == "" { return &ValidateError{ @@ -89,7 +88,34 @@ func (f *Function) validate(fsys filesys.FileSystem, fnType string, idx int, pkg } } } - // TODO(droot): validate the exec + + // Validate ResourceRef + if f.ResourceRef != nil { + if f.ConfigPath != "" { + return &ValidateError{ + Field: fmt.Sprintf("pipeline.%s[%d]", fnType, idx), + Reason: "resourceRef cannot be specified with configPath", + } + } + if len(f.ConfigMap) != 0 { + return &ValidateError{ + Field: fmt.Sprintf("pipeline.%s[%d]", fnType, idx), + Reason: "resourceRef cannot be specified with configMap", + } + } + if f.ResourceRef.Name == "" { + return &ValidateError{ + Field: fmt.Sprintf("pipeline.%s[%d].resourceRef", fnType, idx), + Reason: "name is required", + } + } + if f.ResourceRef.Kind == "" { + return &ValidateError{ + Field: fmt.Sprintf("pipeline.%s[%d].resourceRef", fnType, idx), + Reason: "kind is required", + } + } + } if len(f.ConfigMap) != 0 && f.ConfigPath != "" { return &ValidateError{ diff --git a/pkg/api/kptfile/v1/validation_test.go b/pkg/api/kptfile/v1/validation_test.go index 02c01362f..e6cb22769 100644 --- a/pkg/api/kptfile/v1/validation_test.go +++ b/pkg/api/kptfile/v1/validation_test.go @@ -149,6 +149,93 @@ func TestKptfileValidate(t *testing.T) { }, valid: false, }, + { + name: "pipeline: valid resource reference", + kptfile: KptFile{ + Pipeline: &Pipeline{ + Mutators: []Function{ + { + Image: "gcr.io/kpt-fn/set-labels:v0.1", + ResourceRef: &ResourceReference{ + Name: "my-config", + Kind: "ConfigMap", + }, + }, + }, + }, + }, + valid: true, + }, + { + name: "pipeline: resource ref missing name", + kptfile: KptFile{ + Pipeline: &Pipeline{ + Mutators: []Function{ + { + Image: "gcr.io/kpt-fn/set-labels:v0.1", + ResourceRef: &ResourceReference{ + Kind: "ConfigMap", + }, + }, + }, + }, + }, + valid: false, + }, + { + name: "pipeline: resource ref missing kind", + kptfile: KptFile{ + Pipeline: &Pipeline{ + Mutators: []Function{ + { + Image: "gcr.io/kpt-fn/set-labels:v0.1", + ResourceRef: &ResourceReference{ + Name: "my-config", + }, + }, + }, + }, + }, + valid: false, + }, + { + name: "pipeline: resource ref with configPath", + kptfile: KptFile{ + Pipeline: &Pipeline{ + Mutators: []Function{ + { + Image: "gcr.io/kpt-fn/set-labels:v0.1", + ConfigPath: "config.yaml", + ResourceRef: &ResourceReference{ + Name: "my-config", + Kind: "ConfigMap", + }, + }, + }, + }, + }, + valid: false, + }, + { + name: "pipeline: resource ref with configMap", + kptfile: KptFile{ + Pipeline: &Pipeline{ + Mutators: []Function{ + { + Image: "gcr.io/kpt-fn/set-labels:v0.1", + ConfigMap: map[string]string{ + "key": "value", + }, + ResourceRef: &ResourceReference{ + Name: "my-config", + Kind: "ConfigMap", + }, + }, + }, + }, + }, + valid: false, + }, } for _, c := range cases {