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
15 changes: 15 additions & 0 deletions cmd/func-operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ type cliFlags struct {
funcCLIPath string
funcCLICheckInterval time.Duration
disableFuncCLIUpdate bool
createConfig bool
}

func parseFlags() cliFlags {
Expand Down Expand Up @@ -108,6 +109,8 @@ func parseFlags() cliFlags {
flag.DurationVar(&flags.funcCLICheckInterval, "func-cli-check-interval", 5*time.Minute,
"How often to check for new func CLI versions")
flag.BoolVar(&flags.disableFuncCLIUpdate, "disable-func-cli-update", false, "Disable the function-cli update")
flag.BoolVar(&flags.createConfig, "create-config", false,
"If set, create the default controller-config ConfigMap at startup if it does not already exist.")
opts := zap.Options{
Development: true,
}
Expand Down Expand Up @@ -304,6 +307,18 @@ func main() {
metricsServerOptions := setupMetricsServerOptions(flags.metricsAddr, flags.secureMetrics, metricsCertWatcher, tlsOpts)

operatorNamespace := getOperatorNamespace()

// Optionally create the default controller-config ConfigMap before the manager
// starts. This is used when deploying via OLM, where the default ConfigMap is
// not part of the bundle. When deploying via the config/ manifests, the
// ConfigMap is managed there and this flag is left unset.
if flags.createConfig {
if err := controller.EnsureDefaultConfigMap(context.Background(), operatorNamespace); err != nil {
setupLog.Error(err, "failed to ensure default controller-config ConfigMap")
os.Exit(1)
}
}

cacheOpts := setupCacheOptions(operatorNamespace)

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Expand Down
80 changes: 80 additions & 0 deletions internal/controller/controller_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
Copyright 2025.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controller

import (
"context"
"fmt"

v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log"
)

// defaultAutoUpdateMiddleware mirrors the value shipped in
// config/manager/manager.yaml. It is used when creating the controller-config
// ConfigMap for deployments that do not provide one (e.g. OLM bundles that omit
// the default ConfigMap).
const defaultAutoUpdateMiddleware = "true"

// EnsureDefaultConfigMap creates the controller-config ConfigMap with default
// values if it does not already exist. It is a no-op when the ConfigMap is
// already present. This allows the operator to be deployed via OLM, where the
// default ConfigMap is not part of the bundle.
func EnsureDefaultConfigMap(ctx context.Context, namespace string) error {
clientset, err := kubernetes.NewForConfig(ctrl.GetConfigOrDie())
if err != nil {
return fmt.Errorf("creating kubernetes clientset: %w", err)
}

_, err = clientset.CoreV1().ConfigMaps(namespace).Get(ctx, controllerConfigName, metav1.GetOptions{})
if err == nil {
log.Log.Info("controller-config ConfigMap already exists, not creating",
"name", controllerConfigName, "namespace", namespace)
return nil
}
if !apierrors.IsNotFound(err) {
return fmt.Errorf("checking for ConfigMap %s/%s: %w", namespace, controllerConfigName, err)
}

cm := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: controllerConfigName,
Namespace: namespace,
},
Data: map[string]string{
"autoUpdateMiddleware": defaultAutoUpdateMiddleware,
},
}

if _, err := clientset.CoreV1().ConfigMaps(namespace).Create(ctx, cm, metav1.CreateOptions{}); err != nil {
if apierrors.IsAlreadyExists(err) {
// Another replica created it concurrently; treat as success.
log.Log.Info("controller-config ConfigMap already created concurrently",
"name", controllerConfigName, "namespace", namespace)
return nil
}
return fmt.Errorf("creating default ConfigMap %s/%s: %w", namespace, controllerConfigName, err)
}

log.Log.Info("created default controller-config ConfigMap",
"name", controllerConfigName, "namespace", namespace)
return nil
}
2 changes: 1 addition & 1 deletion internal/controller/function_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ type FunctionReconciler struct {
// +kubebuilder:rbac:groups=functions.dev,resources=functions/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=functions.dev,resources=functions/finalizers,verbs=update
// +kubebuilder:rbac:groups="",resources=pods;pods/attach;secrets;services;persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create
// +kubebuilder:rbac:groups="apps",resources=deployments;replicasets,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="serving.knative.dev",resources=services;routes,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="eventing.knative.dev",resources=triggers,verbs=get;list;watch;create;update;patch;delete
Expand Down
Loading