Skip to content

Commit 0fca8a1

Browse files
authored
Resolve functions using function objects registered in apiserver (#3633)
The intent is that many users will run a function mirror or similar.
1 parent fa37dbc commit 0fca8a1

File tree

9 files changed

+117
-38
lines changed

9 files changed

+117
-38
lines changed

porch/deployments/porch/5-rbac.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ rules:
2424
resources:
2525
["mutatingwebhookconfigurations", "validatingwebhookconfigurations"]
2626
verbs: ["get", "watch", "list"]
27+
- apiGroups: ["porch.kpt.dev"]
28+
resources: ["functions"]
29+
verbs: ["get", "list", "watch", "create", "update", "patch"]
2730
- apiGroups: ["config.porch.kpt.dev"]
2831
resources: ["repositories", "repositories/status"]
2932
verbs: ["get", "list", "watch", "create", "update", "patch"]

porch/pkg/apiserver/apiserver.go

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ import (
2020

2121
"github.com/GoogleContainerTools/kpt/internal/fnruntime"
2222
"github.com/GoogleContainerTools/kpt/porch/api/porch/install"
23+
porchapi "github.com/GoogleContainerTools/kpt/porch/api/porch/v1alpha1"
2324
configapi "github.com/GoogleContainerTools/kpt/porch/api/porchconfig/v1alpha1"
2425
internalapi "github.com/GoogleContainerTools/kpt/porch/internal/api/porchinternal/v1alpha1"
2526
"github.com/GoogleContainerTools/kpt/porch/pkg/cache"
2627
"github.com/GoogleContainerTools/kpt/porch/pkg/engine"
27-
"github.com/GoogleContainerTools/kpt/porch/pkg/kpt"
2828
"github.com/GoogleContainerTools/kpt/porch/pkg/meta"
2929
"github.com/GoogleContainerTools/kpt/porch/pkg/registry/porch"
3030
"google.golang.org/api/option"
@@ -73,6 +73,7 @@ type ExtraConfig struct {
7373
CoreAPIKubeconfigPath string
7474
CacheDirectory string
7575
FunctionRunnerAddress string
76+
DefaultImagePrefix string
7677
}
7778

7879
// Config defines the config for the apiserver
@@ -147,6 +148,11 @@ func (c completedConfig) getCoreClient() (client.WithWatch, error) {
147148
if err := configapi.AddToScheme(scheme); err != nil {
148149
return nil, fmt.Errorf("error building scheme: %w", err)
149150
}
151+
152+
if err := porchapi.AddToScheme(scheme); err != nil {
153+
return nil, fmt.Errorf("error building scheme: %w", err)
154+
}
155+
150156
if err := corev1.AddToScheme(scheme); err != nil {
151157
return nil, fmt.Errorf("error building scheme: %w", err)
152158
}
@@ -210,18 +216,25 @@ func (c completedConfig) New() (*PorchServer, error) {
210216
referenceResolver := porch.NewReferenceResolver(coreClient)
211217
userInfoProvider := &porch.ApiserverUserInfoProvider{}
212218

213-
runnerOptions := fnruntime.RunnerOptions{}
214-
runnerOptions.ResolveToImage = resolveToImagePorch
215-
216-
runnerOptions.InitDefaults()
217-
218-
renderer := kpt.NewRenderer(runnerOptions)
219-
220219
cache := cache.NewCache(c.ExtraConfig.CacheDirectory, cache.CacheOptions{
221220
CredentialResolver: credentialResolver,
222221
UserInfoProvider: userInfoProvider,
223222
MetadataStore: metadataStore,
224223
})
224+
225+
runnerOptionsResolver := func(namespace string) fnruntime.RunnerOptions {
226+
runnerOptions := fnruntime.RunnerOptions{}
227+
runnerOptions.InitDefaults()
228+
r := &KubeFunctionResolver{
229+
client: coreClient,
230+
defaultImagePrefix: c.ExtraConfig.DefaultImagePrefix,
231+
namespace: namespace,
232+
}
233+
runnerOptions.ResolveToImage = r.resolveToImagePorch
234+
235+
return runnerOptions
236+
}
237+
225238
cad, err := engine.NewCaDEngine(
226239
engine.WithCache(cache),
227240
// The order of registering the function runtimes matters here. When
@@ -230,7 +243,7 @@ func (c completedConfig) New() (*PorchServer, error) {
230243
engine.WithBuiltinFunctionRuntime(),
231244
engine.WithGRPCFunctionRuntime(c.ExtraConfig.FunctionRunnerAddress),
232245
engine.WithCredentialResolver(credentialResolver),
233-
engine.WithRenderer(renderer),
246+
engine.WithRunnerOptionsResolver(runnerOptionsResolver),
234247
engine.WithReferenceResolver(referenceResolver),
235248
engine.WithUserInfoProvider(userInfoProvider),
236249
engine.WithMetadataStore(metadataStore),

porch/pkg/apiserver/resolvetoimage.go

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,55 @@ import (
2020
"strings"
2121

2222
"github.com/GoogleContainerTools/kpt/internal/util/porch"
23+
"github.com/GoogleContainerTools/kpt/porch/api/porch/v1alpha1"
24+
apierrors "k8s.io/apimachinery/pkg/api/errors"
25+
"k8s.io/apimachinery/pkg/types"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
2327
)
2428

29+
// KubeFunctionResolver resolves function names to full image paths
30+
type KubeFunctionResolver struct {
31+
client client.WithWatch
32+
defaultImagePrefix string
33+
// resolver *FunctionResolver
34+
namespace string
35+
}
36+
2537
// resolveToImagePorch converts the function short path to the full image url.
2638
// If the function is Catalog function, it adds "gcr.io/kpt-fn/".e.g. set-namespace:v0.1 --> gcr.io/kpt-fn/set-namespace:v0.1
2739
// If the function is porch function, it queries porch to get the function image by name and namespace.
2840
// e.g. default:set-namespace:v0.1 --> us-west1-docker.pkg.dev/cpa-kit-dev/packages/set-namespace:v0.1
29-
func resolveToImagePorch(ctx context.Context, image string) (string, error) {
41+
func (r *KubeFunctionResolver) resolveToImagePorch(ctx context.Context, image string) (string, error) {
3042
segments := strings.Split(image, ":")
3143
if len(segments) == 4 {
3244
// Porch function
45+
// TODO: Remove this legacy configuration
3346
functionName := strings.Join(segments[1:], ":")
3447
function, err := porch.FunctionGetter{}.Get(ctx, functionName, segments[0])
3548
if err != nil {
36-
return "", fmt.Errorf("failed to resolve image: %w", err)
49+
return "", fmt.Errorf("failed to get image for function %q: %w", image, err)
3750
}
3851
return function.Spec.Image, nil
3952
}
4053
if !strings.Contains(image, "/") {
41-
return fmt.Sprintf("gcr.io/kpt-fn/%s", image), nil
54+
var function v1alpha1.Function
55+
// TODO: Use fieldSelectors and better lookup
56+
name := "functions:" + image + ":latest"
57+
key := types.NamespacedName{
58+
Namespace: r.namespace,
59+
Name: name,
60+
}
61+
// We query the apiserver for these types, even if we could query directly; this will then work with CRDs etc.
62+
// TODO: We need to think about priority-and-fairness with loopback queries
63+
if err := r.client.Get(ctx, key, &function); err != nil {
64+
if !apierrors.IsNotFound(err) {
65+
return "", fmt.Errorf("failed to get image for function %q: %w", image, err)
66+
}
67+
} else {
68+
return function.Spec.Image, nil
69+
}
70+
// TODO: Fallback to cluster-scoped?
71+
return r.defaultImagePrefix + image, nil
4272
}
4373
return image, nil
4474
}

porch/pkg/cmd/server/start.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type PorchServerOptions struct {
5050
CacheDirectory string
5151
CoreAPIKubeconfigPath string
5252
FunctionRunnerAddress string
53+
DefaultImagePrefix string
5354

5455
SharedInformerFactory informers.SharedInformerFactory
5556
StdOut io.Writer
@@ -182,6 +183,7 @@ func (o *PorchServerOptions) Config() (*apiserver.Config, error) {
182183
CoreAPIKubeconfigPath: o.CoreAPIKubeconfigPath,
183184
CacheDirectory: o.CacheDirectory,
184185
FunctionRunnerAddress: o.FunctionRunnerAddress,
186+
DefaultImagePrefix: o.DefaultImagePrefix,
185187
},
186188
}
187189
return config, nil
@@ -225,5 +227,6 @@ func (o *PorchServerOptions) AddFlags(fs *pflag.FlagSet) {
225227
}
226228

227229
fs.StringVar(&o.FunctionRunnerAddress, "function-runner", "", "Address of the function runner gRPC service.")
230+
fs.StringVar(&o.DefaultImagePrefix, "default-image-prefix", "gcr.io/kpt-fn/", "Default prefix for unqualified function names")
228231
fs.StringVar(&o.CacheDirectory, "cache-directory", "", "Directory where Porch server stores repository and package caches.")
229232
}

porch/pkg/engine/engine.go

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"unicode"
2828

2929
"github.com/GoogleContainerTools/kpt/internal/builtins"
30+
"github.com/GoogleContainerTools/kpt/internal/fnruntime"
3031
kptfile "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
3132
"github.com/GoogleContainerTools/kpt/pkg/fn"
3233
api "github.com/GoogleContainerTools/kpt/porch/api/porch/v1alpha1"
@@ -44,6 +45,7 @@ import (
4445
"k8s.io/apimachinery/pkg/runtime/schema"
4546
"k8s.io/apimachinery/pkg/types"
4647
"k8s.io/klog/v2"
48+
"sigs.k8s.io/controller-runtime/pkg/client"
4749
"sigs.k8s.io/kustomize/kyaml/comments"
4850
"sigs.k8s.io/kustomize/kyaml/kio"
4951
"sigs.k8s.io/kustomize/kyaml/yaml"
@@ -137,8 +139,11 @@ func NewCaDEngine(opts ...EngineOption) (CaDEngine, error) {
137139
}
138140

139141
type cadEngine struct {
140-
cache *cache.Cache
141-
renderer fn.Renderer
142+
cache *cache.Cache
143+
144+
// runnerOptionsResolver returns the RunnerOptions for function execution in the specified namespace.
145+
runnerOptionsResolver func(namespace string) fnruntime.RunnerOptions
146+
142147
runtime fn.FunctionRuntime
143148
credentialResolver repository.CredentialResolver
144149
referenceResolver ReferenceResolver
@@ -411,7 +416,7 @@ func (cad *cadEngine) applyTasks(ctx context.Context, draft repository.PackageDr
411416
}
412417

413418
// Render package after creation.
414-
mutations = cad.conditionalAddRender(mutations)
419+
mutations = cad.conditionalAddRender(obj, mutations)
415420

416421
baseResources := repository.PackageResources{}
417422
if _, _, err := applyResourceMutations(ctx, draft, baseResources, mutations); err != nil {
@@ -488,14 +493,17 @@ func (cad *cadEngine) mapTaskToMutation(ctx context.Context, obj *api.PackageRev
488493
// TODO: We should find a different way to do this. Probably a separate
489494
// task for render.
490495
if task.Eval.Image == "render" {
496+
runnerOptions := cad.runnerOptionsResolver(obj.Namespace)
491497
return &renderPackageMutation{
492-
renderer: cad.renderer,
493-
runtime: cad.runtime,
498+
runnerOptions: runnerOptions,
499+
runtime: cad.runtime,
494500
}, nil
495501
} else {
502+
runnerOptions := cad.runnerOptionsResolver(obj.Namespace)
496503
return &evalFunctionMutation{
497-
runtime: cad.runtime,
498-
task: task,
504+
runnerOptions: runnerOptions,
505+
runtime: cad.runtime,
506+
task: task,
499507
}, nil
500508
}
501509

@@ -598,7 +606,7 @@ func (cad *cadEngine) UpdatePackageRevision(ctx context.Context, repositoryObj *
598606
}
599607

600608
// Re-render if we are making changes.
601-
mutations = cad.conditionalAddRender(mutations)
609+
mutations = cad.conditionalAddRender(newObj, mutations)
602610

603611
draft, err := repo.UpdatePackageRevision(ctx, oldPackage.repoPackageRevision)
604612
if err != nil {
@@ -620,7 +628,7 @@ func (cad *cadEngine) UpdatePackageRevision(ctx context.Context, repositoryObj *
620628
}
621629

622630
// Re-render if we are making changes.
623-
mutations = cad.conditionalAddRender(mutations)
631+
mutations = cad.conditionalAddRender(newObj, mutations)
624632

625633
// TODO: Handle the case if alongside lifecycle change, tasks are changed too.
626634
// Update package contents only if the package is in draft state
@@ -752,7 +760,7 @@ func convertStatusToKptfile(s api.ConditionStatus) kptfile.ConditionStatus {
752760

753761
// conditionalAddRender adds a render mutation to the end of the mutations slice if the last
754762
// entry is not already a render mutation.
755-
func (cad *cadEngine) conditionalAddRender(mutations []mutation) []mutation {
763+
func (cad *cadEngine) conditionalAddRender(subject client.Object, mutations []mutation) []mutation {
756764
if len(mutations) == 0 {
757765
return mutations
758766
}
@@ -763,9 +771,11 @@ func (cad *cadEngine) conditionalAddRender(mutations []mutation) []mutation {
763771
return mutations
764772
}
765773

774+
runnerOptions := cad.runnerOptionsResolver(subject.GetNamespace())
775+
766776
return append(mutations, &renderPackageMutation{
767-
renderer: cad.renderer,
768-
runtime: cad.runtime,
777+
runnerOptions: runnerOptions,
778+
runtime: cad.runtime,
769779
})
770780
}
771781

@@ -888,6 +898,8 @@ func (cad *cadEngine) UpdatePackageResources(ctx context.Context, repositoryObj
888898
return nil, nil, err
889899
}
890900

901+
runnerOptions := cad.runnerOptionsResolver(old.GetNamespace())
902+
891903
mutations := []mutation{
892904
&mutationReplaceResources{
893905
newResources: new,
@@ -916,8 +928,8 @@ func (cad *cadEngine) UpdatePackageResources(ctx context.Context, repositoryObj
916928
draft,
917929
appliedResources,
918930
[]mutation{&renderPackageMutation{
919-
renderer: cad.renderer,
920-
runtime: cad.runtime,
931+
runnerOptions: runnerOptions,
932+
runtime: cad.runtime,
921933
}})
922934

923935
// No lifecycle change when updating package resources; updates are done.

porch/pkg/engine/eval.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ import (
3030
)
3131

3232
type evalFunctionMutation struct {
33-
runtime fn.FunctionRuntime
34-
task *api.Task
33+
runtime fn.FunctionRuntime
34+
runnerOptions fnruntime.RunnerOptions
35+
task *api.Task
3536
}
3637

3738
func (m *evalFunctionMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) {
@@ -40,11 +41,20 @@ func (m *evalFunctionMutation) Apply(ctx context.Context, resources repository.P
4041

4142
e := m.task.Eval
4243

44+
function := v1.Function{
45+
Image: e.Image,
46+
}
47+
if function.Image != "" && m.runnerOptions.ResolveToImage != nil {
48+
img, err := m.runnerOptions.ResolveToImage(ctx, function.Image)
49+
if err != nil {
50+
return repository.PackageResources{}, nil, err
51+
}
52+
function.Image = img
53+
}
54+
4355
// TODO: Apply should accept filesystem instead of PackageResources
4456

45-
runner, err := m.runtime.GetRunner(ctx, &v1.Function{
46-
Image: e.Image,
47-
})
57+
runner, err := m.runtime.GetRunner(ctx, &function)
4858
if err != nil {
4959
return repository.PackageResources{}, nil, fmt.Errorf("failed to create function runner: %w", err)
5060
}

porch/pkg/engine/options.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package engine
1717
import (
1818
"fmt"
1919

20+
"github.com/GoogleContainerTools/kpt/internal/fnruntime"
2021
"github.com/GoogleContainerTools/kpt/pkg/fn"
2122
"github.com/GoogleContainerTools/kpt/porch/pkg/cache"
2223
"github.com/GoogleContainerTools/kpt/porch/pkg/kpt"
@@ -88,9 +89,13 @@ func WithSimpleFunctionRuntime() EngineOption {
8889
})
8990
}
9091

91-
func WithRenderer(renderer fn.Renderer) EngineOption {
92+
func WithRunnerOptions(options fnruntime.RunnerOptions) EngineOption {
93+
return WithRunnerOptionsResolver(func(namespace string) fnruntime.RunnerOptions { return options })
94+
}
95+
96+
func WithRunnerOptionsResolver(fn func(namespace string) fnruntime.RunnerOptions) EngineOption {
9297
return EngineOptionFunc(func(engine *cadEngine) error {
93-
engine.renderer = renderer
98+
engine.runnerOptionsResolver = fn
9499
return nil
95100
})
96101
}

porch/pkg/engine/render.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,20 @@ import (
2121
"path"
2222
"strings"
2323

24+
"github.com/GoogleContainerTools/kpt/internal/fnruntime"
2425
fnresult "github.com/GoogleContainerTools/kpt/pkg/api/fnresult/v1"
2526
"github.com/GoogleContainerTools/kpt/pkg/fn"
2627
api "github.com/GoogleContainerTools/kpt/porch/api/porch/v1alpha1"
28+
"github.com/GoogleContainerTools/kpt/porch/pkg/kpt"
2729
"github.com/GoogleContainerTools/kpt/porch/pkg/repository"
2830
"go.opentelemetry.io/otel/trace"
2931
"k8s.io/klog/v2"
3032
"sigs.k8s.io/kustomize/kyaml/filesys"
3133
)
3234

3335
type renderPackageMutation struct {
34-
renderer fn.Renderer
35-
runtime fn.FunctionRuntime
36+
runtime fn.FunctionRuntime
37+
runnerOptions fnruntime.RunnerOptions
3638
}
3739

3840
var _ mutation = &renderPackageMutation{}
@@ -62,7 +64,8 @@ func (m *renderPackageMutation) Apply(ctx context.Context, resources repository.
6264
// TODO: we should handle this better
6365
klog.Warningf("skipping render as no package was found")
6466
} else {
65-
result, err := m.renderer.Render(ctx, fs, fn.RenderOptions{
67+
renderer := kpt.NewRenderer(m.runnerOptions)
68+
result, err := renderer.Render(ctx, fs, fn.RenderOptions{
6669
PkgPath: pkgPath,
6770
Runtime: m.runtime,
6871
})

porch/pkg/engine/render_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ func TestRender(t *testing.T) {
3434
runnerOptions.InitDefaults()
3535

3636
render := &renderPackageMutation{
37-
renderer: kpt.NewRenderer(runnerOptions),
38-
runtime: kpt.NewSimpleFunctionRuntime(),
37+
runnerOptions: runnerOptions,
38+
runtime: kpt.NewSimpleFunctionRuntime(),
3939
}
4040

4141
testdata, err := filepath.Abs(filepath.Join(".", "testdata", "simple-render"))

0 commit comments

Comments
 (0)