From 9287d911eabdd8297c4850a38c76cc7a429d15ac Mon Sep 17 00:00:00 2001 From: Justin SB Date: Mon, 19 Sep 2022 15:30:29 -0400 Subject: [PATCH] klippy: a controller that auto-instantiates packages This controller will automatically create draft package instances where the bindings are satisfied. If the bindings are sufficiently constraining, this can act as a guided workflow to help users do the next right thing. --- .../controllers/klippy/config/rbac/role.yaml | 61 +++ .../klippy/config/rbac/rolebinding.yaml | 26 ++ porch/controllers/klippy/doc.go | 17 + .../controllers/klippy/klippy_controller.go | 436 ++++++++++++++++++ .../klippy/pkg/controllers/klippy/rbac.go | 21 + porch/controllers/main.go | 47 +- .../remoterootsync_controller.go | 12 + .../pkg/controllers/rootsyncset/controller.go | 18 +- .../workloadidentitybinding/controller.go | 13 +- 9 files changed, 629 insertions(+), 22 deletions(-) create mode 100644 porch/controllers/klippy/config/rbac/role.yaml create mode 100644 porch/controllers/klippy/config/rbac/rolebinding.yaml create mode 100644 porch/controllers/klippy/doc.go create mode 100644 porch/controllers/klippy/pkg/controllers/klippy/klippy_controller.go create mode 100644 porch/controllers/klippy/pkg/controllers/klippy/rbac.go diff --git a/porch/controllers/klippy/config/rbac/role.yaml b/porch/controllers/klippy/config/rbac/role.yaml new file mode 100644 index 0000000000..b243eb7e37 --- /dev/null +++ b/porch/controllers/klippy/config/rbac/role.yaml @@ -0,0 +1,61 @@ +# Copyright 2022 Google LLC +# +# 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. + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: porch-controllers-klippy +rules: +- apiGroups: + - porch.kpt.dev + resources: + - packagerevisionresources + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - porch.kpt.dev + resources: + - packagerevisionresources/status + verbs: + - get + - patch + - update +- apiGroups: + - porch.kpt.dev + resources: + - packagerevisions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - porch.kpt.dev + resources: + - packagerevisions/status + verbs: + - get + - patch + - update diff --git a/porch/controllers/klippy/config/rbac/rolebinding.yaml b/porch/controllers/klippy/config/rbac/rolebinding.yaml new file mode 100644 index 0000000000..8e41a8c715 --- /dev/null +++ b/porch/controllers/klippy/config/rbac/rolebinding.yaml @@ -0,0 +1,26 @@ +# Copyright 2022 Google LLC +# +# 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. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: porch-system:porch-controllers-klippy +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: porch-controllers-klippy +subjects: +- kind: ServiceAccount + name: porch-controllers + namespace: porch-system \ No newline at end of file diff --git a/porch/controllers/klippy/doc.go b/porch/controllers/klippy/doc.go new file mode 100644 index 0000000000..481427c691 --- /dev/null +++ b/porch/controllers/klippy/doc.go @@ -0,0 +1,17 @@ +// Copyright 2022 Google LLC +// +// 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 klippy + +//go:generate go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0 rbac:roleName=porch-controllers-klippy webhook paths="./..." output:rbac:artifacts:config=config/rbac diff --git a/porch/controllers/klippy/pkg/controllers/klippy/klippy_controller.go b/porch/controllers/klippy/pkg/controllers/klippy/klippy_controller.go new file mode 100644 index 0000000000..0c5fd2b86b --- /dev/null +++ b/porch/controllers/klippy/pkg/controllers/klippy/klippy_controller.go @@ -0,0 +1,436 @@ +// Copyright 2022 Google LLC +// +// 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 klippy + +import ( + "context" + "flag" + "fmt" + "path/filepath" + "strings" + "unicode" + + api "github.com/GoogleContainerTools/kpt/porch/api/porch/v1alpha1" + "github.com/GoogleContainerTools/kpt/porch/controllers/remoterootsyncsets/pkg/applyset" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/yaml" +) + +type Options struct { + // BindFunction is the image name for the "bind" function + BindFunction string +} + +func (o *Options) InitDefaults() { + o.BindFunction = "bind" +} + +func (o *Options) BindFlags(prefix string, flagset *flag.FlagSet) { + flagset.StringVar(&o.BindFunction, prefix+"bindFunction", o.BindFunction, "image name for the bind function") +} + +// KlippyReconciler reconciles Klippy objects +type KlippyReconciler struct { + Options + + client client.Client + + dynamicClient dynamic.Interface + restMapper meta.RESTMapper +} + +// Reconcile implements the main kubernetes reconciliation loop. +func (r *KlippyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := log.FromContext(ctx) + + log.Info("reconciling object", "id", req.NamespacedName) + + var parent api.PackageRevision + if err := r.client.Get(ctx, req.NamespacedName, &parent); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // TODO: Can we filter the watch? + if !parent.Status.Deployment { + log.V(2).Info("ignoring package not in deployments repository", "package", parent.Spec.PackageName) + return ctrl.Result{}, nil + } + + var parentResources api.PackageRevisionResources + if err := r.client.Get(ctx, req.NamespacedName, &parentResources); err != nil { + // Not found here is unexpected + return ctrl.Result{}, err + } + + if err := r.reconcile(ctx, &parent, &parentResources); err != nil { + // TODO: raise event? + log.Error(err, "error reconciling", "id", req.NamespacedName) + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +func isWhitespace(s string) bool { + for _, r := range s { + if !unicode.IsSpace(r) { + return false + } + } + return true +} + +func (r *KlippyReconciler) reconcile(ctx context.Context, parent *api.PackageRevision, parentResources *api.PackageRevisionResources) error { + log := log.FromContext(ctx) + + var blueprintPackageRevisions api.PackageRevisionList + if err := r.client.List(ctx, &blueprintPackageRevisions); err != nil { + return fmt.Errorf("error listing blueprints: %w", err) + } + + // TODO: We should be able to cache this + blueprints, err := r.parseBlueprints(ctx, &blueprintPackageRevisions) + if err != nil { + return err + } + + parentObjects, err := parseObjects(ctx, parentResources) + if err != nil { + return err + } + + var allProposals []*api.PackageRevision + for _, blueprint := range blueprints { + proposals, err := r.buildProposals(ctx, parent, parentObjects, blueprint) + if err != nil { + return err + } + allProposals = append(allProposals, proposals...) + } + + log.Info("built proposals", "proposal", allProposals) + if err := r.storeProposals(ctx, parent, allProposals); err != nil { + return err + } + + return nil +} + +func (r *KlippyReconciler) parseBlueprints(ctx context.Context, packageRevisions *api.PackageRevisionList) ([]*blueprint, error) { + log := log.FromContext(ctx) + + var blueprints []*blueprint + + for i := range packageRevisions.Items { + packageRevision := &packageRevisions.Items[i] + + if !strings.HasPrefix(packageRevision.Spec.PackageName, "infra/") { + log.Info("HACK: ignoring non-infra package", "package", packageRevision.Spec.PackageName) + continue + } + + // Only match blueprint packages + // TODO: Push-down into a field selector? + if packageRevision.Status.Deployment { + continue + } + + // TODO: Cache + + var packageRevisionResources api.PackageRevisionResources + id := types.NamespacedName{ + Namespace: packageRevision.Namespace, + Name: packageRevision.Name, + } + if err := r.client.Get(ctx, id, &packageRevisionResources); err != nil { + return nil, fmt.Errorf("error fetching PackageRevisionResources %v: %w", id, err) + } + + objects, err := parseObjects(ctx, &packageRevisionResources) + if err != nil { + return nil, err + } + blueprint := &blueprint{ + Objects: objects, + ID: id, + PackageName: packageRevision.Spec.PackageName, + } + blueprints = append(blueprints, blueprint) + } + return blueprints, nil +} + +func parseObjects(ctx context.Context, subject *api.PackageRevisionResources) ([]*unstructured.Unstructured, error) { + log := log.FromContext(ctx) + + // TODO: Does this function exist somewhere? + var objects []*unstructured.Unstructured + + for path, contents := range subject.Spec.Resources { + ext := filepath.Ext(path) + ext = strings.ToLower(ext) + + switch ext { + case ".yaml", ".yml": + // TODO: Use https://github.com/kubernetes-sigs/kustomize/blob/a5b61016bb40c30dd1b0a78290b28b2330a0383e/kyaml/kio/byteio_reader.go#L170 or similar? + for _, s := range strings.Split(contents, "\n---\n") { + if isWhitespace(s) { + continue + } + + o := &unstructured.Unstructured{} + if err := yaml.Unmarshal([]byte(s), o); err != nil { + return nil, fmt.Errorf("error parsing package as kubernetes object: %w", err) + } + + // TODO: sync with kpt logic; skip objects marked with the local-only annotation + objects = append(objects, o) + } + + default: + log.V(2).Info("skipping file with unhandled extension", "path", path) + } + } + + return objects, nil +} + +type bindingSlotRequirements struct { + MatchLabels map[string]string +} + +type blueprint struct { + ID types.NamespacedName + PackageName string + Objects []*unstructured.Unstructured +} + +func (r *KlippyReconciler) buildProposals(ctx context.Context, parent *api.PackageRevision, parentObjects []*unstructured.Unstructured, blueprint *blueprint) ([]*api.PackageRevision, error) { + log := log.FromContext(ctx) + + var proposals []*api.PackageRevision + + slots := make(map[schema.GroupKind]*bindingSlotRequirements) + for _, obj := range blueprint.Objects { + annotations := obj.GetAnnotations() + if annotations["config.kubernetes.io/local-config"] != "binding" { + continue + } + + gk := obj.GroupVersionKind().GroupKind() + + requirements := &bindingSlotRequirements{} + requirements.MatchLabels = obj.GetLabels() + + slots[gk] = requirements + } + + isMatch := false + bindings := make(map[schema.GroupKind]*unstructured.Unstructured) + { + for _, obj := range parentObjects { + annotations := obj.GetAnnotations() + if annotations["config.kubernetes.io/local-config"] == "binding" { + // Ignore binding objects in the "parent"; they don't satisfy bindings + continue + } + + gk := obj.GroupVersionKind().GroupKind() + + requirements, found := slots[gk] + if !found { + continue + } + + matchesLabels := true + for k, v := range requirements.MatchLabels { + if obj.GetLabels()[k] != v { + matchesLabels = false + } + } + if !matchesLabels { + continue + } + bindings[gk] = obj + } + + isMatch = len(bindings) == len(slots) + if !isMatch { + log.Info("package does not match", "parent", parent.Spec.PackageName, "child", blueprint.PackageName) + } + } + + if isMatch { + log.Info("matched package", "parent", parent.Spec.PackageName, "child", blueprint.PackageName) + // TODO: How to avoid collisions + name := "packagename-" + strings.ReplaceAll(blueprint.PackageName, "/", "-") + packageName := blueprint.PackageName + + clone := &api.PackageCloneTaskSpec{} + clone.Upstream = api.UpstreamPackage{ + UpstreamRef: &api.PackageRevisionRef{ + Name: blueprint.ID.Name, + }, + } + + parentName := parent.Spec.PackageName + objectName := packageName + "-" + parentName + "-" + name + + // TODO: How to sanitize? + // packageName can be something like dir/name, and that isn't allowed in names + objectName = strings.ReplaceAll(objectName, "/", "-") + + proposal := &api.PackageRevision{ + TypeMeta: metav1.TypeMeta{ + Kind: "PackageRevision", + APIVersion: api.SchemeGroupVersion.Identifier(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: parent.GetNamespace(), + Name: objectName, + }, + Spec: api.PackageRevisionSpec{ + PackageName: name, + Revision: "v1", + RepositoryName: parent.Spec.RepositoryName, + // TODO: Speculative: true, + Tasks: []api.Task{ + { + Type: api.TaskTypeClone, + Clone: clone, + }, + }, + }, + } + proposal.Labels = map[string]string{ + "alpha.kpt.dev/proposal": "", + } + proposal.Spec.Parent = &api.ParentReference{ + Name: parent.Name, + } + + for _, bindingObj := range bindings { + bindingCore := &unstructured.Unstructured{} + bindingCore.SetAPIVersion(bindingObj.GetAPIVersion()) + bindingCore.SetKind(bindingObj.GetKind()) + bindingCore.SetName(bindingObj.GetName()) + if bindingObj.GetNamespace() != "" { + bindingCore.SetNamespace(bindingObj.GetNamespace()) + } + + var bindingConfig runtime.RawExtension + bindingConfig.Object = bindingCore + + image := r.Options.BindFunction + + proposal.Spec.Tasks = append(proposal.Spec.Tasks, api.Task{ + Type: api.TaskTypeEval, + Eval: &api.FunctionEvalTaskSpec{ + Image: image, + Config: bindingConfig, + }, + }) + + } + + proposals = append(proposals, proposal) + } + + return proposals, nil +} + +func (r *KlippyReconciler) storeProposals(ctx context.Context, parent *api.PackageRevision, children []*api.PackageRevision) error { + log := log.FromContext(ctx) + + // TODO: Cache applyset + + // TODO: Should the fieldmanager just be klippy? These objects should be owned + patchOptions := metav1.PatchOptions{ + FieldManager: "klippy-" + parent.GetNamespace() + "-" + parent.GetName(), + } + + // We force to overcome errors like: Apply failed with 1 conflict: conflict with "kubectl-client-side-apply" using apps/v1: .spec.template.spec.containers[name="porch-server"].image + // TODO: How to handle this better + force := true + patchOptions.Force = &force + + applier, err := applyset.New(applyset.Options{ + RESTMapper: r.restMapper, + Client: r.dynamicClient, + PatchOptions: patchOptions, + }) + if err != nil { + return err + } + + // TODO: Set owner refs + + var applyableObjects []applyset.ApplyableObject + for _, o := range children { + applyableObjects = append(applyableObjects, o) + } + if err := applier.ReplaceAllObjects(applyableObjects); err != nil { + return err + } + + results, err := applier.ApplyOnce(ctx) + if err != nil { + return fmt.Errorf("failed to apply proposals: %w", err) + } + + // TODO: Signal that we don't care about health? + + log.Info("applied objects", "results", results) + + // TODO: Implement pruning + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *KlippyReconciler) SetupWithManager(mgr ctrl.Manager) error { + if err := api.AddToScheme(mgr.GetScheme()); err != nil { + return err + } + + r.client = mgr.GetClient() + + r.restMapper = mgr.GetRESTMapper() + + restConfig := mgr.GetConfig() + + client, err := dynamic.NewForConfig(restConfig) + if err != nil { + return fmt.Errorf("failed to create a new dynamic client: %w", err) + } + r.dynamicClient = client + + if err := ctrl.NewControllerManagedBy(mgr). + For(&api.PackageRevisionResources{}). + Complete(r); err != nil { + return err + } + + return nil +} diff --git a/porch/controllers/klippy/pkg/controllers/klippy/rbac.go b/porch/controllers/klippy/pkg/controllers/klippy/rbac.go new file mode 100644 index 0000000000..b411dd92f2 --- /dev/null +++ b/porch/controllers/klippy/pkg/controllers/klippy/rbac.go @@ -0,0 +1,21 @@ +// Copyright 2022 Google LLC +// +// 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 klippy + +//+kubebuilder:rbac:groups=porch.kpt.dev,resources=packagerevisionresources,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=porch.kpt.dev,resources=packagerevisionresources/status,verbs=get;update;patch + +//+kubebuilder:rbac:groups=porch.kpt.dev,resources=packagerevisions,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=porch.kpt.dev,resources=packagerevisions/status,verbs=get;update;patch diff --git a/porch/controllers/main.go b/porch/controllers/main.go index ac84080c17..903719898b 100644 --- a/porch/controllers/main.go +++ b/porch/controllers/main.go @@ -39,6 +39,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/GoogleContainerTools/kpt/porch/controllers/klippy/pkg/controllers/klippy" "github.com/GoogleContainerTools/kpt/porch/controllers/remoterootsyncsets/pkg/controllers/remoterootsyncset" "github.com/GoogleContainerTools/kpt/porch/controllers/rootsyncsets/pkg/controllers/rootsyncset" "github.com/GoogleContainerTools/kpt/porch/controllers/workloadidentitybindings/pkg/controllers/workloadidentitybinding" @@ -47,26 +48,28 @@ import ( ) var ( - reconcilers = map[string]newReconciler{ - "rootsyncsets": func() Reconciler { - return rootsyncset.NewRootSyncSetReconciler() - }, - "remoterootsyncsets": func() Reconciler { - return &remoterootsyncset.RemoteRootSyncSetReconciler{} - }, - "workloadidentitybindings": func() Reconciler { - return &workloadidentitybinding.WorkloadIdentityBindingReconciler{} - }, + reconcilers = map[string]Reconciler{ + "rootsyncsets": &rootsyncset.RootSyncSetReconciler{}, + "remoterootsyncsets": &remoterootsyncset.RemoteRootSyncSetReconciler{}, + "workloadidentitybindings": &workloadidentitybinding.WorkloadIdentityBindingReconciler{}, + "klippy": &klippy.KlippyReconciler{}, } ) +// Reconciler is the interface implemented by (our) reconcilers, which includes some configuration and initialization. type Reconciler interface { reconcile.Reconciler + + // InitDefaults populates default values into our options + InitDefaults() + + // BindFlags binds options to flags + BindFlags(prefix string, flags *flag.FlagSet) + + // SetupWithManager registers the reconciler to run under the specified manager SetupWithManager(ctrl.Manager) error } -type newReconciler func() Reconciler - // We include our lease / events permissions in the main RBAC role //+kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;watch;create;update;patch;delete @@ -86,6 +89,10 @@ func run(ctx context.Context) error { // var probeAddr string var enabledReconcilersString string + for _, reconciler := range reconcilers { + reconciler.InitDefaults() + } + klog.InitFlags(nil) // flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") @@ -95,8 +102,16 @@ func run(ctx context.Context) error { // "Enabling this will ensure there is only one active controller manager.") flag.StringVar(&enabledReconcilersString, "reconcilers", "*", "reconcilers that should be enabled; use * to mean 'enable all'") + for name, reconciler := range reconcilers { + reconciler.BindFlags(name+".", flag.CommandLine) + } + flag.Parse() + if len(flag.Args()) != 0 { + return fmt.Errorf("unexpected additional (non-flag) arguments: %v", flag.Args()) + } + scheme := runtime.NewScheme() if err := clientgoscheme.AddToScheme(scheme); err != nil { return fmt.Errorf("error initializing scheme: %w", err) @@ -121,12 +136,12 @@ func run(ctx context.Context) error { } enabledReconcilers := parseReconcilers(enabledReconcilersString) - for r, f := range reconcilers { - if !reconcilerIsEnabled(enabledReconcilers, r) { + for name, reconciler := range reconcilers { + if !reconcilerIsEnabled(enabledReconcilers, name) { continue } - if err = f().SetupWithManager(mgr); err != nil { - return fmt.Errorf("error creating %s reconciler: %w", r, err) + if err = reconciler.SetupWithManager(mgr); err != nil { + return fmt.Errorf("error creating %s reconciler: %w", name, err) } } diff --git a/porch/controllers/remoterootsyncsets/pkg/controllers/remoterootsyncset/remoterootsync_controller.go b/porch/controllers/remoterootsyncsets/pkg/controllers/remoterootsyncset/remoterootsync_controller.go index 414e13fa08..9a1179b721 100644 --- a/porch/controllers/remoterootsyncsets/pkg/controllers/remoterootsyncset/remoterootsync_controller.go +++ b/porch/controllers/remoterootsyncsets/pkg/controllers/remoterootsyncset/remoterootsync_controller.go @@ -16,6 +16,7 @@ package remoterootsyncset import ( "context" + "flag" "fmt" "os" "path" @@ -49,8 +50,19 @@ var ( RootSyncKind = "RootSync" ) +type Options struct { +} + +func (o *Options) InitDefaults() { +} + +func (o *Options) BindFlags(prefix string, flags *flag.FlagSet) { +} + // RemoteRootSyncSetReconciler reconciles RemoteRootSyncSet objects type RemoteRootSyncSetReconciler struct { + Options + client.Client ociStorage *kptoci.Storage diff --git a/porch/controllers/rootsyncsets/pkg/controllers/rootsyncset/controller.go b/porch/controllers/rootsyncsets/pkg/controllers/rootsyncset/controller.go index b57d9234a4..d69e0fbdd2 100644 --- a/porch/controllers/rootsyncsets/pkg/controllers/rootsyncset/controller.go +++ b/porch/controllers/rootsyncsets/pkg/controllers/rootsyncset/controller.go @@ -18,6 +18,7 @@ import ( "context" "encoding/base64" "encoding/json" + "flag" "fmt" "strings" "sync" @@ -69,15 +70,19 @@ var ( configControllerApiVersion = "configcontroller.cnrm.cloud.google.com/v1beta1" ) -func NewRootSyncSetReconciler() *RootSyncSetReconciler { - return &RootSyncSetReconciler{ - channel: make(chan event.GenericEvent, 10), - watchers: make(map[v1alpha1.ClusterRef]*watcher), - } +type Options struct { +} + +func (o *Options) InitDefaults() { +} + +func (o *Options) BindFlags(prefix string, flags *flag.FlagSet) { } // RootSyncSetReconciler reconciles a RootSyncSet object type RootSyncSetReconciler struct { + Options + client.Client WorkloadIdentityHelper @@ -392,6 +397,9 @@ func BuildObjectsToApply(rootsyncset *v1alpha1.RootSyncSet) (*unstructured.Unstr // SetupWithManager sets up the controller with the Manager. func (r *RootSyncSetReconciler) SetupWithManager(mgr ctrl.Manager) error { + r.channel = make(chan event.GenericEvent, 10) + r.watchers = make(map[v1alpha1.ClusterRef]*watcher) + if err := v1alpha1.AddToScheme(mgr.GetScheme()); err != nil { return err } diff --git a/porch/controllers/workloadidentitybindings/pkg/controllers/workloadidentitybinding/controller.go b/porch/controllers/workloadidentitybindings/pkg/controllers/workloadidentitybinding/controller.go index 962aa7e25c..3eee920ebe 100644 --- a/porch/controllers/workloadidentitybindings/pkg/controllers/workloadidentitybinding/controller.go +++ b/porch/controllers/workloadidentitybindings/pkg/controllers/workloadidentitybinding/controller.go @@ -17,6 +17,7 @@ package workloadidentitybinding import ( "context" "encoding/json" + "flag" "fmt" "github.com/GoogleContainerTools/kpt/porch/controllers/remoterootsyncsets/pkg/applyset" @@ -37,10 +38,20 @@ const ( finalizerName = "config.porch.kpt.dev/workloadidentitybindings" ) +type Options struct { +} + +func (o *Options) InitDefaults() { +} + +func (o *Options) BindFlags(prefix string, flags *flag.FlagSet) { +} + // WorkloadIdentityBindingReconciler reconciles WorkloadIdentityBinding objects type WorkloadIdentityBindingReconciler struct { + Options + client.Client - // Scheme *runtime.Scheme dynamicClient dynamic.Interface restMapper meta.RESTMapper