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
45 changes: 45 additions & 0 deletions .github/workflows/test-e2e-bundle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Bundle E2E Tests

on:
push:
pull_request:

jobs:
test-bundle-e2e:
name: Run on Ubuntu
runs-on: ubuntu-latest
steps:
- name: Clone the code
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: Install the latest version of kind
run: |
curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind

- name: Verify kind installation
run: kind version

- name: Install helm
uses: azure/setup-helm@v4.3.0

# func CLI is needed in some e2e tests ATM
- name: Install func cli
uses: functions-dev/action@main
with:
version: nightly # use nightly as long as we use the latest in the operator too

- name: Setup KinD cluster
run: make create-kind-cluster

- name: Running Bundle Test e2e
env:
REGISTRY_INSECURE: true
REGISTRY: kind-registry:5000
run: make test-e2e-bundle
2 changes: 2 additions & 0 deletions .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ jobs:
# func CLI is needed in some e2e tests ATM
- name: Install func cli
uses: functions-dev/action@main
with:
version: nightly # use nightly as long as we use the latest in the operator too

- name: Setup KinD cluster
run: make create-kind-cluster
Expand Down
12 changes: 10 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,15 @@ test: manifests generate fmt vet setup-envtest ## Run tests.

.PHONY: test-e2e ## Run e2e tests.
test-e2e:
go test ./test/e2e/ -v -ginkgo.v
go test ./test/e2e/ -v -ginkgo.v -ginkgo.timeout=1h -ginkgo.label-filter="!bundle"

.PHONY: test-e2e-bundle ## Run bundle e2e tests.
test-e2e-bundle: operator-sdk docker-build docker-push bundle bundle-build bundle-push install-olm-in-cluster
OPERATOR_SDK=$(OPERATOR_SDK) BUNDLE_IMG=$(BUNDLE_IMG) go test -timeout 1h ./test/e2e/ -v -ginkgo.v -ginkgo.timeout=1h -ginkgo.label-filter="bundle"

.PHONY: install-olm-in-cluster
install-olm-in-cluster: operator-sdk ## Install OLM in cluster if not already installed.
@$(OPERATOR_SDK) olm status || $(OPERATOR_SDK) olm install

.PHONY: lint
lint: golangci-lint ## Run golangci-lint linter
Expand Down Expand Up @@ -381,4 +389,4 @@ catalog-push: ## Push a catalog image.

.PHONY: mocksgen
gen-mocks: ${MOCKERY}
${MOCKERY}
${MOCKERY}
29 changes: 29 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ import (
"flag"
"os"
"path/filepath"
"strings"
"time"

"github.com/functions-dev/func-operator/internal/git"
"github.com/functions-dev/func-operator/internal/monitoring"
"sigs.k8s.io/controller-runtime/pkg/cache"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
Expand Down Expand Up @@ -194,6 +196,20 @@ func main() {
})
}

watchNamespaces := getWatchNamespaces()
var cacheOpts cache.Options
if len(watchNamespaces) > 0 {
setupLog.Info("Operator watching specific namespaces", "namespaces", watchNamespaces)

// Map the namespaces into the Cache DefaultNamespaces map
cacheOpts.DefaultNamespaces = make(map[string]cache.Config)
for _, ns := range watchNamespaces {
cacheOpts.DefaultNamespaces[strings.TrimSpace(ns)] = cache.Config{}
}
} else {
setupLog.Info("Operator watching all namespaces")
}

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Metrics: metricsServerOptions,
Expand All @@ -212,6 +228,7 @@ func main() {
// if you are doing or is intended to do any operation such as perform cleanups
// after the manager stops then its usage might be unsafe.
// LeaderElectionReleaseOnCancel: true,
Cache: cacheOpts,
})
if err != nil {
setupLog.Error(err, "unable to start manager")
Expand Down Expand Up @@ -282,3 +299,15 @@ func main() {
os.Exit(1)
}
}

// getWatchNamespaces returns the Namespaces the operator should be watching for changes
func getWatchNamespaces() []string {
watchNamespaceEnvVar := "WATCH_NAMESPACE"
ns, found := os.LookupEnv(watchNamespaceEnvVar)
if !found || ns == "" {
return nil // Return nil to signify "watch all namespaces"
}

// Split by comma to support multiple namespaces
return strings.Split(ns, ",")
}
5 changes: 5 additions & 0 deletions config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ spec:
- --health-probe-bind-address=:8081
- --disable-func-cli-update=true
- --func-cli-path=/func
env:
- name: WATCH_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.annotations['olm.targetNamespaces']
image: controller:latest
imagePullPolicy: Always
name: manager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ spec:
deployments: null
strategy: ""
installModes:
- supported: false
- supported: true
type: OwnNamespace
- supported: false
- supported: true
type: SingleNamespace
- supported: false
- supported: true
type: MultiNamespace
- supported: true
type: AllNamespaces
Expand Down
21 changes: 0 additions & 21 deletions config/rbac/deploy_function_clusterrole.yaml

This file was deleted.

13 changes: 0 additions & 13 deletions config/rbac/deploy_function_clusterrole_binding.yaml

This file was deleted.

3 changes: 0 additions & 3 deletions config/rbac/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,3 @@ resources:
- function_editor_role.yaml
- function_viewer_role.yaml

- deploy_function_clusterrole.yaml
- deploy_function_clusterrole_binding.yaml

2 changes: 2 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ rules:
- rbac.authorization.k8s.io
resources:
- rolebindings
- roles
verbs:
- create
- delete
Expand All @@ -95,6 +96,7 @@ rules:
- apiGroups:
- serving.knative.dev
resources:
- routes
- services
verbs:
- create
Expand Down
87 changes: 74 additions & 13 deletions internal/controller/function_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
)

const (
deployFunctionRoleName = "func-operator-deploy-function"
)

// FunctionReconciler reconciles a Function object
type FunctionReconciler struct {
client.Client
Expand All @@ -58,11 +62,11 @@ type FunctionReconciler struct {
// +kubebuilder:rbac:groups=functions.dev,resources=functions/finalizers,verbs=update
// +kubebuilder:rbac:groups="",resources=secrets;services;persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="serving.knative.dev",resources=services,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
// +kubebuilder:rbac:groups=tekton.dev,resources=pipelines;pipelineruns,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=tekton.dev,resources=taskruns,verbs=get;list;watch
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings;roles,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=http.keda.sh,resources=httpscaledobjects,verbs=get;list;watch;create;update;patch;delete

// Reconcile a Function with status update
Expand Down Expand Up @@ -213,9 +217,67 @@ func (r *FunctionReconciler) updateFunctionStatus(function *v1alpha1.Function, m
}

func (r *FunctionReconciler) setupPipelineRBAC(ctx context.Context, function *v1alpha1.Function) error {
if err := r.ensureDeployFunctionRole(ctx, function.Namespace); err != nil {
return fmt.Errorf("failed to ensure deploy-function role: %w", err)
}

if err := r.ensureDeployFunctionRoleBinding(ctx, function); err != nil {
return fmt.Errorf("failed to ensure deploy-function role binding: %w", err)
}

return nil
}

// ensureDeployFunctionRole ensures the deploy-function Role exists in the namespace and is up-to-date.
// This is a namespace-scoped Role so multiple operator instances won't conflict.
func (r *FunctionReconciler) ensureDeployFunctionRole(ctx context.Context, namespace string) error {
logger := log.FromContext(ctx)

expectedRole := &rbacv1.Role{
ObjectMeta: metav1.ObjectMeta{
Name: deployFunctionRoleName,
Namespace: namespace,
},
Rules: []rbacv1.PolicyRule{
{
APIGroups: []string{"serving.knative.dev"},
Resources: []string{"services", "routes"},
Verbs: []string{"create", "delete", "get", "list", "patch", "update", "watch"},
},
},
}

foundRole := &rbacv1.Role{}
err := r.Get(ctx, types.NamespacedName{Name: expectedRole.Name, Namespace: expectedRole.Namespace}, foundRole)
if err != nil {
if apierrors.IsNotFound(err) {
if err := r.Create(ctx, expectedRole); err != nil {
return fmt.Errorf("failed to create role: %w", err)
}
logger.Info("Created deploy-function role")
return nil
}
return fmt.Errorf("failed to get role: %w", err)
}

// Role exists - update if needed
if !equality.Semantic.DeepEqual(expectedRole.Rules, foundRole.Rules) {
foundRole.Rules = expectedRole.Rules
if err := r.Update(ctx, foundRole); err != nil {
return fmt.Errorf("failed to update role: %w", err)
}
logger.Info("Updated deploy-function role")
} else {
logger.Info("Deploy-function role already up to date")
}

return nil
}

// ensureDeployFunctionRoleBinding ensures the RoleBinding for the deploy-function role exists and is up-to-date.
func (r *FunctionReconciler) ensureDeployFunctionRoleBinding(ctx context.Context, function *v1alpha1.Function) error {
logger := log.FromContext(ctx)

logger.Info("Create rolebinding for deploy-function role")
expectedRoleBinding := &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "deploy-function-default",
Expand All @@ -236,40 +298,39 @@ func (r *FunctionReconciler) setupPipelineRBAC(ctx context.Context, function *v1
Namespace: function.Namespace,
}},
RoleRef: rbacv1.RoleRef{
Kind: "ClusterRole",
Name: "func-operator-deploy-function",
APIGroup: "rbac.authorization.k8s.io",
Kind: "Role",
Name: deployFunctionRoleName,
},
}

foundRoleBinding := &rbacv1.RoleBinding{}
err := r.Get(ctx, types.NamespacedName{Name: expectedRoleBinding.Name, Namespace: expectedRoleBinding.Namespace}, foundRoleBinding)
if err != nil {
if apierrors.IsNotFound(err) {
if err := r.Create(ctx, expectedRoleBinding); err != nil {
return fmt.Errorf("failed to create role binding for deploy-function role: %w", err)
return fmt.Errorf("failed to create role binding: %w", err)
}
logger.Info("Created role binding for deploy-function role")
logger.Info("Created deploy-function role binding")
return nil
}
return fmt.Errorf("failed to check if deploy-function role binding already exists: %w", err)
return fmt.Errorf("failed to get role binding: %w", err)
}

// Update if needed
if !equality.Semantic.DeepDerivative(expectedRoleBinding, foundRoleBinding) {
// Copy expected values into found object
foundRoleBinding.Subjects = expectedRoleBinding.Subjects
foundRoleBinding.RoleRef = expectedRoleBinding.RoleRef
foundRoleBinding.OwnerReferences = expectedRoleBinding.OwnerReferences

if err := r.Update(ctx, foundRoleBinding); err != nil {
return fmt.Errorf("failed to update deploy-function role binding: %w", err)
return fmt.Errorf("failed to update role binding: %w", err)
}

logger.Info("Updated deploy-function role binding")
return nil
} else {
logger.Info("Deploy-function role binding already up to date")
}

logger.Info("Role binding already exists and is up to date. No need to update")
return nil
}

Expand Down
Loading
Loading