Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package reconciler
package functionconfigs

import (
"context"
Expand All @@ -35,6 +35,7 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/predicate"
)

const BaseFinalizer = "config.porch.kpt.dev/functionconfig"
Expand Down Expand Up @@ -250,15 +251,22 @@ const (
ReconcilerForController ReconcilerFor = "controller"
)

type FunctionConfigReconciler struct {
type Reconciler struct {
Client client.Client
FunctionConfigStore *FunctionConfigStore
// For indicates which component the reconciler is collecting the configs for
// TODO: remove after merging of function-runner into server
For ReconcilerFor
}

func (r *FunctionConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, finalErr error) {
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&configapi.FunctionConfig{}).
WithEventFilter(predicate.GenerationChangedPredicate{}).
Complete(r)
}

func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, finalErr error) {
klog.Infof("FunctionConfig %q changed", req.NamespacedName)
obj := &configapi.FunctionConfig{}
err := r.Client.Get(ctx, req.NamespacedName, obj)
Expand Down Expand Up @@ -330,7 +338,7 @@ func (r *FunctionConfigReconciler) Reconcile(ctx context.Context, req ctrl.Reque
return ctrl.Result{}, nil
}

func (r *FunctionConfigReconciler) removeFinalizer(ctx context.Context, obj *configapi.FunctionConfig) error {
func (r *Reconciler) removeFinalizer(ctx context.Context, obj *configapi.FunctionConfig) error {
patch := client.MergeFrom(obj.DeepCopy())

switch r.For {
Expand All @@ -350,7 +358,7 @@ func (r *FunctionConfigReconciler) removeFinalizer(ctx context.Context, obj *con
return nil
}

func (r *FunctionConfigReconciler) addFinalizer(ctx context.Context, obj *configapi.FunctionConfig) error {
func (r *Reconciler) addFinalizer(ctx context.Context, obj *configapi.FunctionConfig) error {
patch := client.MergeFrom(obj.DeepCopy())

updated := false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2025 The kpt Authors
// Copyright 2026 The kpt Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package reconciler
package functionconfigs

import (
"context"
Expand All @@ -39,7 +39,7 @@ func TestFunctionConfigReconciler(t *testing.T) {
type testcase struct {
name string
objs []client.Object // input: objects to seed the fake client
check func(t *testing.T, reconciler *FunctionConfigReconciler)
check func(t *testing.T, reconciler *Reconciler)
requests []string // name set in the reconcile request
expectErr bool
}
Expand Down Expand Up @@ -137,7 +137,7 @@ func TestFunctionConfigReconciler(t *testing.T) {
name: "FunctionConfig object is stored in FunctionStore after reconciliation",
objs: []client.Object{sampleFunctionConfig},
requests: []string{"set-image"},
check: func(t *testing.T, r *FunctionConfigReconciler) {
check: func(t *testing.T, r *Reconciler) {
// Check existence of the functionConfig in cluster
got, exists := r.FunctionConfigStore.GetFunctionConfig("set-image")
expectedNumberOfFunctions := 1
Expand All @@ -152,7 +152,7 @@ func TestFunctionConfigReconciler(t *testing.T) {
name: "FunctionConfig object is deleted from FunctionStore after reconciliation",
objs: []client.Object{},
requests: []string{"set-image"},
check: func(t *testing.T, r *FunctionConfigReconciler) {
check: func(t *testing.T, r *Reconciler) {
// Check existence of the functionConfig in cluster
_, exists := r.FunctionConfigStore.GetFunctionConfig("set-image")
assert.False(t, exists, "FunctionConfig 'set-image' should not exist in the store")
Expand All @@ -162,7 +162,7 @@ func TestFunctionConfigReconciler(t *testing.T) {
name: "BinaryExecutorCache is available with image",
objs: []client.Object{sampleFunctionConfig},
requests: []string{"set-image"},
check: func(t *testing.T, r *FunctionConfigReconciler) {
check: func(t *testing.T, r *Reconciler) {
expectedKey := "ghcr.io/kptdev/krm-functions-catalog/set-image:v0.1.4"
expectedPath := "/functions/set-image"
binary, exists := r.FunctionConfigStore.GetBinaryFromCache(expectedKey)
Expand All @@ -174,7 +174,7 @@ func TestFunctionConfigReconciler(t *testing.T) {
name: "BuiltInExecutorCache is available for starlark",
objs: []client.Object{builtInSetNamespace, builtInApplyReplacements, builtInStarlarkWithId},
requests: []string{"apply-replacements", "set-namespace", "starlark"},
check: func(t *testing.T, r *FunctionConfigReconciler) {
check: func(t *testing.T, r *Reconciler) {
expectedStarlarkKey := "starlark-id"
execFunctions := r.FunctionConfigStore.GetExecCache()

Expand All @@ -197,7 +197,7 @@ func TestFunctionConfigReconciler(t *testing.T) {
c := fake.NewClientBuilder().WithObjects(tt.objs...).WithScheme(scheme).WithStatusSubresource(&configapi.FunctionConfig{}).Build()

functionConfigStore := NewFunctionConfigStore(defaultImagePrefix, functionCacheDir)
reconciler := &FunctionConfigReconciler{
reconciler := &Reconciler{
Client: c,
FunctionConfigStore: functionConfigStore,
}
Expand Down Expand Up @@ -260,7 +260,7 @@ func TestFinalizersAdded(t *testing.T) {
}

c := fake.NewClientBuilder().WithScheme(schemeWithFunctionConfig(t)).WithObjects(obj).WithStatusSubresource(&configapi.FunctionConfig{}).Build()
r := &FunctionConfigReconciler{
r := &Reconciler{
Client: c,
FunctionConfigStore: NewFunctionConfigStore(defaultImagePrefix, functionCacheDir),
For: tc.forValue,
Expand Down Expand Up @@ -528,7 +528,7 @@ func TestFinalizersRemoved(t *testing.T) {
store := NewFunctionConfigStore(defaultImagePrefix, functionCacheDir)
store.UpsertFunctionConfig(objName, obj)

r := &FunctionConfigReconciler{
r := &Reconciler{
Client: c,
FunctionConfigStore: store,
For: tc.forValue,
Expand Down
20 changes: 8 additions & 12 deletions controllers/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

"slices"

"github.com/kptdev/porch/controllers/functionconfigs"
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
Expand All @@ -40,7 +41,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook"

"github.com/kptdev/kpt/pkg/lib/runneroptions"
"github.com/kptdev/porch/controllers/functionconfigs/reconciler"
"github.com/kptdev/porch/controllers/packagerevisions/pkg/controllers/packagerevision"
"github.com/kptdev/porch/controllers/packagevariants/pkg/controllers/packagevariant"
"github.com/kptdev/porch/controllers/packagevariantsets/pkg/controllers/packagevariantset"
Expand All @@ -52,7 +52,6 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

porchapi "github.com/kptdev/porch/api/porch/v1alpha1"
Expand Down Expand Up @@ -305,37 +304,34 @@ func setupReconciler(mgr ctrl.Manager, enabled []string, r Reconciler, started [
return append(started, name), nil
}

func setupFunctionConfigReconciler(mgr ctrl.Manager) (*reconciler.FunctionConfigStore, error) {
func setupFunctionConfigReconciler(mgr ctrl.Manager) (*functionconfigs.FunctionConfigStore, error) {
prefix := os.Getenv("DEFAULT_IMAGE_PREFIX")
if prefix == "" {
prefix = runneroptions.GHCRImagePrefix
}
functionConfigStore := reconciler.NewFunctionConfigStore(prefix, "")
functionConfigStore := functionconfigs.NewFunctionConfigStore(prefix, "")

rec := &reconciler.FunctionConfigReconciler{
rec := &functionconfigs.Reconciler{
Client: mgr.GetClient(),
FunctionConfigStore: functionConfigStore,
For: reconciler.ReconcilerForController,
For: functionconfigs.ReconcilerForController,
}

if err := ctrl.NewControllerManagedBy(mgr).
For(&configapi.FunctionConfig{}).
WithEventFilter(predicate.GenerationChangedPredicate{}).
Complete(rec); err != nil {
if err := rec.SetupWithManager(mgr); err != nil {
return nil, fmt.Errorf("error creating FunctionConfig controller: %w", err)
}

prePopulateFunctionConfigStore(mgr.GetAPIReader(), functionConfigStore)

klog.Infof("FunctionConfig reconciler registered (for: %s)", reconciler.ReconcilerForController)
klog.Infof("FunctionConfig reconciler registered (for: %s)", functionconfigs.ReconcilerForController)
return functionConfigStore, nil
}

// prePopulateFunctionConfigStore loads all FunctionConfigs into the store
// synchronously so the exec cache is ready before the PR controller starts.
// Without this, a pod restart leaves the cache empty until the async
// informer triggers reconciliation.
func prePopulateFunctionConfigStore(reader client.Reader, store *reconciler.FunctionConfigStore) {
func prePopulateFunctionConfigStore(reader client.Reader, store *functionconfigs.FunctionConfigStore) {
var fcList configapi.FunctionConfigList
if err := reader.List(context.Background(), &fcList); err != nil {
klog.Warningf("FunctionConfig pre-population failed (non-fatal): %v", err)
Expand Down
8 changes: 4 additions & 4 deletions controllers/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"testing"

configapi "github.com/kptdev/porch/api/porchconfig/v1alpha1"
"github.com/kptdev/porch/controllers/functionconfigs/reconciler"
"github.com/kptdev/porch/controllers/functionconfigs"
mockclient "github.com/kptdev/porch/test/mockery/mocks/external/sigs.k8s.io/controller-runtime/pkg/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
Expand Down Expand Up @@ -171,7 +171,7 @@ func TestPrePopulateFunctionConfigStore_Success(t *testing.T) {
list.(*configapi.FunctionConfigList).Items = items
}).Return(nil)

store := reconciler.NewFunctionConfigStore("ghcr.io/kptdev", "/tmp/bins")
store := functionconfigs.NewFunctionConfigStore("ghcr.io/kptdev", "/tmp/bins")
prePopulateFunctionConfigStore(mockReader, store)

_, ok := store.GetFunctionConfig("set-namespace")
Expand All @@ -186,7 +186,7 @@ func TestPrePopulateFunctionConfigStore_ListError(t *testing.T) {
mockReader := mockclient.NewMockReader(t)
mockReader.EXPECT().List(mock.Anything, mock.Anything, mock.Anything).Return(assert.AnError)

store := reconciler.NewFunctionConfigStore("ghcr.io/kptdev", "/tmp/bins")
store := functionconfigs.NewFunctionConfigStore("ghcr.io/kptdev", "/tmp/bins")
prePopulateFunctionConfigStore(mockReader, store)

_, ok := store.GetFunctionConfig("anything")
Expand All @@ -198,7 +198,7 @@ func TestPrePopulateFunctionConfigStore_EmptyList(t *testing.T) {
mockReader.EXPECT().List(mock.Anything, mock.AnythingOfType("*v1alpha1.FunctionConfigList"), mock.Anything).
Return(nil)

store := reconciler.NewFunctionConfigStore("ghcr.io/kptdev", "/tmp/bins")
store := functionconfigs.NewFunctionConfigStore("ghcr.io/kptdev", "/tmp/bins")
prePopulateFunctionConfigStore(mockReader, store)

assert.Equal(t, 0, len(store.List()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"flag"
"testing"

"github.com/kptdev/porch/controllers/functionconfigs/reconciler"
"github.com/kptdev/porch/controllers/functionconfigs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
Expand Down Expand Up @@ -62,7 +62,7 @@ func TestInit_NilCache(t *testing.T) {
r := &PackageRevisionReconciler{
RepoOperationRetryAttempts: 3,
MaxGRPCMessageSize: defaultMaxGRPCMessageSize,
FunctionConfigStore: reconciler.NewFunctionConfigStore("", ""),
FunctionConfigStore: functionconfigs.NewFunctionConfigStore("", ""),
}
err := r.Init(mgr)
require.NoError(t, err)
Expand All @@ -75,7 +75,7 @@ func TestInit_SetsCredResolverAndFetcher(t *testing.T) {
r := &PackageRevisionReconciler{
RepoOperationRetryAttempts: 3,
MaxGRPCMessageSize: defaultMaxGRPCMessageSize,
FunctionConfigStore: reconciler.NewFunctionConfigStore("", ""),
FunctionConfigStore: functionconfigs.NewFunctionConfigStore("", ""),
}

err := r.Init(mgr)
Expand All @@ -93,7 +93,7 @@ func TestInit_RendererEnabledWithFnRunner(t *testing.T) {
r := &PackageRevisionReconciler{
RepoOperationRetryAttempts: 3,
MaxGRPCMessageSize: defaultMaxGRPCMessageSize,
FunctionConfigStore: reconciler.NewFunctionConfigStore("", ""),
FunctionConfigStore: functionconfigs.NewFunctionConfigStore("", ""),
}

t.Setenv("FUNCTION_RUNNER_ADDRESS", "localhost:0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"time"

porchv1alpha2 "github.com/kptdev/porch/api/porch/v1alpha2"
"github.com/kptdev/porch/controllers/functionconfigs/reconciler"
"github.com/kptdev/porch/controllers/functionconfigs"
"github.com/kptdev/porch/pkg/repository"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -52,7 +52,7 @@ type PackageRevisionReconciler struct {
Scheme *runtime.Scheme
ContentCache repository.ContentCache
ExternalPackageFetcher repository.ExternalPackageFetcher
FunctionConfigStore *reconciler.FunctionConfigStore
FunctionConfigStore *functionconfigs.FunctionConfigStore
Renderer renderer // nil = skip rendering

MaxConcurrentReconciles int
Expand Down
Loading
Loading