diff --git a/Makefile b/Makefile index 9b893be60e..7dab6b0197 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,9 @@ container: build ## Build the container @docker -v || (code=$$?; printf "\033[0;31mError\033[0m: there was a problem with Docker\n"; exit $$code) docker build --build-arg VERSION=$(VERSION) --build-arg GIT_COMMIT=$(GIT_COMMIT) --build-arg DATE=$(DATE) --target $(TARGET) -f build/Dockerfile -t $(PREFIX):$(TAG) . +contaner-provisioner: build-provisioner + docker build --build-arg VERSION=$(VERSION) --build-arg GIT_COMMIT=$(GIT_COMMIT) --build-arg DATE=$(DATE) -f build/ProvisionerDockerfile -t nginx-kubernetes-gateway-provisioner:$(TAG) . + .PHONY: build build: ## Build the binary ifeq (${TARGET},local) @@ -28,6 +31,9 @@ ifeq (${TARGET},local) CGO_ENABLED=0 GOOS=linux go build -trimpath -a -ldflags "-s -w -X main.version=${VERSION} -X main.commit=${GIT_COMMIT} -X main.date=${DATE}" -o $(OUT_DIR)/gateway github.com/nginxinc/nginx-kubernetes-gateway/cmd/gateway endif +build-provisioner: + CGO_ENABLED=0 GOOS=linux go build -trimpath -a -ldflags "-s -w -X main.version=${VERSION} -X main.commit=${GIT_COMMIT} -X main.date=${DATE}" -o $(OUT_DIR)/gateway-provisioner github.com/nginxinc/nginx-kubernetes-gateway/cmd/provisioner + .PHONY: generate generate: ## Run go generate go generate ./... diff --git a/build/ProvisionerDockerfile b/build/ProvisionerDockerfile new file mode 100644 index 0000000000..955cc712a1 --- /dev/null +++ b/build/ProvisionerDockerfile @@ -0,0 +1,5 @@ +# syntax=docker/dockerfile:1.4 +FROM scratch +USER 1001:1001 +COPY ./build/.out/gateway-provisioner /usr/bin/ +ENTRYPOINT [ "/usr/bin/gateway-provisioner" ] \ No newline at end of file diff --git a/cmd/gateway/main.go b/cmd/gateway/main.go index d4c666c073..90820b739c 100644 --- a/cmd/gateway/main.go +++ b/cmd/gateway/main.go @@ -3,8 +3,10 @@ package main import ( "fmt" "os" + "strings" flag "github.com/spf13/pflag" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/log/zap" "github.com/nginxinc/nginx-kubernetes-gateway/internal/config" @@ -33,16 +35,35 @@ var ( ) gatewayClassName = flag.String("gatewayclass", "", gatewayClassNameUsage) + + gateway = flag.String("gateway", "", "Gateway to watch") + + updateGatewayClassStatus = flag.Bool("update-gatewayclass-status", true, "Update GatewayClass status") ) func main() { flag.Parse() + var gwNsName types.NamespacedName + + if *gateway != "" { + parts := strings.Split(*gateway, "/") + if len(parts) != 2 { + panic("invalid gateway name") + } + gwNsName = types.NamespacedName{ + Namespace: parts[0], + Name: parts[1], + } + } + logger := zap.New() conf := config.Config{ - GatewayCtlrName: *gatewayCtlrName, - Logger: logger, - GatewayClassName: *gatewayClassName, + GatewayCtlrName: *gatewayCtlrName, + Logger: logger, + GatewayClassName: *gatewayClassName, + GatewayNsName: &gwNsName, + UpdateGatewayClassStatus: *updateGatewayClassStatus, } MustValidateArguments( diff --git a/cmd/provisioner/controllers.go b/cmd/provisioner/controllers.go new file mode 100644 index 0000000000..fda9407b14 --- /dev/null +++ b/cmd/provisioner/controllers.go @@ -0,0 +1,82 @@ +package main + +import ( + "context" + "fmt" + + ctlr "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + "github.com/nginxinc/nginx-kubernetes-gateway/internal/reconciler" +) + +type newReconcilerFunc func(cfg reconciler.Config) *reconciler.Implementation + +type controllerConfig struct { + namespacedNameFilter reconciler.NamespacedNameFilterFunc + k8sPredicate predicate.Predicate + newReconciler newReconcilerFunc +} + +type controllerOption func(*controllerConfig) + +func withNamespacedNameFilter(filter reconciler.NamespacedNameFilterFunc) controllerOption { + return func(cfg *controllerConfig) { + cfg.namespacedNameFilter = filter + } +} + +func withK8sPredicate(p predicate.Predicate) controllerOption { + return func(cfg *controllerConfig) { + cfg.k8sPredicate = p + } +} + +// withNewReconciler allows us to mock reconciler creation in the unit tests. +func withNewReconciler(newReconciler newReconcilerFunc) controllerOption { + return func(cfg *controllerConfig) { + cfg.newReconciler = newReconciler + } +} + +func defaultControllerConfig() controllerConfig { + return controllerConfig{ + newReconciler: reconciler.NewImplementation, + } +} + +func registerController( + ctx context.Context, + objectType client.Object, + mgr manager.Manager, + eventCh chan<- interface{}, + options ...controllerOption, +) error { + cfg := defaultControllerConfig() + + for _, opt := range options { + opt(&cfg) + } + + builder := ctlr.NewControllerManagedBy(mgr).For(objectType) + + if cfg.k8sPredicate != nil { + builder = builder.WithEventFilter(cfg.k8sPredicate) + } + + recCfg := reconciler.Config{ + Getter: mgr.GetClient(), + ObjectType: objectType, + EventCh: eventCh, + NamespacedNameFilter: cfg.namespacedNameFilter, + } + + err := builder.Complete(cfg.newReconciler(recCfg)) + if err != nil { + return fmt.Errorf("cannot build a controller for %T: %w", objectType, err) + } + + return nil +} diff --git a/cmd/provisioner/handler.go b/cmd/provisioner/handler.go new file mode 100644 index 0000000000..91143765a9 --- /dev/null +++ b/cmd/provisioner/handler.go @@ -0,0 +1,246 @@ +package main + +import ( + "bytes" + "context" + "text/template" + + "github.com/go-logr/logr" + v1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/yaml" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/nginxinc/nginx-kubernetes-gateway/internal/events" + "github.com/nginxinc/nginx-kubernetes-gateway/internal/state" + "github.com/nginxinc/nginx-kubernetes-gateway/internal/state/conditions" + "github.com/nginxinc/nginx-kubernetes-gateway/internal/status" +) + +type eventHandler struct { + gatewayClasses map[types.NamespacedName]*v1beta1.GatewayClass + gateways map[types.NamespacedName]*v1beta1.Gateway + + provisions map[types.NamespacedName]*v1.Deployment + + statusUpdater status.Updater + + gcName string + + k8sClient client.Client + + logger logr.Logger +} + +func newEventHandler( + gcName string, + statusUpdater status.Updater, + k8sClient client.Client, + logger logr.Logger, +) *eventHandler { + return &eventHandler{ + gatewayClasses: make(map[types.NamespacedName]*v1beta1.GatewayClass), + gateways: make(map[types.NamespacedName]*v1beta1.Gateway), + provisions: make(map[types.NamespacedName]*v1.Deployment), + statusUpdater: statusUpdater, + gcName: gcName, + k8sClient: k8sClient, + logger: logger, + } +} + +func (h *eventHandler) HandleEventBatch(ctx context.Context, batch events.EventBatch) { + // update caches + for _, event := range batch { + switch e := event.(type) { + case *events.UpsertEvent: + switch obj := e.Resource.(type) { + case *v1beta1.GatewayClass: + h.gatewayClasses[client.ObjectKeyFromObject(obj)] = obj + case *v1beta1.Gateway: + h.gateways[client.ObjectKeyFromObject(obj)] = obj + default: + panic("unknown object type") + } + case *events.DeleteEvent: + switch e.Type.(type) { + case *v1beta1.GatewayClass: + delete(h.gatewayClasses, e.NamespacedName) + case *v1beta1.Gateway: + delete(h.gateways, e.NamespacedName) + default: + panic("unknown object type") + } + default: + panic("unknown event type") + } + } + + // set Accepted True for our GatewayClass + + gc, exist := h.gatewayClasses[types.NamespacedName{Name: h.gcName}] + if !exist { + panic("gateway class not found") + } + + statuses := state.Statuses{ + GatewayClassStatus: &state.GatewayClassStatus{ + Conditions: conditions.NewDefaultGatewayClassConditions(), + ObservedGeneration: gc.Generation, + }, + } + + // process Gateways + + var toCreate, toRemove []types.NamespacedName + + for nsname, gw := range h.gateways { + if string(gw.Spec.GatewayClassName) != h.gcName { + continue + } + + _, exist := h.provisions[nsname] + if exist { + continue + } + + toCreate = append(toCreate, nsname) + } + + for nsname := range h.provisions { + _, exist := h.gateways[nsname] + if exist { + continue + } + + toRemove = append(toRemove, nsname) + } + + // create new deployments + + for _, nsname := range toCreate { + // create deployment + + deployment := prepareDeployment(nsname) + + err := h.k8sClient.Create(ctx, deployment) + if err != nil { + panic(err) + } + + h.provisions[nsname] = deployment + + h.logger.Info("created deployment", "deployment", client.ObjectKeyFromObject(deployment)) + } + + // remove unnecessary deployments + + for _, nsname := range toRemove { + deployment := h.provisions[nsname] + + err := h.k8sClient.Delete(ctx, deployment) + if err != nil { + panic(err) + } + + delete(h.provisions, nsname) + + h.logger.Info("deleted deployment", "deployment", client.ObjectKeyFromObject(deployment)) + } + + // update statuses + + h.statusUpdater.Update(ctx, statuses) +} + +const deploymentTemplate = `apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .DeploymentName }} + namespace: nginx-gateway +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .DeploymentName }} + template: + metadata: + labels: + app: {{ .DeploymentName }} + spec: + shareProcessNamespace: true + serviceAccountName: nginx-gateway + volumes: + - name: nginx-config + emptyDir: { } + - name: var-lib-nginx + emptyDir: { } + - name: njs-modules + configMap: + name: njs-modules + initContainers: + - image: busybox:1.34 # FIXME(pleshakov): use gateway container to init the Config with proper main config + name: nginx-config-initializer + command: [ 'sh', '-c', 'echo "load_module /usr/lib/nginx/modules/ngx_http_js_module.so; events {} pid /etc/nginx/nginx.pid; error_log stderr debug; http { include /etc/nginx/conf.d/*.conf; js_import /usr/lib/nginx/modules/njs/httpmatches.js; }" > /etc/nginx/nginx.conf && (rm -r /etc/nginx/conf.d /etc/nginx/secrets; mkdir /etc/nginx/conf.d /etc/nginx/secrets && chown 1001:0 /etc/nginx/conf.d /etc/nginx/secrets)' ] + volumeMounts: + - name: nginx-config + mountPath: /etc/nginx + containers: + - image: nginx-kubernetes-gateway:edge + imagePullPolicy: Never + name: nginx-gateway + volumeMounts: + - name: nginx-config + mountPath: /etc/nginx + securityContext: + runAsUser: 1001 + # FIXME(pleshakov) - figure out which capabilities are required + # dropping ALL and adding only CAP_KILL doesn't work + # Note: CAP_KILL is needed for sending HUP signal to NGINX main process + args: + - --gateway-ctlr-name=k8s-gateway.nginx.org/nginx-gateway-controller + - --gatewayclass=nginx + - --gateway={{ .GatewayArg }} + - --update-gatewayclass-status=false + - image: nginx:1.23 + imagePullPolicy: IfNotPresent + name: nginx + ports: + - name: http + containerPort: 80 + - name: https + containerPort: 443 + volumeMounts: + - name: nginx-config + mountPath: /etc/nginx + - name: var-lib-nginx + mountPath: /var/lib/nginx + - name: njs-modules + mountPath: /usr/lib/nginx/modules/njs +` + +func prepareDeployment(gwNsName types.NamespacedName) *v1.Deployment { + t := template.Must(template.New("deployment").Parse(deploymentTemplate)) + + var buf bytes.Buffer + err := t.Execute(&buf, + struct { + DeploymentName string + GatewayArg string + }{DeploymentName: gwNsName.Name, + GatewayArg: gwNsName.String(), + }, + ) + if err != nil { + panic(err) + } + + deployment := &v1.Deployment{} + err = yaml.Unmarshal(buf.Bytes(), deployment) + if err != nil { + panic(err) + } + + return deployment +} diff --git a/cmd/provisioner/main.go b/cmd/provisioner/main.go new file mode 100644 index 0000000000..36c2c5a82b --- /dev/null +++ b/cmd/provisioner/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "os" + + flag "github.com/spf13/pflag" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +const ( + domain = "k8s-gateway.nginx.org" + gatewayClassNameUsage = `The name of the GatewayClass resource. ` + + `Every NGINX Gateway must have a unique corresponding GatewayClass resource.` + gatewayCtrlNameUsageFmt = `The name of the Gateway controller. ` + + `The controller name must be of the form: DOMAIN/PATH. The controller's domain is '%s'` +) + +var ( + // Command-line flags + gatewayCtlrName = flag.String( + "gateway-ctlr-name", + "", + fmt.Sprintf(gatewayCtrlNameUsageFmt, domain), + ) + + gatewayClassName = flag.String("gatewayclass", "", gatewayClassNameUsage) +) + +func main() { + flag.Parse() + + logger := zap.New() + + logger.Info("Starting NGINX Kubernetes Gateway Deployer") + + err := startManager(logger, *gatewayClassName) + if err != nil { + logger.Error(err, "Failed to start control loop") + os.Exit(1) + } +} diff --git a/cmd/provisioner/manager.go b/cmd/provisioner/manager.go new file mode 100644 index 0000000000..b2261b9406 --- /dev/null +++ b/cmd/provisioner/manager.go @@ -0,0 +1,108 @@ +package main + +import ( + "fmt" + + "github.com/go-logr/logr" + v1 "k8s.io/api/apps/v1" + apiv1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + ctlr "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/nginxinc/nginx-kubernetes-gateway/internal/events" + "github.com/nginxinc/nginx-kubernetes-gateway/internal/manager/filter" + "github.com/nginxinc/nginx-kubernetes-gateway/internal/status" +) + +var scheme = runtime.NewScheme() + +func init() { + utilruntime.Must(gatewayv1beta1.AddToScheme(scheme)) + utilruntime.Must(apiv1.AddToScheme(scheme)) + utilruntime.Must(v1.AddToScheme(scheme)) +} + +func startManager(logger logr.Logger, gcName string) error { + clusterCfg := ctlr.GetConfigOrDie() + + options := manager.Options{ + Scheme: scheme, + Logger: logger, + } + + mgr, err := manager.New(clusterCfg, options) + if err != nil { + return fmt.Errorf("cannot build runtime manager: %w", err) + } + + controllerRegCfgs := []struct { + objectType client.Object + options []controllerOption + }{ + { + objectType: &gatewayv1beta1.GatewayClass{}, + options: []controllerOption{ + withNamespacedNameFilter(filter.CreateFilterForGatewayClass(gcName)), + }, + }, + { + objectType: &gatewayv1beta1.Gateway{}, + }, + } + + ctx := ctlr.SetupSignalHandler() + + eventCh := make(chan interface{}) + + for _, regCfg := range controllerRegCfgs { + err := registerController(ctx, regCfg.objectType, mgr, eventCh, regCfg.options...) + if err != nil { + return fmt.Errorf("cannot register controller for %T: %w", regCfg.objectType, err) + } + } + + firstBatchPreparer := events.NewFirstEventBatchPreparerImpl( + mgr.GetCache(), + []client.Object{ + &gatewayv1beta1.GatewayClass{ObjectMeta: metav1.ObjectMeta{Name: gcName}}, + }, + []client.ObjectList{ + &gatewayv1beta1.GatewayList{}, + }, + ) + + statusUpdater := status.NewUpdater( + status.UpdaterConfig{ + Client: mgr.GetClient(), + Clock: status.NewRealClock(), + Logger: logger.WithName("statusUpdater"), + GatewayClassName: gcName, + }, + ) + + handler := newEventHandler( + gcName, + statusUpdater, + mgr.GetClient(), + logger.WithName("eventHandler"), + ) + + eventLoop := events.NewEventLoop( + eventCh, + logger.WithName("eventLoop"), + handler, + firstBatchPreparer) + + err = mgr.Add(eventLoop) + if err != nil { + return fmt.Errorf("cannot register event loop: %w", err) + } + + logger.Info("Starting manager") + return mgr.Start(ctx) +} diff --git a/deploy/manifests/nginx-gateway.yaml b/deploy/manifests/nginx-gateway.yaml index 2a026ceb4f..a40762f33d 100644 --- a/deploy/manifests/nginx-gateway.yaml +++ b/deploy/manifests/nginx-gateway.yaml @@ -68,66 +68,66 @@ roleRef: kind: ClusterRole name: nginx-gateway apiGroup: rbac.authorization.k8s.io ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-gateway - namespace: nginx-gateway -spec: - replicas: 1 - selector: - matchLabels: - app: nginx-gateway - template: - metadata: - labels: - app: nginx-gateway - spec: - shareProcessNamespace: true - serviceAccountName: nginx-gateway - volumes: - - name: nginx-config - emptyDir: { } - - name: var-lib-nginx - emptyDir: { } - - name: njs-modules - configMap: - name: njs-modules - initContainers: - - image: busybox:1.34 # FIXME(pleshakov): use gateway container to init the Config with proper main config - name: nginx-config-initializer - command: [ 'sh', '-c', 'echo "load_module /usr/lib/nginx/modules/ngx_http_js_module.so; events {} pid /etc/nginx/nginx.pid; error_log stderr debug; http { include /etc/nginx/conf.d/*.conf; js_import /usr/lib/nginx/modules/njs/httpmatches.js; }" > /etc/nginx/nginx.conf && (rm -r /etc/nginx/conf.d /etc/nginx/secrets; mkdir /etc/nginx/conf.d /etc/nginx/secrets && chown 1001:0 /etc/nginx/conf.d /etc/nginx/secrets)' ] - volumeMounts: - - name: nginx-config - mountPath: /etc/nginx - containers: - - image: ghcr.io/nginxinc/nginx-kubernetes-gateway:edge - imagePullPolicy: Always - name: nginx-gateway - volumeMounts: - - name: nginx-config - mountPath: /etc/nginx - securityContext: - runAsUser: 1001 - # FIXME(pleshakov) - figure out which capabilities are required - # dropping ALL and adding only CAP_KILL doesn't work - # Note: CAP_KILL is needed for sending HUP signal to NGINX main process - args: - - --gateway-ctlr-name=k8s-gateway.nginx.org/nginx-gateway-controller - - --gatewayclass=nginx - - image: nginx:1.23 - imagePullPolicy: IfNotPresent - name: nginx - ports: - - name: http - containerPort: 80 - - name: https - containerPort: 443 - volumeMounts: - - name: nginx-config - mountPath: /etc/nginx - - name: var-lib-nginx - mountPath: /var/lib/nginx - - name: njs-modules - mountPath: /usr/lib/nginx/modules/njs +#--- +#apiVersion: apps/v1 +#kind: Deployment +#metadata: +# name: nginx-gateway +# namespace: nginx-gateway +#spec: +# replicas: 1 +# selector: +# matchLabels: +# app: nginx-gateway +# template: +# metadata: +# labels: +# app: nginx-gateway +# spec: +# shareProcessNamespace: true +# serviceAccountName: nginx-gateway +# volumes: +# - name: nginx-config +# emptyDir: { } +# - name: var-lib-nginx +# emptyDir: { } +# - name: njs-modules +# configMap: +# name: njs-modules +# initContainers: +# - image: busybox:1.34 # FIXME(pleshakov): use gateway container to init the Config with proper main config +# name: nginx-config-initializer +# command: [ 'sh', '-c', 'echo "load_module /usr/lib/nginx/modules/ngx_http_js_module.so; events {} pid /etc/nginx/nginx.pid; error_log stderr debug; http { include /etc/nginx/conf.d/*.conf; js_import /usr/lib/nginx/modules/njs/httpmatches.js; }" > /etc/nginx/nginx.conf && (rm -r /etc/nginx/conf.d /etc/nginx/secrets; mkdir /etc/nginx/conf.d /etc/nginx/secrets && chown 1001:0 /etc/nginx/conf.d /etc/nginx/secrets)' ] +# volumeMounts: +# - name: nginx-config +# mountPath: /etc/nginx +# containers: +# - image: ghcr.io/nginxinc/nginx-kubernetes-gateway:edge +# imagePullPolicy: Always +# name: nginx-gateway +# volumeMounts: +# - name: nginx-config +# mountPath: /etc/nginx +# securityContext: +# runAsUser: 1001 +# # FIXME(pleshakov) - figure out which capabilities are required +# # dropping ALL and adding only CAP_KILL doesn't work +# # Note: CAP_KILL is needed for sending HUP signal to NGINX main process +# args: +# - --gateway-ctlr-name=k8s-gateway.nginx.org/nginx-gateway-controller +# - --gatewayclass=nginx +# - image: nginx:1.23 +# imagePullPolicy: IfNotPresent +# name: nginx +# ports: +# - name: http +# containerPort: 80 +# - name: https +# containerPort: 443 +# volumeMounts: +# - name: nginx-config +# mountPath: /etc/nginx +# - name: var-lib-nginx +# mountPath: /var/lib/nginx +# - name: njs-modules +# mountPath: /usr/lib/nginx/modules/njs diff --git a/deploy/manifests/provisioner.yaml b/deploy/manifests/provisioner.yaml new file mode 100644 index 0000000000..cec02cce5b --- /dev/null +++ b/deploy/manifests/provisioner.yaml @@ -0,0 +1,69 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: nginx-gateway-provisioner + namespace: nginx-gateway +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: nginx-gateway-provisioner +rules: +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete +- apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses + - gateways + verbs: + - list + - watch +- apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses/status + verbs: + - update +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: nginx-gateway-provisioner +subjects: +- kind: ServiceAccount + name: nginx-gateway-provisioner + namespace: nginx-gateway +roleRef: + kind: ClusterRole + name: nginx-gateway-provisioner + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-gateway-provisioner + namespace: nginx-gateway +spec: + replicas: 1 + selector: + matchLabels: + app: nginx-gateway-provisioner + template: + metadata: + labels: + app: nginx-gateway-provisioner + spec: + serviceAccountName: nginx-gateway-provisioner + containers: + - image: nginx-kubernetes-gateway-provisioner:edge + imagePullPolicy: Never + name: nginx-gateway-provisioner + args: + - --gateway-ctlr-name=k8s-gateway.nginx.org/nginx-gateway-controller + - --gatewayclass=nginx \ No newline at end of file diff --git a/internal/config/config.go b/internal/config/config.go index fadddcc27f..d309a594c5 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,7 +10,8 @@ type Config struct { Logger logr.Logger // GatewayNsName is the namespaced name of a Gateway resource that the Gateway will use. // The Gateway will ignore all other Gateway resources. - GatewayNsName types.NamespacedName + GatewayNsName *types.NamespacedName // GatewayClassName is the name of the GatewayClass resource that the Gateway will use. - GatewayClassName string + GatewayClassName string + UpdateGatewayClassStatus bool } diff --git a/internal/manager/filter/gatewayclass.go b/internal/manager/filter/gatewayclass.go index 306877b4a1..8a96e9f565 100644 --- a/internal/manager/filter/gatewayclass.go +++ b/internal/manager/filter/gatewayclass.go @@ -21,3 +21,19 @@ func CreateFilterForGatewayClass(gcName string) reconciler.NamespacedNameFilterF return true, "" } } + +func CreateFilterForGateway(gwNsName *types.NamespacedName) reconciler.NamespacedNameFilterFunc { + if gwNsName == nil { + return nil + } + return func(nsname types.NamespacedName) (bool, string) { + if nsname != *gwNsName { + return false, fmt.Sprintf( + "Gateway is ignored because this controller only supports the Gateway %s/%s", + gwNsName.Namespace, + gwNsName.Name, + ) + } + return true, "" + } +} diff --git a/internal/manager/manager.go b/internal/manager/manager.go index 79cd8c11e2..fd6cae4f99 100644 --- a/internal/manager/manager.go +++ b/internal/manager/manager.go @@ -78,6 +78,9 @@ func Start(cfg config.Config) error { }, { objectType: &gatewayv1beta1.Gateway{}, + options: []controllerOption{ + withNamespacedNameFilter(filter.CreateFilterForGateway(cfg.GatewayNsName)), + }, }, { objectType: &gatewayv1beta1.HTTPRoute{}, @@ -139,8 +142,9 @@ func Start(cfg config.Config) error { // FIXME(pleshakov) Make sure each component: // (1) Has a dedicated named logger. // (2) Get it from the Manager (the WithName is done here for all components). - Logger: cfg.Logger.WithName("statusUpdater"), - Clock: status.NewRealClock(), + Logger: cfg.Logger.WithName("statusUpdater"), + Clock: status.NewRealClock(), + UpdateGatewayClass: cfg.UpdateGatewayClassStatus, }) eventHandler := events.NewEventHandlerImpl(events.EventHandlerConfig{ diff --git a/internal/status/updater.go b/internal/status/updater.go index 5abc9a339a..daf0eaff0c 100644 --- a/internal/status/updater.go +++ b/internal/status/updater.go @@ -31,7 +31,8 @@ type UpdaterConfig struct { // GatewayCtlrName is the name of the Gateway controller. GatewayCtlrName string // GatewayClassName is the name of the GatewayClass resource. - GatewayClassName string + GatewayClassName string + UpdateGatewayClass bool } // updaterImpl updates statuses of the Gateway API resources. @@ -86,7 +87,7 @@ func (upd *updaterImpl) Update(ctx context.Context, statuses state.Statuses) { // FIXME(pleshakov) Merge the new Conditions in the status with the existing Conditions // FIXME(pleshakov) Skip the status update (API call) if the status hasn't changed. - if statuses.GatewayClassStatus != nil { + if upd.cfg.UpdateGatewayClass && statuses.GatewayClassStatus != nil { upd.update( ctx, types.NamespacedName{Name: upd.cfg.GatewayClassName},