Skip to content

Commit 9ff3f4e

Browse files
committed
CP/DP Split: provision NGINX Plus (#3148)
Continuation from the previous commit to add support for provisioning with NGINX Plus. This adds support for duplicating any NGINX Plus or docker registry secrets into the Gateway namespace. Added unit tests.
1 parent c62b005 commit 9ff3f4e

File tree

26 files changed

+2644
-198
lines changed

26 files changed

+2644
-198
lines changed

charts/nginx-gateway-fabric/templates/deployment.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ spec:
3636
- --gatewayclass={{ .Values.nginxGateway.gatewayClassName }}
3737
- --config={{ include "nginx-gateway.config-name" . }}
3838
- --service={{ include "nginx-gateway.fullname" . }}
39+
{{- if .Values.nginx.imagePullSecret }}
40+
- --nginx-docker-secret={{ .Values.nginx.imagePullSecret }}
41+
{{- end }}
42+
{{- if .Values.nginx.imagePullSecrets }}
43+
{{- range .Values.nginx.imagePullSecrets }}
44+
- --nginx-docker-secret={{ . }}
45+
{{- end }}
46+
{{- end }}
3947
{{- if .Values.nginx.plus }}
4048
- --nginx-plus
4149
{{- if .Values.nginx.usage.secretName }}

cmd/gateway/commands.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func createStaticModeCommand() *cobra.Command {
6969
leaderElectionLockNameFlag = "leader-election-lock-name"
7070
productTelemetryDisableFlag = "product-telemetry-disable"
7171
gwAPIExperimentalFlag = "gateway-api-experimental-features"
72+
nginxDockerSecretFlag = "nginx-docker-secret" //nolint:gosec // not credentials
7273
usageReportSecretFlag = "usage-report-secret"
7374
usageReportEndpointFlag = "usage-report-endpoint"
7475
usageReportResolverFlag = "usage-report-resolver"
@@ -120,7 +121,10 @@ func createStaticModeCommand() *cobra.Command {
120121

121122
snippetsFilters bool
122123

123-
plus bool
124+
plus bool
125+
nginxDockerSecrets = stringSliceValidatingValue{
126+
validator: validateResourceName,
127+
}
124128
usageReportSkipVerify bool
125129
usageReportSecretName = stringValidatingValue{
126130
validator: validateResourceName,
@@ -249,7 +253,8 @@ func createStaticModeCommand() *cobra.Command {
249253
Names: flagKeys,
250254
Values: flagValues,
251255
},
252-
SnippetsFilters: snippetsFilters,
256+
SnippetsFilters: snippetsFilters,
257+
NginxDockerSecretNames: nginxDockerSecrets.values,
253258
}
254259

255260
if err := static.StartManager(conf); err != nil {
@@ -378,6 +383,13 @@ func createStaticModeCommand() *cobra.Command {
378383
"Requires the Gateway APIs installed from the experimental channel.",
379384
)
380385

386+
cmd.Flags().Var(
387+
&nginxDockerSecrets,
388+
nginxDockerSecretFlag,
389+
"The name of the NGINX docker registry Secret(s). Must exist in the same namespace "+
390+
"that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway).",
391+
)
392+
381393
cmd.Flags().Var(
382394
&usageReportSecretName,
383395
usageReportSecretFlag,

cmd/gateway/commands_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ func TestStaticModeCmdFlagValidation(t *testing.T) {
153153
"--leader-election-lock-name=my-lock",
154154
"--leader-election-disable=false",
155155
"--nginx-plus",
156+
"--nginx-docker-secret=secret1",
157+
"--nginx-docker-secret=secret2",
156158
"--usage-report-secret=my-secret",
157159
"--usage-report-endpoint=example.com",
158160
"--usage-report-resolver=resolver.com",
@@ -314,6 +316,31 @@ func TestStaticModeCmdFlagValidation(t *testing.T) {
314316
wantErr: true,
315317
expectedErrPrefix: `invalid argument "" for "--leader-election-disable" flag: strconv.ParseBool`,
316318
},
319+
{
320+
name: "nginx-docker-secret is set to empty string",
321+
args: []string{
322+
"--nginx-docker-secret=",
323+
},
324+
wantErr: true,
325+
expectedErrPrefix: `invalid argument "" for "--nginx-docker-secret" flag: must be set`,
326+
},
327+
{
328+
name: "nginx-docker-secret is invalid",
329+
args: []string{
330+
"--nginx-docker-secret=!@#$",
331+
},
332+
wantErr: true,
333+
expectedErrPrefix: `invalid argument "!@#$" for "--nginx-docker-secret" flag: invalid format: `,
334+
},
335+
{
336+
name: "one nginx-docker-secret is invalid",
337+
args: []string{
338+
"--nginx-docker-secret=valid",
339+
"--nginx-docker-secret=!@#$",
340+
},
341+
wantErr: true,
342+
expectedErrPrefix: `invalid argument "!@#$" for "--nginx-docker-secret" flag: invalid format: `,
343+
},
317344
{
318345
name: "usage-report-secret is set to empty string",
319346
args: []string{

cmd/gateway/validating_types.go

+50
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package main
22

33
import (
4+
"bytes"
5+
"encoding/csv"
46
"fmt"
57
"strconv"
8+
"strings"
69

710
"k8s.io/apimachinery/pkg/types"
811
)
@@ -30,6 +33,53 @@ func (v *stringValidatingValue) Type() string {
3033
return "string"
3134
}
3235

36+
// stringSliceValidatingValue is a string slice flag value with custom validation logic.
37+
// it implements the pflag.Value interface.
38+
type stringSliceValidatingValue struct {
39+
validator func(v string) error
40+
values []string
41+
changed bool
42+
}
43+
44+
func (v *stringSliceValidatingValue) String() string {
45+
b := &bytes.Buffer{}
46+
w := csv.NewWriter(b)
47+
err := w.Write(v.values)
48+
if err != nil {
49+
return ""
50+
}
51+
52+
w.Flush()
53+
str := strings.TrimSuffix(b.String(), "\n")
54+
return "[" + str + "]"
55+
}
56+
57+
func (v *stringSliceValidatingValue) Set(param string) error {
58+
if err := v.validator(param); err != nil {
59+
return err
60+
}
61+
62+
stringReader := strings.NewReader(param)
63+
csvReader := csv.NewReader(stringReader)
64+
str, err := csvReader.Read()
65+
if err != nil {
66+
return err
67+
}
68+
69+
if !v.changed {
70+
v.values = str
71+
} else {
72+
v.values = append(v.values, str...)
73+
}
74+
v.changed = true
75+
76+
return nil
77+
}
78+
79+
func (v *stringSliceValidatingValue) Type() string {
80+
return "stringSlice"
81+
}
82+
3383
type intValidatingValue struct {
3484
validator func(v int) error
3585
value int

deploy/experimental-nginx-plus/deploy.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ spec:
209209
- --gatewayclass=nginx
210210
- --config=nginx-gateway-config
211211
- --service=nginx-gateway
212+
- --nginx-docker-secret=nginx-plus-registry-secret
212213
- --nginx-plus
213214
- --usage-report-secret=nplus-license
214215
- --metrics-port=9113

deploy/nginx-plus/deploy.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ spec:
205205
- --gatewayclass=nginx
206206
- --config=nginx-gateway-config
207207
- --service=nginx-gateway
208+
- --nginx-docker-secret=nginx-plus-registry-secret
208209
- --nginx-plus
209210
- --usage-report-secret=nplus-license
210211
- --metrics-port=9113

deploy/snippets-filters-nginx-plus/deploy.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ spec:
207207
- --gatewayclass=nginx
208208
- --config=nginx-gateway-config
209209
- --service=nginx-gateway
210+
- --nginx-docker-secret=nginx-plus-registry-secret
210211
- --nginx-plus
211212
- --usage-report-secret=nplus-license
212213
- --metrics-port=9113

internal/framework/controller/resource.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ import "fmt"
44

55
// CreateNginxResourceName creates the base resource name for all nginx resources
66
// created by the control plane.
7-
func CreateNginxResourceName(gatewayName, gatewayClassName string) string {
8-
return fmt.Sprintf("%s-%s", gatewayName, gatewayClassName)
7+
func CreateNginxResourceName(prefix, suffix string) string {
8+
return fmt.Sprintf("%s-%s", prefix, suffix)
99
}

internal/mode/static/config/config.go

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ type Config struct {
3232
ConfigName string
3333
// GatewayClassName is the name of the GatewayClass resource that the Gateway will use.
3434
GatewayClassName string
35+
// NginxDockerSecretNames are the names of any Docker registry Secrets for the NGINX container.
36+
NginxDockerSecretNames []string
3537
// LeaderElection contains the configuration for leader election.
3638
LeaderElection LeaderElectionConfig
3739
// ProductTelemetryConfig contains the configuration for collecting product telemetry.

internal/mode/static/handler.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,19 @@ func (h *eventHandlerImpl) HandleEventBatch(ctx context.Context, logger logr.Log
169169
h.sendNginxConfig(ctx, logger, gr, changeType)
170170
}
171171

172+
// enable is called when the pod becomes leader to ensure the provisioner has
173+
// the latest configuration.
174+
func (h *eventHandlerImpl) enable(ctx context.Context) {
175+
h.sendNginxConfig(ctx, h.cfg.logger, h.cfg.processor.GetLatestGraph(), state.ClusterStateChange)
176+
}
177+
172178
func (h *eventHandlerImpl) sendNginxConfig(
173179
ctx context.Context,
174180
logger logr.Logger,
175181
gr *graph.Graph,
176182
changeType state.ChangeType,
177183
) {
178184
if gr == nil {
179-
logger.Info("Handling events didn't result into NGINX configuration changes")
180185
return
181186
}
182187

@@ -246,13 +251,13 @@ func (h *eventHandlerImpl) processStateAndBuildConfig(
246251

247252
h.setLatestConfiguration(&cfg)
248253

249-
deployment.Lock.Lock()
254+
deployment.FileLock.Lock()
250255
if h.cfg.plus {
251256
configApplied = h.cfg.nginxUpdater.UpdateUpstreamServers(deployment, cfg)
252257
} else {
253258
configApplied = h.updateNginxConf(deployment, cfg)
254259
}
255-
deployment.Lock.Unlock()
260+
deployment.FileLock.Unlock()
256261
case state.ClusterStateChange:
257262
h.version++
258263
cfg := dataplane.BuildConfiguration(ctx, gr, h.cfg.serviceResolver, h.version, h.cfg.plus)
@@ -264,9 +269,9 @@ func (h *eventHandlerImpl) processStateAndBuildConfig(
264269

265270
h.setLatestConfiguration(&cfg)
266271

267-
deployment.Lock.Lock()
272+
deployment.FileLock.Lock()
268273
configApplied = h.updateNginxConf(deployment, cfg)
269-
deployment.Lock.Unlock()
274+
deployment.FileLock.Unlock()
270275
}
271276

272277
return configApplied

internal/mode/static/manager.go

+10-7
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,15 @@ func StartManager(cfg config.Config) error {
201201
ctx,
202202
mgr,
203203
provisioner.Config{
204-
DeploymentStore: nginxUpdater.NginxDeployments,
205-
StatusQueue: statusQueue,
206-
Logger: cfg.Logger.WithName("provisioner"),
207-
EventRecorder: recorder,
208-
GatewayPodConfig: cfg.GatewayPodConfig,
209-
GCName: cfg.GatewayClassName,
210-
Plus: cfg.Plus,
204+
DeploymentStore: nginxUpdater.NginxDeployments,
205+
StatusQueue: statusQueue,
206+
Logger: cfg.Logger.WithName("provisioner"),
207+
EventRecorder: recorder,
208+
GatewayPodConfig: &cfg.GatewayPodConfig,
209+
GCName: cfg.GatewayClassName,
210+
Plus: cfg.Plus,
211+
NginxDockerSecretNames: cfg.NginxDockerSecretNames,
212+
PlusUsageConfig: &cfg.UsageReportConfig,
211213
},
212214
)
213215
if err != nil {
@@ -265,6 +267,7 @@ func StartManager(cfg config.Config) error {
265267
if err = mgr.Add(runnables.NewCallFunctionsAfterBecameLeader([]func(context.Context){
266268
groupStatusUpdater.Enable,
267269
nginxProvisioner.Enable,
270+
eventHandler.enable,
268271
})); err != nil {
269272
return fmt.Errorf("cannot register functions that get called after Pod becomes leader: %w", err)
270273
}

0 commit comments

Comments
 (0)