underpost 3.2.80 → 3.3.0
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/ghpkg.ci.yml +7 -1
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +1 -9
- package/CHANGELOG.md +291 -1
- package/CLI-HELP.md +174 -23
- package/README.md +5 -2
- package/bin/build.js +7 -5
- package/bin/deploy.js +19 -17
- package/deploy/lib/logging.sh +96 -0
- package/deploy/pwa-microservices-template/deploy.sh +72 -0
- package/deploy/release/deploy.sh +62 -0
- package/docker-compose.yml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +52 -0
- 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 +19 -19
- package/scripts/audit-selinux.sh +64 -0
- package/scripts/coverall-test.sh +24 -0
- package/scripts/gpu-diag.sh +0 -0
- package/scripts/ip-info.sh +0 -0
- package/scripts/k3s-node-setup.sh +18 -15
- package/scripts/kubeadm-node-setup.sh +12 -23
- package/scripts/link-local-underpost-cli.sh +0 -0
- package/scripts/lxd-vm-setup.sh +0 -0
- package/scripts/maas-nat-firewalld.sh +0 -0
- package/scripts/nat-iptables.sh +12 -4
- package/scripts/rhel-grpc-setup.sh +0 -0
- package/scripts/rocky-kickstart.sh +25 -9
- package/scripts/test-monitor.sh +4 -3
- package/src/cli/baremetal.js +1 -2
- package/src/cli/cloud-init.js +1 -1
- package/src/cli/cluster.js +786 -96
- package/src/cli/db.js +11 -4
- package/src/cli/deploy.js +1698 -177
- package/src/cli/docker-compose.js +19 -178
- package/src/cli/env.js +1 -1
- package/src/cli/image.js +15 -7
- package/src/cli/index.js +245 -44
- package/src/cli/ipfs.js +82 -11
- package/src/cli/lxd.js +1 -1
- package/src/cli/monitor.js +2 -2
- package/src/cli/release.js +57 -22
- package/src/cli/repository.js +12 -10
- package/src/cli/run.js +2195 -427
- package/src/cli/secrets.js +969 -0
- package/src/cli/ssh.js +206 -105
- package/src/cli/system.js +26 -13
- package/src/cli/test.js +1 -1
- package/src/cli/vultr.js +583 -0
- package/src/cli/wireguard.js +2125 -0
- package/src/client-builder/client-build.js +102 -13
- package/src/client-builder/ssr.js +27 -73
- package/src/db/mongo/MongoBootstrap.js +295 -54
- package/src/db/mongo/MongooseDB.js +51 -32
- package/src/index.js +25 -1
- package/src/projects/underpost/catalog-underpost.js +4 -1
- package/src/server/backup.js +1 -1
- package/src/server/conf.js +1216 -168
- package/src/server/cri.js +70 -0
- package/src/server/cron.js +249 -51
- package/src/server/dns.js +100 -6
- package/src/server/environment.js +98 -0
- package/src/server/forward-proxy.js +549 -0
- package/src/server/middlewares.js +56 -1
- package/src/server/process.js +0 -1
- package/src/server/selinux.js +185 -0
- package/src/server/systemd.js +205 -0
- package/src/server/underpost-compression.js +186 -0
- package/src/server/underpost-gateway.js +1083 -0
- package/src/server/underpost-ingress.js +380 -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/selinux.test.js +71 -0
- package/test/sops-secret-store.test.js +612 -0
- package/test/underpost-gateway.test.js +510 -0
- package/test/underpost-ingress.test.js +305 -0
- package/test/wireguard-edge.test.js +1177 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
// Named import: js-yaml's ESM build exports no default.
|
|
6
|
+
import { loadAll } from 'js-yaml';
|
|
7
|
+
import {
|
|
8
|
+
UNDERPOST_INGRESS,
|
|
9
|
+
gatewayBackendFactory,
|
|
10
|
+
underpostIngressConfFactory,
|
|
11
|
+
underpostIngressHostMapFactory,
|
|
12
|
+
underpostIngressManifestsFactory,
|
|
13
|
+
} from '../src/server/underpost-ingress.js';
|
|
14
|
+
|
|
15
|
+
const CONTOUR = UNDERPOST_INGRESS.backends.contour;
|
|
16
|
+
const GATEWAY = gatewayBackendFactory('envoy-eg-d8c59e83');
|
|
17
|
+
const BOTH = { contour: CONTOUR, gateway: GATEWAY };
|
|
18
|
+
|
|
19
|
+
describe('shared ingress front', () => {
|
|
20
|
+
describe('underpostIngressHostMapFactory', () => {
|
|
21
|
+
it('routes each hostname to the stack that describes it', () => {
|
|
22
|
+
const { entries } = underpostIngressHostMapFactory({
|
|
23
|
+
contourHosts: ['legacy.test'],
|
|
24
|
+
gatewayHosts: ['app.test'],
|
|
25
|
+
});
|
|
26
|
+
expect(entries).to.deep.equal([
|
|
27
|
+
{ host: 'app.test', backend: 'gateway' },
|
|
28
|
+
{ host: 'legacy.test', backend: 'contour' },
|
|
29
|
+
]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Both objects existing for one hostname is what switching stacks leaves
|
|
33
|
+
// behind. Only one can win, so the choice is fixed rather than left to
|
|
34
|
+
// whichever happened to be listed first.
|
|
35
|
+
it('reports a hostname described by both, and resolves it to the preferred stack', () => {
|
|
36
|
+
const { entries, conflicts } = underpostIngressHostMapFactory({
|
|
37
|
+
contourHosts: ['shared.test'],
|
|
38
|
+
gatewayHosts: ['shared.test'],
|
|
39
|
+
});
|
|
40
|
+
expect(conflicts).to.deep.equal(['shared.test']);
|
|
41
|
+
expect(entries).to.deep.equal([{ host: 'shared.test', backend: 'gateway' }]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('honours an explicit preference for the conflicted hostname', () => {
|
|
45
|
+
const { entries } = underpostIngressHostMapFactory({
|
|
46
|
+
contourHosts: ['shared.test'],
|
|
47
|
+
gatewayHosts: ['shared.test'],
|
|
48
|
+
preferred: 'contour',
|
|
49
|
+
});
|
|
50
|
+
expect(entries).to.deep.equal([{ host: 'shared.test', backend: 'contour' }]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('deduplicates and drops empty hostnames', () => {
|
|
54
|
+
const { entries, conflicts } = underpostIngressHostMapFactory({
|
|
55
|
+
contourHosts: ['a.test', 'a.test', '', null],
|
|
56
|
+
gatewayHosts: [],
|
|
57
|
+
});
|
|
58
|
+
expect(entries).to.deep.equal([{ host: 'a.test', backend: 'contour' }]);
|
|
59
|
+
expect(conflicts).to.deep.equal([]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('is empty with nothing routed', () => {
|
|
63
|
+
expect(underpostIngressHostMapFactory()).to.deep.equal({ entries: [], conflicts: [] });
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('underpostIngressConfFactory', () => {
|
|
68
|
+
const conf = (input) => underpostIngressConfFactory({ backends: BOTH, ...input });
|
|
69
|
+
|
|
70
|
+
it('maps a hostname to its stack in both the HTTP and the TLS table', () => {
|
|
71
|
+
const out = conf({ entries: [{ host: 'legacy.test', backend: 'contour' }] });
|
|
72
|
+
expect(out).to.include(`legacy.test ${CONTOUR.http};`);
|
|
73
|
+
expect(out).to.include(`legacy.test ${CONTOUR.tls};`);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// The upstream must reach nginx as a variable. A literal upstream in a
|
|
77
|
+
// `stream` block is resolved when the config is parsed, so nginx refuses to
|
|
78
|
+
// start until the Service has DNS and then caches that address forever.
|
|
79
|
+
it('passes every upstream through a variable so the resolver applies', () => {
|
|
80
|
+
const out = conf({ entries: [] });
|
|
81
|
+
expect(out).to.include('proxy_pass http://$underpost_ingress_http_upstream;');
|
|
82
|
+
expect(out).to.include('proxy_pass $underpost_ingress_tls_upstream;');
|
|
83
|
+
expect(out).to.include('proxy_pass $underpost_ingress_quic_upstream;');
|
|
84
|
+
expect(out).to.not.match(/proxy_pass\s+[a-z0-9.-]+\.svc\.cluster\.local/);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('falls back to the Gateway API stack for an unknown hostname', () => {
|
|
88
|
+
const out = conf({ entries: [] });
|
|
89
|
+
expect(out).to.include(`default ${GATEWAY.http};`);
|
|
90
|
+
expect(out).to.include(`default ${GATEWAY.tls};`);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('falls back to the only stack installed', () => {
|
|
94
|
+
const out = underpostIngressConfFactory({ backends: { contour: CONTOUR }, defaultBackend: 'gateway' });
|
|
95
|
+
expect(out).to.include(`default ${CONTOUR.http};`);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// A host left over from the other stack would otherwise render a map entry
|
|
99
|
+
// pointing at an upstream that does not exist in this config.
|
|
100
|
+
it('drops a hostname whose stack is not installed', () => {
|
|
101
|
+
const out = underpostIngressConfFactory({
|
|
102
|
+
backends: { gateway: GATEWAY },
|
|
103
|
+
entries: [
|
|
104
|
+
{ host: 'orphan.test', backend: 'contour' },
|
|
105
|
+
{ host: 'app.test', backend: 'gateway' },
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
expect(out).to.not.include('orphan.test');
|
|
109
|
+
expect(out).to.include('app.test');
|
|
110
|
+
expect(out).to.not.include('projectcontour');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('serves QUIC only when the Gateway API data plane is present', () => {
|
|
114
|
+
expect(conf({ entries: [] })).to.include(`listen ${UNDERPOST_INGRESS.httpsPort} udp;`);
|
|
115
|
+
expect(underpostIngressConfFactory({ backends: { contour: CONTOUR } })).to.not.include('udp;');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('refuses to render with no backend at all', () => {
|
|
119
|
+
expect(() => underpostIngressConfFactory({ backends: {} })).to.throw(/No data plane backend/);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('carries the cluster DNS resolver into both the http and stream contexts', () => {
|
|
123
|
+
const out = conf({ entries: [], resolver: '10.43.0.10' });
|
|
124
|
+
expect(out.match(/resolver 10\.43\.0\.10 valid=10s ipv6=off;/g)).to.have.lengthOf(2);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('response compression', () => {
|
|
128
|
+
const halves = (input) => {
|
|
129
|
+
const out = conf({ entries: [], ...input });
|
|
130
|
+
const split = out.indexOf('\nstream {');
|
|
131
|
+
return { http: out.slice(0, split), stream: out.slice(split) };
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
it('compresses the cleartext hop it proxies at L7', () => {
|
|
135
|
+
const { http } = halves({ compression: { brotliModuleDir: '' } });
|
|
136
|
+
expect(http).to.include('gzip on;');
|
|
137
|
+
expect(http).to.include('gzip_proxied any;');
|
|
138
|
+
expect(http).to.include('gzip_vary on;');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// `:443` is forwarded by SNI without being decrypted, so there is no body
|
|
142
|
+
// in this context to encode — and no compression directive is even valid
|
|
143
|
+
// in `stream`.
|
|
144
|
+
it('leaves the L4 contexts alone', () => {
|
|
145
|
+
const { stream } = halves({ compression: { brotliModuleDir: '/usr/lib/nginx/modules' } });
|
|
146
|
+
expect(stream).to.not.include('gzip');
|
|
147
|
+
expect(stream).to.not.include('brotli');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// This workload proxies every byte and serves no documents, so there is
|
|
151
|
+
// never a pre-compressed sibling on disk to look for.
|
|
152
|
+
it('does not look for pre-compressed files it can never have', () => {
|
|
153
|
+
const { http } = halves({ compression: { brotliModuleDir: '/usr/lib/nginx/modules' } });
|
|
154
|
+
expect(http).to.not.include('_static');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// `brotli on;` in an image without the module is an unknown directive, and
|
|
158
|
+
// this workload holds the node's 80/443 — a start-up failure here is the
|
|
159
|
+
// whole edge, so brotli is rendered only where it is declared to exist.
|
|
160
|
+
it('renders brotli and its modules only when the image is declared to carry them', () => {
|
|
161
|
+
const stock = conf({ entries: [], compression: { brotliModuleDir: '' } });
|
|
162
|
+
expect(stock).to.not.include('brotli');
|
|
163
|
+
expect(stock).to.not.include('load_module');
|
|
164
|
+
const declared = conf({ entries: [], compression: { brotliModuleDir: '/usr/lib/nginx/modules' } });
|
|
165
|
+
expect(declared).to.include('load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so;');
|
|
166
|
+
expect(declared).to.include('brotli on;');
|
|
167
|
+
// Main context: nginx rejects `load_module` anywhere else.
|
|
168
|
+
expect(declared.indexOf('load_module')).to.be.lessThan(declared.indexOf('events {'));
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('renders no compression directive when compression is switched off', () => {
|
|
172
|
+
const out = conf({ entries: [], compression: { enabled: false, brotliModuleDir: '/usr/lib/nginx/modules' } });
|
|
173
|
+
expect(out).to.not.include('gzip');
|
|
174
|
+
expect(out).to.not.include('brotli');
|
|
175
|
+
expect(out).to.not.include('load_module');
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('underpostIngressManifestsFactory', () => {
|
|
181
|
+
// The regression that left the edge Pending: under `hostNetwork: true`
|
|
182
|
+
// Kubernetes sets `hostPort` to each declared `containerPort`, and the
|
|
183
|
+
// scheduler then refuses to place the pod until those host ports are free —
|
|
184
|
+
// which they only become once this pod replaces the data planes holding them.
|
|
185
|
+
it('takes the host network while declaring no port field at all', () => {
|
|
186
|
+
const out = underpostIngressManifestsFactory({ conf: 'worker_processes auto;' });
|
|
187
|
+
expect(out).to.include('hostNetwork: true');
|
|
188
|
+
expect(out).to.include('dnsPolicy: ClusterFirstWithHostNet');
|
|
189
|
+
const fields = out.split('\n').filter((line) => !line.trim().startsWith('#'));
|
|
190
|
+
expect(fields.filter((line) => /^\s*-?\s*(ports|containerPort|hostPort):/.test(line))).to.deep.equal([]);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('restarts from the cached Nginx image when an edge node cannot reach Docker Hub', () => {
|
|
194
|
+
expect(underpostIngressManifestsFactory({ conf: 'x' })).to.include('imagePullPolicy: IfNotPresent');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Host-map changes are reloaded in place. Keeping them out of the pod
|
|
198
|
+
// template avoids a Recreate gap on every HTTPRoute/HTTPProxy migration.
|
|
199
|
+
it('keeps the pod template stable and runs from a writable config copy', () => {
|
|
200
|
+
const deployment = (conf) =>
|
|
201
|
+
loadAll(underpostIngressManifestsFactory({ conf })).find((document) => document?.kind === 'Deployment');
|
|
202
|
+
expect(deployment('a').spec.template).to.deep.equal(deployment('b').spec.template);
|
|
203
|
+
const out = underpostIngressManifestsFactory({ conf: 'a' });
|
|
204
|
+
expect(out).to.include('cp /etc/underpost-ingress/nginx.conf /tmp/nginx.conf');
|
|
205
|
+
expect(out).to.not.include('subPath:');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Verified against the real nginx:alpine image: dropping any of these is a
|
|
209
|
+
// start-up failure, and only NET_BIND_SERVICE is visible to `nginx -t` —
|
|
210
|
+
// CHOWN fails on the temp paths, SETUID/SETGID when workers spawn.
|
|
211
|
+
it('grants every capability the nginx master needs to start', () => {
|
|
212
|
+
const out = underpostIngressManifestsFactory({ conf: 'x' });
|
|
213
|
+
for (const capability of ['NET_BIND_SERVICE', 'CHOWN', 'SETUID', 'SETGID'])
|
|
214
|
+
expect(out).to.include(`- ${capability}`);
|
|
215
|
+
expect(out).to.include('- ALL');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// The node's ports are a single resource: a rolling update would surge a
|
|
219
|
+
// second pod that cannot bind and stall the rollout indefinitely.
|
|
220
|
+
it('replaces rather than rolls, because the ports cannot be held twice', () => {
|
|
221
|
+
expect(underpostIngressManifestsFactory({ conf: 'x' })).to.include('type: Recreate');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('pins to a node only when one is given', () => {
|
|
225
|
+
expect(underpostIngressManifestsFactory({ conf: 'x', nodeName: 'node-1' })).to.include(
|
|
226
|
+
'kubernetes.io/hostname: node-1',
|
|
227
|
+
);
|
|
228
|
+
expect(underpostIngressManifestsFactory({ conf: 'x' })).to.not.include('nodeSelector');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// The manifest is assembled by string templating, and the config is embedded
|
|
232
|
+
// as an indented block scalar — so "it looks right" is not evidence. Parsing
|
|
233
|
+
// it back is what proves the pod spec is the one intended and that the
|
|
234
|
+
// embedded config survived the indentation intact.
|
|
235
|
+
it('parses as YAML, with the config round-tripping unchanged', () => {
|
|
236
|
+
const conf = underpostIngressConfFactory({
|
|
237
|
+
entries: [{ host: 'legacy.test', backend: 'contour' }],
|
|
238
|
+
backends: BOTH,
|
|
239
|
+
});
|
|
240
|
+
const documents = loadAll(underpostIngressManifestsFactory({ conf, nodeName: 'node-1' })).filter(Boolean);
|
|
241
|
+
expect(documents.map((document) => document.kind)).to.deep.equal(['ConfigMap', 'Deployment']);
|
|
242
|
+
|
|
243
|
+
const deployment = documents.find((document) => document.kind === 'Deployment');
|
|
244
|
+
const container = deployment.spec.template.spec.containers[0];
|
|
245
|
+
expect(deployment.spec.template.spec.hostNetwork).to.equal(true);
|
|
246
|
+
expect(container.ports).to.equal(undefined);
|
|
247
|
+
expect(container.securityContext.capabilities.add).to.have.members([
|
|
248
|
+
'NET_BIND_SERVICE',
|
|
249
|
+
'CHOWN',
|
|
250
|
+
'SETUID',
|
|
251
|
+
'SETGID',
|
|
252
|
+
]);
|
|
253
|
+
|
|
254
|
+
const configMap = documents.find((document) => document.kind === 'ConfigMap');
|
|
255
|
+
expect(configMap.data['nginx.conf'].trimEnd()).to.equal(conf.trimEnd());
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('validates and reloads a live config before persisting it', () => {
|
|
259
|
+
const clusterSource = fs.readFileSync(new URL('../src/cli/cluster.js', import.meta.url), 'utf8');
|
|
260
|
+
const install = clusterSource.slice(
|
|
261
|
+
clusterSource.indexOf(' installUnderpostIngress('),
|
|
262
|
+
clusterSource.indexOf(' pruneEndpointlessService(', clusterSource.indexOf(' installUnderpostIngress(')),
|
|
263
|
+
);
|
|
264
|
+
const testCandidate = install.indexOf("execIngress('nginx -t -c /tmp/nginx.candidate.conf'");
|
|
265
|
+
const reload = install.indexOf('nginx -s reload -c /tmp/nginx.conf', testCandidate);
|
|
266
|
+
const persist = install.indexOf("shellExec(`kubectl apply -f - -n ${namespace} <<'EOF'", reload);
|
|
267
|
+
expect(testCandidate).to.be.greaterThan(-1);
|
|
268
|
+
expect(reload).to.be.greaterThan(testCandidate);
|
|
269
|
+
expect(persist).to.be.greaterThan(reload);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('never inherits an application node flag during a route-table refresh', () => {
|
|
273
|
+
const clusterSource = fs.readFileSync(new URL('../src/cli/cluster.js', import.meta.url), 'utf8');
|
|
274
|
+
const install = clusterSource.slice(
|
|
275
|
+
clusterSource.indexOf(' installUnderpostIngress('),
|
|
276
|
+
clusterSource.indexOf(' pruneEndpointlessService(', clusterSource.indexOf(' installUnderpostIngress(')),
|
|
277
|
+
);
|
|
278
|
+
expect(install).to.include("const requestedNode = options.ingressNode || '';");
|
|
279
|
+
expect(install).to.not.include('const requestedNode = options.nodeName || options.node;');
|
|
280
|
+
expect(install).to.include('requestedNode ||\n liveNode ||');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('waits for an explicit ingress-node replacement before reporting refresh success', () => {
|
|
284
|
+
const clusterSource = fs.readFileSync(new URL('../src/cli/cluster.js', import.meta.url), 'utf8');
|
|
285
|
+
const install = clusterSource.slice(
|
|
286
|
+
clusterSource.indexOf(' installUnderpostIngress('),
|
|
287
|
+
clusterSource.indexOf(' pruneEndpointlessService(', clusterSource.indexOf(' installUnderpostIngress(')),
|
|
288
|
+
);
|
|
289
|
+
const changesNode = install.indexOf('const changesNode =');
|
|
290
|
+
const preflight = install.indexOf('pod/${preflightPod}', changesNode);
|
|
291
|
+
const apply = install.indexOf('underpostIngressManifestsFactory({');
|
|
292
|
+
const rollout = install.indexOf('if (!canHotReload || changesNode)', apply);
|
|
293
|
+
const available = install.indexOf('--for=condition=Available', rollout);
|
|
294
|
+
const nginxTest = install.indexOf("execIngress('nginx -t -c /tmp/nginx.conf'", available);
|
|
295
|
+
const success = install.indexOf("logger.info('Underpost ingress applied'", rollout);
|
|
296
|
+
expect(changesNode).to.be.greaterThan(-1);
|
|
297
|
+
expect(preflight).to.be.greaterThan(changesNode);
|
|
298
|
+
expect(apply).to.be.greaterThan(preflight);
|
|
299
|
+
expect(rollout).to.be.greaterThan(apply);
|
|
300
|
+
expect(available).to.be.greaterThan(rollout);
|
|
301
|
+
expect(nginxTest).to.be.greaterThan(available);
|
|
302
|
+
expect(success).to.be.greaterThan(nginxTest);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
});
|