underpost 3.2.70 → 3.2.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.ci.yml +3 -3
- package/.github/workflows/release.cd.yml +1 -1
- package/CHANGELOG.md +1358 -1038
- package/CLI-HELP.md +39 -16
- package/README.md +3 -3
- package/bin/build.js +10 -4
- package/bin/deploy.js +18 -16
- package/docker-compose.yml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/playwright/deployment.yaml +1 -1
- package/manifests/mongodb/kustomization.yaml +4 -1
- package/manifests/mongodb/statefulset.yaml +4 -0
- package/manifests/mongodb/storage-class.yaml +9 -2
- package/package.json +20 -20
- package/scripts/nat-iptables.sh +10 -4
- package/scripts/test-monitor.sh +4 -3
- package/src/api/core/core.controller.js +4 -65
- package/src/api/core/core.router.js +8 -14
- package/src/api/default/default.controller.js +2 -70
- package/src/api/default/default.router.js +7 -17
- package/src/api/document/document.controller.js +5 -77
- package/src/api/document/document.router.js +9 -13
- package/src/api/file/file.controller.js +9 -53
- package/src/api/file/file.router.js +14 -6
- package/src/api/test/test.controller.js +8 -53
- package/src/api/test/test.router.js +1 -4
- package/src/cli/cluster.js +771 -66
- package/src/cli/db.js +6 -4
- package/src/cli/deploy.js +1715 -168
- package/src/cli/docker-compose.js +19 -24
- package/src/cli/fs.js +0 -1
- package/src/cli/image.js +40 -13
- package/src/cli/index.js +129 -35
- package/src/cli/ipfs.js +82 -11
- package/src/cli/monitor.js +1 -1
- package/src/cli/release.js +4 -0
- package/src/cli/repository.js +14 -3
- package/src/cli/run.js +2253 -439
- package/src/cli/secrets.js +969 -0
- package/src/cli/ssh.js +38 -39
- package/src/client/components/core/Modal.js +38 -4
- package/src/client-builder/client-build.js +94 -11
- package/src/client-builder/ssr.js +27 -73
- package/src/db/mongo/MongoBootstrap.js +295 -54
- package/src/db/mongo/MongooseDB.js +47 -32
- package/src/index.js +1 -1
- package/src/server/conf.js +1307 -6
- package/src/server/cri.js +70 -0
- package/src/server/downloader.js +3 -3
- package/src/server/middlewares.js +152 -0
- package/src/server/underpost-gateway.js +1073 -0
- package/src/server/underpost-ingress.js +364 -0
- package/test/cluster-instances.test.js +435 -0
- package/test/deploy-node-placement.test.js +45 -0
- package/test/instance-traffic-plan.test.js +710 -0
- package/test/sops-secret-store.test.js +612 -0
- package/test/underpost-gateway.test.js +469 -0
- package/test/underpost-ingress.test.js +253 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Underpost ingress: the shared entry point that lets the Contour and Gateway
|
|
3
|
+
* API data planes run side by side.
|
|
4
|
+
*
|
|
5
|
+
* Only one process can hold a node's 80/443, and a `hostPort` claim is stronger
|
|
6
|
+
* than a listener: the CNI hostport plugin DNATs every packet on those ports to
|
|
7
|
+
* the claiming pod before anything else sees them. So two ingress stacks on one
|
|
8
|
+
* node is not a configuration question — whichever claims first silently takes
|
|
9
|
+
* all traffic, and the other logs nothing at all.
|
|
10
|
+
*
|
|
11
|
+
* This module owns the port instead, and hands each connection to the data plane
|
|
12
|
+
* that actually describes its hostname. Both stacks are then reachable through
|
|
13
|
+
* the same address, and neither has to be uninstalled to try the other.
|
|
14
|
+
*
|
|
15
|
+
* Cleartext and TLS are split deliberately:
|
|
16
|
+
*
|
|
17
|
+
* - `:80` is proxied at L7, because a plaintext request carries a readable `Host`
|
|
18
|
+
* header and the backends answer it with their own redirect or content.
|
|
19
|
+
* - `:443` is forwarded at L4 by SNI, because terminating TLS here would mean
|
|
20
|
+
* holding every host's certificate and re-negotiating ALPN. Passing the bytes
|
|
21
|
+
* through keeps certificates, HTTP/2 and mTLS exactly where they already work.
|
|
22
|
+
*
|
|
23
|
+
* @module src/server/underpost-ingress.js
|
|
24
|
+
* @namespace UnderpostIngress
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @constant UNDERPOST_INGRESS
|
|
29
|
+
* @description Identity of the underpost ingress workload. One deployment fronts every
|
|
30
|
+
* data plane on the node, so these names are cluster-wide constants.
|
|
31
|
+
* @memberof UnderpostIngress
|
|
32
|
+
*/
|
|
33
|
+
const UNDERPOST_INGRESS = {
|
|
34
|
+
name: 'underpost-ingress',
|
|
35
|
+
configMapName: 'underpost-ingress-nginx',
|
|
36
|
+
image: 'nginx:alpine',
|
|
37
|
+
httpPort: 80,
|
|
38
|
+
httpsPort: 443,
|
|
39
|
+
healthPort: 8090,
|
|
40
|
+
healthPath: '/healthz',
|
|
41
|
+
// kube-dns's conventional ClusterIP; overridden from the live Service.
|
|
42
|
+
resolver: '10.96.0.10',
|
|
43
|
+
backends: {
|
|
44
|
+
contour: { http: 'envoy.projectcontour.svc.cluster.local:80', tls: 'envoy.projectcontour.svc.cluster.local:443' },
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @method gatewayBackendFactory
|
|
50
|
+
* @description Upstreams for the Gateway API data plane.
|
|
51
|
+
*
|
|
52
|
+
* Envoy Gateway provisions and names its own Service (`envoy-<class>-<hash>`),
|
|
53
|
+
* so unlike Contour's fixed name this one is discovered from the cluster and
|
|
54
|
+
* passed in.
|
|
55
|
+
* @param {string} service - Provisioned Service name.
|
|
56
|
+
* @param {string} [namespace] - Namespace holding it.
|
|
57
|
+
* @returns {{http: string, tls: string}} Qualified upstreams.
|
|
58
|
+
* @memberof UnderpostIngress
|
|
59
|
+
*/
|
|
60
|
+
const gatewayBackendFactory = (service, namespace = 'envoy-gateway-system') => ({
|
|
61
|
+
http: `${service}.${namespace}.svc.cluster.local:80`,
|
|
62
|
+
tls: `${service}.${namespace}.svc.cluster.local:443`,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @method underpostIngressHostMapFactory
|
|
67
|
+
* @description Decides which data plane each hostname is handed to.
|
|
68
|
+
*
|
|
69
|
+
* A hostname belongs to whichever stack has a route object describing it, which
|
|
70
|
+
* is the only fact that survives a switch between them: manifests for both kinds
|
|
71
|
+
* are always generated, so the objects that actually exist in the cluster are
|
|
72
|
+
* what says who serves what.
|
|
73
|
+
*
|
|
74
|
+
* A hostname described by both is a leftover from switching stacks, not a valid
|
|
75
|
+
* state — both would answer, and which one won would depend on ordering here. It
|
|
76
|
+
* resolves to `preferred` and is reported separately so the duplicate can be
|
|
77
|
+
* removed rather than silently tolerated.
|
|
78
|
+
* @param {Array<string>} [contourHosts] - Hostnames with an HTTPProxy.
|
|
79
|
+
* @param {Array<string>} [gatewayHosts] - Hostnames with an HTTPRoute.
|
|
80
|
+
* @param {string} [preferred] - Backend that wins a hostname described by both.
|
|
81
|
+
* @returns {{entries: Array<{host: string, backend: string}>, conflicts: Array<string>}} Routing table and duplicates.
|
|
82
|
+
* @memberof UnderpostIngress
|
|
83
|
+
*/
|
|
84
|
+
const underpostIngressHostMapFactory = ({ contourHosts = [], gatewayHosts = [], preferred = 'gateway' } = {}) => {
|
|
85
|
+
const contour = new Set(contourHosts.filter(Boolean));
|
|
86
|
+
const gateway = new Set(gatewayHosts.filter(Boolean));
|
|
87
|
+
const conflicts = [...contour].filter((host) => gateway.has(host)).sort();
|
|
88
|
+
const entries = [...new Set([...contour, ...gateway])].sort().map((host) => ({
|
|
89
|
+
host,
|
|
90
|
+
backend: contour.has(host) && gateway.has(host) ? preferred : contour.has(host) ? 'contour' : 'gateway',
|
|
91
|
+
}));
|
|
92
|
+
return { entries, conflicts };
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @method underpostIngressConfFactory
|
|
97
|
+
* @description Renders the underpost ingress Nginx configuration.
|
|
98
|
+
*
|
|
99
|
+
* `default` in both maps is what an unknown hostname reaches. It is the stack
|
|
100
|
+
* that owns most of the routing rather than a rejection, so a host whose route
|
|
101
|
+
* object has not been applied yet still reaches a data plane that can answer it
|
|
102
|
+
* — including with its own 404.
|
|
103
|
+
* @param {Array<object>} [entries] - Host table from {@link UnderpostIngress.underpostIngressHostMapFactory}.
|
|
104
|
+
* @param {object} backends - `{contour: {http, tls}, gateway: {http, tls}}`; a missing stack is omitted.
|
|
105
|
+
* @param {string} [defaultBackend] - Backend for an unmatched hostname.
|
|
106
|
+
* @param {string} [resolver] - Cluster DNS ClusterIP.
|
|
107
|
+
* @returns {string} nginx.conf contents.
|
|
108
|
+
* @memberof UnderpostIngress
|
|
109
|
+
*/
|
|
110
|
+
const underpostIngressConfFactory = ({
|
|
111
|
+
entries = [],
|
|
112
|
+
backends = {},
|
|
113
|
+
defaultBackend = 'gateway',
|
|
114
|
+
resolver = UNDERPOST_INGRESS.resolver,
|
|
115
|
+
} = {}) => {
|
|
116
|
+
const available = Object.keys(backends).filter((name) => backends[name]?.http && backends[name]?.tls);
|
|
117
|
+
// With one stack installed the map still renders, so the same workload serves
|
|
118
|
+
// the single-stack case without a second code path.
|
|
119
|
+
const fallback = available.includes(defaultBackend) ? defaultBackend : available[0];
|
|
120
|
+
if (!fallback) throw new Error('[underpost-ingress] No data plane backend to route to');
|
|
121
|
+
const routable = entries.filter((entry) => entry?.host && available.includes(entry.backend));
|
|
122
|
+
const mapEntries = (kind) =>
|
|
123
|
+
routable.map((entry) => ` ${entry.host} ${backends[entry.backend][kind]};`).join('\n') || '';
|
|
124
|
+
|
|
125
|
+
return `worker_processes auto;
|
|
126
|
+
error_log /dev/stderr warn;
|
|
127
|
+
pid /tmp/nginx.pid;
|
|
128
|
+
|
|
129
|
+
events {
|
|
130
|
+
worker_connections 4096;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
http {
|
|
134
|
+
server_tokens off;
|
|
135
|
+
log_format underpost_ingress '$remote_addr "$request" $status "$host" -> $underpost_ingress_http_upstream';
|
|
136
|
+
access_log /dev/stdout underpost_ingress;
|
|
137
|
+
|
|
138
|
+
map $http_upgrade $connection_upgrade {
|
|
139
|
+
default upgrade;
|
|
140
|
+
'' close;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Cluster DNS as a literal address — nginx cannot resolve its own resolver.
|
|
144
|
+
# Every upstream is passed through a variable, so without this nginx resolves
|
|
145
|
+
# each Service name once at start-up and keeps the address for the life of the
|
|
146
|
+
# process; a re-provisioned data plane gets a new ClusterIP.
|
|
147
|
+
resolver ${resolver} valid=10s ipv6=off;
|
|
148
|
+
|
|
149
|
+
map $host $underpost_ingress_http_upstream {
|
|
150
|
+
default ${backends[fallback].http};
|
|
151
|
+
${mapEntries('http')}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
server {
|
|
155
|
+
listen ${UNDERPOST_INGRESS.healthPort} default_server;
|
|
156
|
+
server_name _;
|
|
157
|
+
location = ${UNDERPOST_INGRESS.healthPath} {
|
|
158
|
+
access_log off;
|
|
159
|
+
add_header Content-Type text/plain;
|
|
160
|
+
return 200 'ok';
|
|
161
|
+
}
|
|
162
|
+
location / {
|
|
163
|
+
return 404;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
server {
|
|
168
|
+
listen ${UNDERPOST_INGRESS.httpPort} default_server;
|
|
169
|
+
server_name _;
|
|
170
|
+
|
|
171
|
+
location / {
|
|
172
|
+
proxy_http_version 1.1;
|
|
173
|
+
proxy_set_header Host $host;
|
|
174
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
175
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
176
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
177
|
+
# Websocket upgrades must survive this hop or a client that negotiated one
|
|
178
|
+
# at the edge is left holding a half-open connection.
|
|
179
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
180
|
+
proxy_set_header Connection $connection_upgrade;
|
|
181
|
+
proxy_pass http://$underpost_ingress_http_upstream;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
stream {
|
|
187
|
+
log_format underpost_ingress '$remote_addr -> "$ssl_preread_server_name" $upstream_addr $status';
|
|
188
|
+
access_log /dev/stdout underpost_ingress;
|
|
189
|
+
resolver ${resolver} valid=10s ipv6=off;
|
|
190
|
+
|
|
191
|
+
# SNI is read without terminating the connection, so each data plane keeps
|
|
192
|
+
# serving its own certificates and negotiating its own ALPN. Terminating here
|
|
193
|
+
# would mean holding every host's key and re-offering h2 on the way out.
|
|
194
|
+
map $ssl_preread_server_name $underpost_ingress_tls_upstream {
|
|
195
|
+
default ${backends[fallback].tls};
|
|
196
|
+
${mapEntries('tls')}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
server {
|
|
200
|
+
listen ${UNDERPOST_INGRESS.httpsPort};
|
|
201
|
+
ssl_preread on;
|
|
202
|
+
proxy_pass $underpost_ingress_tls_upstream;
|
|
203
|
+
}
|
|
204
|
+
${
|
|
205
|
+
backends.gateway
|
|
206
|
+
? `
|
|
207
|
+
# QUIC keeps working, but it cannot be routed by hostname: \`ssl_preread\` is
|
|
208
|
+
# TCP-only, and a QUIC Initial carries its SNI inside an encrypted frame. Only
|
|
209
|
+
# the Gateway API data plane serves HTTP/3 here — Contour advertises no
|
|
210
|
+
# Alt-Svc — so every datagram goes there. A client that tries QUIC against a
|
|
211
|
+
# Contour host gets no answer and falls back to TCP, exactly as it would have
|
|
212
|
+
# if nothing had advertised HTTP/3 at all.
|
|
213
|
+
#
|
|
214
|
+
# The constant is reached through a map because a literal \`proxy_pass\` in
|
|
215
|
+
# \`stream\` is resolved when the config is parsed, not through \`resolver\`:
|
|
216
|
+
# nginx refuses to start while the Service has no DNS record, and once it does
|
|
217
|
+
# start it keeps that address for the life of the process.
|
|
218
|
+
map $remote_addr $underpost_ingress_quic_upstream {
|
|
219
|
+
default ${backends.gateway.tls};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
server {
|
|
223
|
+
listen ${UNDERPOST_INGRESS.httpsPort} udp;
|
|
224
|
+
proxy_pass $underpost_ingress_quic_upstream;
|
|
225
|
+
proxy_timeout 30s;
|
|
226
|
+
}
|
|
227
|
+
`
|
|
228
|
+
: ''
|
|
229
|
+
}}
|
|
230
|
+
`;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* @method underpostIngressManifestsFactory
|
|
235
|
+
* @description Renders the underpost ingress workload.
|
|
236
|
+
*
|
|
237
|
+
* Host-networked, because it exists to be the thing that holds the node's 80/443
|
|
238
|
+
* — the ports both data planes have just been moved off. `hostPort` is not used:
|
|
239
|
+
* the claim it installs is what made the two stacks exclusive in the first place.
|
|
240
|
+
* @param {string} [namespace] - Namespace to deploy into.
|
|
241
|
+
* @param {string} conf - Rendered nginx.conf.
|
|
242
|
+
* @param {string} [nodeName] - Node to pin the underpost ingress to; empty schedules freely.
|
|
243
|
+
* @returns {string} Multi-document YAML.
|
|
244
|
+
* @memberof UnderpostIngress
|
|
245
|
+
*/
|
|
246
|
+
const underpostIngressManifestsFactory = ({ namespace = 'default', conf, nodeName = '' } = {}) => {
|
|
247
|
+
return `
|
|
248
|
+
---
|
|
249
|
+
apiVersion: v1
|
|
250
|
+
kind: ConfigMap
|
|
251
|
+
metadata:
|
|
252
|
+
name: ${UNDERPOST_INGRESS.configMapName}
|
|
253
|
+
namespace: ${namespace}
|
|
254
|
+
data:
|
|
255
|
+
nginx.conf: |
|
|
256
|
+
${`${conf}`
|
|
257
|
+
.replace(/\n$/, '')
|
|
258
|
+
.split('\n')
|
|
259
|
+
.map((line) => (line.length > 0 ? ` ${line}` : ''))
|
|
260
|
+
.join('\n')}
|
|
261
|
+
---
|
|
262
|
+
apiVersion: apps/v1
|
|
263
|
+
kind: Deployment
|
|
264
|
+
metadata:
|
|
265
|
+
name: ${UNDERPOST_INGRESS.name}
|
|
266
|
+
namespace: ${namespace}
|
|
267
|
+
labels:
|
|
268
|
+
app: ${UNDERPOST_INGRESS.name}
|
|
269
|
+
spec:
|
|
270
|
+
replicas: 1
|
|
271
|
+
# Recreate is retained for real pod-template upgrades because the node's ports
|
|
272
|
+
# cannot be held twice. Host-table changes do not alter this template: the
|
|
273
|
+
# installer validates and hot-reloads /tmp/nginx.conf in the existing pod.
|
|
274
|
+
strategy:
|
|
275
|
+
type: Recreate
|
|
276
|
+
selector:
|
|
277
|
+
matchLabels:
|
|
278
|
+
app: ${UNDERPOST_INGRESS.name}
|
|
279
|
+
template:
|
|
280
|
+
metadata:
|
|
281
|
+
labels:
|
|
282
|
+
app: ${UNDERPOST_INGRESS.name}
|
|
283
|
+
spec:
|
|
284
|
+
hostNetwork: true
|
|
285
|
+
dnsPolicy: ClusterFirstWithHostNet${
|
|
286
|
+
nodeName
|
|
287
|
+
? `
|
|
288
|
+
nodeSelector:
|
|
289
|
+
kubernetes.io/hostname: ${nodeName}`
|
|
290
|
+
: ''
|
|
291
|
+
}
|
|
292
|
+
containers:
|
|
293
|
+
- name: nginx
|
|
294
|
+
image: ${UNDERPOST_INGRESS.image}
|
|
295
|
+
# The latest tag otherwise implies Always. Production edge nodes are often
|
|
296
|
+
# deliberately unable to reach Docker Hub; once the audited image is
|
|
297
|
+
# present, a route-table refresh or pod restart must stay offline-safe.
|
|
298
|
+
imagePullPolicy: IfNotPresent
|
|
299
|
+
command:
|
|
300
|
+
- /bin/sh
|
|
301
|
+
- -c
|
|
302
|
+
- cp /etc/underpost-ingress/nginx.conf /tmp/nginx.conf && exec nginx -c /tmp/nginx.conf -g 'daemon off;'
|
|
303
|
+
# No \`ports:\` block, deliberately. Under \`hostNetwork: true\` Kubernetes
|
|
304
|
+
# sets \`hostPort\` to each \`containerPort\`, and the scheduler then
|
|
305
|
+
# refuses to place the pod unless those host ports are already free —
|
|
306
|
+
# which they are not, because the data planes only release them as this
|
|
307
|
+
# workload arrives. Declaring them turns the ports this exists to take
|
|
308
|
+
# into a precondition for being scheduled at all.
|
|
309
|
+
securityContext:
|
|
310
|
+
runAsNonRoot: false
|
|
311
|
+
runAsUser: 0
|
|
312
|
+
allowPrivilegeEscalation: false
|
|
313
|
+
capabilities:
|
|
314
|
+
drop:
|
|
315
|
+
- ALL
|
|
316
|
+
# The master starts as root and needs all four: NET_BIND_SERVICE to
|
|
317
|
+
# bind 80/443, CHOWN to take ownership of its temp paths, and
|
|
318
|
+
# SETUID/SETGID to drop each worker to the unprivileged \`nginx\`
|
|
319
|
+
# user. Dropping any one of them is a start-up failure rather than a
|
|
320
|
+
# degraded mode — and only the first is visible to \`nginx -t\`, since
|
|
321
|
+
# the other two are reached when workers spawn.
|
|
322
|
+
add:
|
|
323
|
+
- NET_BIND_SERVICE
|
|
324
|
+
- CHOWN
|
|
325
|
+
- SETUID
|
|
326
|
+
- SETGID
|
|
327
|
+
readinessProbe:
|
|
328
|
+
httpGet:
|
|
329
|
+
path: ${UNDERPOST_INGRESS.healthPath}
|
|
330
|
+
port: ${UNDERPOST_INGRESS.healthPort}
|
|
331
|
+
initialDelaySeconds: 2
|
|
332
|
+
periodSeconds: 5
|
|
333
|
+
livenessProbe:
|
|
334
|
+
httpGet:
|
|
335
|
+
path: ${UNDERPOST_INGRESS.healthPath}
|
|
336
|
+
port: ${UNDERPOST_INGRESS.healthPort}
|
|
337
|
+
initialDelaySeconds: 10
|
|
338
|
+
periodSeconds: 20
|
|
339
|
+
volumeMounts:
|
|
340
|
+
- name: nginx-conf
|
|
341
|
+
mountPath: /etc/underpost-ingress
|
|
342
|
+
readOnly: true
|
|
343
|
+
- name: cache
|
|
344
|
+
mountPath: /var/cache/nginx
|
|
345
|
+
- name: run
|
|
346
|
+
mountPath: /tmp
|
|
347
|
+
volumes:
|
|
348
|
+
- name: nginx-conf
|
|
349
|
+
configMap:
|
|
350
|
+
name: ${UNDERPOST_INGRESS.configMapName}
|
|
351
|
+
- name: cache
|
|
352
|
+
emptyDir: {}
|
|
353
|
+
- name: run
|
|
354
|
+
emptyDir: {}
|
|
355
|
+
`;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
export {
|
|
359
|
+
UNDERPOST_INGRESS,
|
|
360
|
+
gatewayBackendFactory,
|
|
361
|
+
underpostIngressConfFactory,
|
|
362
|
+
underpostIngressHostMapFactory,
|
|
363
|
+
underpostIngressManifestsFactory,
|
|
364
|
+
};
|