diff --git a/pkg/config/config.go b/pkg/config/config.go index 3e4a17c..edca53f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -64,6 +64,7 @@ const ( flagReconcileDefaultMaxConcurrency = "reconcile-default-max-concurrent-syncs" flagReconcileResourceMaxConcurrency = "reconcile-resource-max-concurrent-syncs" flagFeatureGates = "feature-gates" + flagReconcileResources = "reconcile-resources" envVarAWSRegion = "AWS_REGION" ) @@ -104,6 +105,7 @@ type Config struct { ReconcileResourceResyncSeconds []string ReconcileDefaultMaxConcurrency int ReconcileResourceMaxConcurrency []string + ReconcileResources string // TODO(a-hilaly): migrate to k8s.io/component-base and implement a proper parser for feature gates. FeatureGates featuregate.FeatureGates featureGatesRaw string @@ -250,6 +252,11 @@ func (cfg *Config) BindFlags() { "Valid keys are feature names and valid values are 'true' or 'false'."+ "Available features: "+strings.Join(featuregate.GetDefaultFeatureGates().GetFeatureNames(), ", "), ) + flag.StringVar( + &cfg.ReconcileResources, flagReconcileResources, + "", + "A comma-separated list of resource kinds to reconcile. If unspecified, all resources will be reconciled.", + ) } // SetupLogger initializes the logger used in the service controller @@ -389,6 +396,12 @@ func (cfg *Config) validateReconcileConfigResources(supportedGVKs []schema.Group return fmt.Errorf("invalid value for flag '%s': %v", flagReconcileResourceMaxConcurrency, err) } } + + // Also validate the resource filter settings + if err := cfg.validateReconcileResources(validResourceNames); err != nil { + return err + } + return nil } @@ -564,3 +577,56 @@ func parseFeatureGates(featureGatesRaw string) (map[string]bool, error) { return featureGatesMap, nil } + +// GetReconcileResources returns a slice of resource kinds that should be reconciled. +func (cfg *Config) GetReconcileResources() ([]string, error) { + return parseReconcileResourcesString(cfg.ReconcileResources) +} + +// parseReconcileResourcesString parses the reconcileResources flag and returns a slice +// of resource kinds to reconcile. +func parseReconcileResourcesString(resources string) ([]string, error) { + resources = strings.TrimSpace(resources) + if resources == "" { + return nil, nil + } + + visited := make(map[string]bool) + resourceKinds := []string{} + + for _, kind := range strings.Split(resources, ",") { + kind = strings.TrimSpace(kind) + if kind == "" { + return nil, fmt.Errorf("invalid resource kind: empty kind") + } + if _, ok := visited[kind]; ok { + return nil, fmt.Errorf("duplicate resource kind '%s'", kind) + } + visited[kind] = true + resourceKinds = append(resourceKinds, kind) + } + return resourceKinds, nil +} + +// validateReconcileResources validates that the specified resource kinds are supported by the controller. +func (cfg *Config) validateReconcileResources(validResourceNames []string) error { + resources, err := cfg.GetReconcileResources() + if err != nil { + return fmt.Errorf("invalid value for flag '%s': %v", flagReconcileResources, err) + } + if len(resources) == 0 { + return nil + } + + for _, resource := range resources { + if !ackutil.InStrings(resource, validResourceNames) { + return fmt.Errorf( + "invalid value for flag '%s': resource kind '%s' is not supported by this controller. Valid resource kinds are: %s", + flagReconcileResources, + resource, + strings.Join(validResourceNames, ", "), + ) + } + } + return nil +} diff --git a/pkg/runtime/service_controller.go b/pkg/runtime/service_controller.go index 9276ab5..7488c12 100644 --- a/pkg/runtime/service_controller.go +++ b/pkg/runtime/service_controller.go @@ -34,6 +34,7 @@ import ( ackmetrics "github.com/aws-controllers-k8s/runtime/pkg/metrics" ackrtcache "github.com/aws-controllers-k8s/runtime/pkg/runtime/cache" acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + ackutil "github.com/aws-controllers-k8s/runtime/pkg/util" ) const ( @@ -271,7 +272,33 @@ func (c *serviceController) BindControllerManager(mgr ctrlrt.Manager, cfg ackcfg c.fieldExportReconciler = rec } - for _, rmf := range c.rmFactories { + // Get the list of resources to reconcile from the config + reconcileResources, err := cfg.GetReconcileResources() + if err != nil { + return fmt.Errorf("error parsing reconcile resources: %v", err) + } + + if len(reconcileResources) == 0 { + c.log.Info("No resources? Did they all go on vacation? Defaulting to reconciling all resources.") + } + // Filter the resource manager factories + filteredRMFs := c.rmFactories + if len(reconcileResources) > 0 { + filteredRMFs = make(map[string]acktypes.AWSResourceManagerFactory) + for key, rmf := range c.rmFactories { + rd := rmf.ResourceDescriptor() + resourceKind := rd.GroupVersionKind().Kind + + if ackutil.InStrings(resourceKind, reconcileResources) { + filteredRMFs[key] = rmf + c.log.Info("including reconciler for resource kind", "kind", resourceKind) + } else { + c.log.Info("excluding reconciler for resource kind", "kind", resourceKind, "reason", "not in reconcile-resources flag") + } + } + } + + for _, rmf := range filteredRMFs { rec := NewReconciler(c, rmf, c.log, cfg, c.metrics, cache) if err := rec.BindControllerManager(mgr); err != nil { return err