Skip to content

Commit

Permalink
Add support to accept function spec from catalogs
Browse files Browse the repository at this point in the history
This commit modifies the logic to read function specs
from catalog in addition to the annotations. This feature
is currently enabled only with transformers.

Signed-off-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>
  • Loading branch information
varshaprasad96 committed Jan 5, 2024
1 parent ef7f06a commit 4ccb1bf
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 67 deletions.
51 changes: 17 additions & 34 deletions api/internal/plugins/fnplugin/fnplugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ package fnplugin
import (
"bytes"
"fmt"
"os"

"sigs.k8s.io/kustomize/kyaml/errors"
k8syaml "sigs.k8s.io/yaml"

"sigs.k8s.io/kustomize/api/internal/plugins/utils"
"sigs.k8s.io/kustomize/api/resmap"
Expand Down Expand Up @@ -54,47 +52,31 @@ func resourceToRNode(res *resource.Resource) (*yaml.RNode, error) {
}

// GetFunctionSpec return function spec is there is. Otherwise return nil
func GetFunctionSpec(res *resource.Resource) (*runtimeutil.FunctionSpec, error) {
rnode, err := resourceToRNode(res)
if err != nil {
return nil, fmt.Errorf("could not convert resource to RNode: %w", err)
}
functionSpec, err := runtimeutil.GetFunctionSpec(rnode)
if err != nil {
return nil, fmt.Errorf("failed to get FunctionSpec: %w", err)
}
return functionSpec, nil
}

func GetFunctionSpecFromCatalog(res *resource.Resource) (*runtimeutil.FunctionSpec, error) {
if res.GetKind() != framework.CatalogKind || res.GetApiVersion() != framework.FunctionDefinitionGroupVersion {
return nil, fmt.Errorf("invalid resource kind: %q and apiversion: %q", res.GetKind(), res.GetApiVersion())
}
func GetFunctionSpec(res *resource.Resource, catalogs []framework.Catalog) (*runtimeutil.FunctionSpec, error) {
var (
functionSpec *runtimeutil.FunctionSpec
err error
)

rnode, err := resourceToRNode(res)
if err != nil {
return nil, fmt.Errorf("could not convert resource to RNode: %w", err)
}

n, err := rnode.Pipe(yaml.Lookup("metadata", "configFn"))
if err != nil {
return nil, fmt.Errorf("failed to look up metadata.configFn: %w", err)
}
if yaml.IsMissingOrNull(n) {
return nil, nil
if catalogs != nil && len(catalogs) > 0 {

Check failure on line 66 in api/internal/plugins/fnplugin/fnplugin.go

View workflow job for this annotation

GitHub Actions / Lint

S1009: should omit nil check; len() for []sigs.k8s.io/kustomize/kyaml/fn/framework.Catalog is defined as zero (gosimple)
functionSpec, err = framework.FindMatchingFunctionSpec(rnode, catalogs)
if err != nil {
return nil, err

Check failure on line 69 in api/internal/plugins/fnplugin/fnplugin.go

View workflow job for this annotation

GitHub Actions / Lint

error returned from external package is unwrapped: sig: func sigs.k8s.io/kustomize/kyaml/fn/framework.FindMatchingFunctionSpec(res *sigs.k8s.io/kustomize/kyaml/yaml.RNode, catalogs []sigs.k8s.io/kustomize/kyaml/fn/framework.Catalog) (*sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil.FunctionSpec, error) (wrapcheck)
}
}
var fs runtimeutil.FunctionSpec

s, err := n.String()
if err != nil {
fmt.Fprintf(os.Stderr, "configFn parse error: %v\n", err)
return nil, fmt.Errorf("configFn parse error: %w", err)
}
if err := k8syaml.UnmarshalStrict([]byte(s), &fs); err != nil {
return nil, fmt.Errorf("%s unmarshal error: %w", "configFn", err)
if functionSpec == nil {
functionSpec, err = runtimeutil.GetFunctionSpec(rnode)
if err != nil {
return nil, fmt.Errorf("failed to get FunctionSpec: %w", err)
}
}
return &fs, nil

return functionSpec, nil
}

func toStorageMounts(mounts []string) []runtimeutil.StorageMount {
Expand All @@ -117,6 +99,7 @@ func NewFnPlugin(o *types.FnPluginLoadingOptions) *FnPlugin {
Env: o.Env,
AsCurrentUser: o.AsCurrentUser,
WorkingDir: o.WorkingDir,
Catalogs: o.Catalogs,
},
}
}
Expand Down
163 changes: 163 additions & 0 deletions api/internal/plugins/fnplugin/fnplugin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package fnplugin

import (
"strings"
"testing"

"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/api/provider"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

var factory = provider.NewDefaultDepProvider().GetResourceFactory()

Check failure on line 17 in api/internal/plugins/fnplugin/fnplugin_test.go

View workflow job for this annotation

GitHub Actions / Lint

factory is a global variable (gochecknoglobals)

func Test_GetFunctionSpec(t *testing.T) {
var tests = []struct {
name string
resource string
catalogs []framework.Catalog
expectedFn string
missingFn bool
}{
// fn annotation, no catalogs
{
name: "fn from annotation",
resource: `
apiVersion: example.com/v1
kind: Example
metadata:
name: test
annotations:
config.kubernetes.io/function: |-
container:
image: foo:v1.0.0
`,
expectedFn: `
container:
image: foo:v1.0.0
`,
catalogs: []framework.Catalog{},
},
// from catalog, no annotation
{
name: "fn from catalog",
resource: `
apiVersion: example.com/v1
kind: Example
metadata:
name: test
`,
catalogs: []framework.Catalog{
{
Spec: framework.CatalogSpec{
KrmFunctions: []framework.KrmFunctionDefinitionSpec{
{
Group: "example.com",
Names: framework.KRMFunctionNames{
Kind: "Example",
},
Versions: []framework.KRMFunctionVersion{
{
Name: "v1",
Runtime: runtimeutil.FunctionSpec{
Container: runtimeutil.ContainerSpec{
Image: "foo:v1.0.0",
},
},
},
},
},
},
},
},
},
expectedFn: `
container:
image: foo:v1.0.0
`,
},
{
// nil fn spec
resource: `
apiVersion: example.com/v1
kind: Example
metadata:
name: test
`,
catalogs: []framework.Catalog{},
missingFn: true,
},
// when both are provided, fn spec is taken from catalog.
{
name: "accept catalog fn spec when both are provided",
resource: `
apiVersion: example.com/v1
kind: Example
metadata:
name: test
annotations:
config.kubernetes.io/function: |-
container:
image: foo:v1.0.0
`,
catalogs: []framework.Catalog{
{
Spec: framework.CatalogSpec{
KrmFunctions: []framework.KrmFunctionDefinitionSpec{
{
Group: "example.com",
Names: framework.KRMFunctionNames{
Kind: "Example",
},
Versions: []framework.KRMFunctionVersion{
{
Name: "v1",
Runtime: runtimeutil.FunctionSpec{
Container: runtimeutil.ContainerSpec{
Image: "foocatalog:v1.0.0",
},
},
},
},
},
},
},
},
},
expectedFn: `
container:
image: foocatalog:v1.0.0
`,
},
}

for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
resource, err := factory.FromBytes([]byte(tt.resource))
assert.NoError(t, err)
fn, err := GetFunctionSpec(resource, tt.catalogs)
if tt.missingFn {
if err == nil && !assert.Nil(t, fn) {
t.FailNow()
}
return
}

b, err := yaml.Marshal(fn)
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t,
strings.TrimSpace(tt.expectedFn),
strings.TrimSpace(string(b))) {
t.FailNow()
}
})
}
}
23 changes: 13 additions & 10 deletions api/internal/plugins/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/filesys"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/resid"
)

Expand Down Expand Up @@ -79,7 +80,8 @@ func (l *Loader) LoadGenerators(

func (l *Loader) LoadGenerator(
ldr ifc.Loader, v ifc.Validator, res *resource.Resource) (resmap.Generator, error) {
c, err := l.loadAndConfigurePlugin(ldr, v, res)
// TODO: enable accepting catalogs through generators in follow up.
c, err := l.loadAndConfigurePlugin(ldr, v, res, nil)
if err != nil {
return nil, err
}
Expand All @@ -91,10 +93,10 @@ func (l *Loader) LoadGenerator(
}

func (l *Loader) LoadTransformers(
ldr ifc.Loader, v ifc.Validator, rm resmap.ResMap) ([]*resmap.TransformerWithProperties, error) {
ldr ifc.Loader, v ifc.Validator, rm resmap.ResMap, catalogs []framework.Catalog) ([]*resmap.TransformerWithProperties, error) {
var result []*resmap.TransformerWithProperties
for _, res := range rm.Resources() {
t, err := l.LoadTransformer(ldr, v, res)
t, err := l.LoadTransformer(ldr, v, res, catalogs)
if err != nil {
return nil, err
}
Expand All @@ -108,8 +110,8 @@ func (l *Loader) LoadTransformers(
}

func (l *Loader) LoadTransformer(
ldr ifc.Loader, v ifc.Validator, res *resource.Resource) (*resmap.TransformerWithProperties, error) {
c, err := l.loadAndConfigurePlugin(ldr, v, res)
ldr ifc.Loader, v ifc.Validator, res *resource.Resource, catalogs []framework.Catalog) (*resmap.TransformerWithProperties, error) {
c, err := l.loadAndConfigurePlugin(ldr, v, res, catalogs)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -183,11 +185,11 @@ func isBuiltinPlugin(res *resource.Resource) bool {
func (l *Loader) loadAndConfigurePlugin(
ldr ifc.Loader,
v ifc.Validator,
res *resource.Resource) (c resmap.Configurable, err error) {
res *resource.Resource, catalog []framework.Catalog) (c resmap.Configurable, err error) {
if isBuiltinPlugin(res) {
switch l.pc.BpLoadingOptions {
case types.BploLoadFromFileSys:
c, err = l.loadPlugin(res)
c, err = l.loadPlugin(res, catalog)
case types.BploUseStaticallyLinked:
// Instead of looking for and loading a .so file,
// instantiate the plugin from a generated factory
Expand All @@ -202,7 +204,7 @@ func (l *Loader) loadAndConfigurePlugin(
} else {
switch l.pc.PluginRestrictions {
case types.PluginRestrictionsNone:
c, err = l.loadPlugin(res)
c, err = l.loadPlugin(res, catalog)
case types.PluginRestrictionsBuiltinsOnly:
err = types.NewErrOnlyBuiltinPluginsAllowed(res.OrgId().Kind)
default:
Expand Down Expand Up @@ -237,8 +239,8 @@ func (l *Loader) makeBuiltinPlugin(r resid.Gvk) (resmap.Configurable, error) {
return nil, errors.Errorf("unable to load builtin %s", r)
}

func (l *Loader) loadPlugin(res *resource.Resource) (resmap.Configurable, error) {
spec, err := fnplugin.GetFunctionSpec(res)
func (l *Loader) loadPlugin(res *resource.Resource, catalog []framework.Catalog) (resmap.Configurable, error) {
spec, err := fnplugin.GetFunctionSpec(res, catalog)
if err != nil {
return nil, fmt.Errorf("loader: %w", err)
}
Expand All @@ -254,6 +256,7 @@ func (l *Loader) loadPlugin(res *resource.Resource) (resmap.Configurable, error)
"mount paths must be under the current kustomization directory", res.OrgId(), mount.Src)
}
}
l.pc.FnpLoadingOptions.Catalogs = catalog
return fnplugin.NewFnPlugin(&l.pc.FnpLoadingOptions), nil
}
return l.loadExecOrGoPlugin(res.OrgId())
Expand Down
Loading

0 comments on commit 4ccb1bf

Please sign in to comment.