@specific.dev/spectest 0.32.1 → 0.33.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/dist/daemon.js +97 -14
- package/dist/index.d.ts +20 -0
- package/dist/index.js +12 -4
- package/dist/ingress.d.ts +11 -0
- package/dist/ingress.js +22 -4
- package/package.json +1 -1
package/dist/daemon.js
CHANGED
|
@@ -511,6 +511,17 @@ async function recordAbsoluteVolumeDir(host) {
|
|
|
511
511
|
ABS_VOLUME_DIRS.add(host);
|
|
512
512
|
await fs.writeFile(VOLUME_DIRS_MANIFEST, [...ABS_VOLUME_DIRS].join("\n") + "\n");
|
|
513
513
|
}
|
|
514
|
+
/// True when `p` exists and is not a directory (a socket, a regular file,
|
|
515
|
+
/// a device). Used to tell "a volume dir we must create" from "a VM-host
|
|
516
|
+
/// object the service asked to bind-mount as-is".
|
|
517
|
+
async function isExistingNonDirectory(p) {
|
|
518
|
+
try {
|
|
519
|
+
return !(await fs.lstat(p)).isDirectory();
|
|
520
|
+
}
|
|
521
|
+
catch {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
514
525
|
async function ensureVolumes(svc) {
|
|
515
526
|
const flags = [];
|
|
516
527
|
if (!svc.volumes || svc.volumes.length === 0)
|
|
@@ -522,9 +533,18 @@ async function ensureVolumes(svc) {
|
|
|
522
533
|
throw new Error(`service "${svc.name}" volume for ${JSON.stringify(vol.target)} sets both \`name\` and \`source\``);
|
|
523
534
|
}
|
|
524
535
|
const host = resolveHostPath(svc.name, vol);
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
536
|
+
// An absolute `source` can name something that already exists and is
|
|
537
|
+
// NOT a directory — the canonical case is the VM's docker socket
|
|
538
|
+
// (`/var/run/docker.sock`), bind-mounted into a service that drives
|
|
539
|
+
// docker itself (LocalStack's Lambda executor, dind-style builders).
|
|
540
|
+
// `mkdir -p` fails on those with EEXIST. They are also VM-host
|
|
541
|
+
// infrastructure rather than environment state, so they are neither
|
|
542
|
+
// created here nor recorded for the delta-restore wipe.
|
|
543
|
+
if (!(await isExistingNonDirectory(host))) {
|
|
544
|
+
await fs.mkdir(host, { recursive: true });
|
|
545
|
+
if (vol.source?.startsWith("/") && !host.startsWith("/var/cache/spectest/")) {
|
|
546
|
+
await recordAbsoluteVolumeDir(host);
|
|
547
|
+
}
|
|
528
548
|
}
|
|
529
549
|
flags.push(`--volume=${host}:${vol.target}${vol.readOnly ? ":ro" : ""}`);
|
|
530
550
|
}
|
|
@@ -1575,17 +1595,22 @@ async function startIngress() {
|
|
|
1575
1595
|
routes.set(p.hostname, { kind: "proxy", service: p.service, port: p.port });
|
|
1576
1596
|
}
|
|
1577
1597
|
}
|
|
1578
|
-
// The :443 route table mirrors every
|
|
1598
|
+
// The :443 route table mirrors every handler whose hostname a cert
|
|
1599
|
+
// covers — exactly, or through a wildcard SAN, so `*.example.com` in a
|
|
1600
|
+
// service's `tls` also puts an unrelated exact `api.example.com` route on
|
|
1601
|
+
// HTTPS. Proxies are applied after fakes so a proxy wins a shared host.
|
|
1579
1602
|
if (HTTPS_CERT_BY_HOST.size > 0) {
|
|
1580
1603
|
const httpsRoutes = ensurePort(INGRESS_HTTPS_PORT);
|
|
1581
|
-
for (const
|
|
1582
|
-
for (const
|
|
1583
|
-
if (
|
|
1604
|
+
for (const fake of FAKES.values()) {
|
|
1605
|
+
for (const h of fake.hostnames) {
|
|
1606
|
+
if (certCovers(h))
|
|
1584
1607
|
httpsRoutes.set(h, { kind: "fake", fake });
|
|
1585
1608
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1609
|
+
}
|
|
1610
|
+
for (const p of LOWERED.proxies) {
|
|
1611
|
+
if (certCovers(p.hostname)) {
|
|
1612
|
+
httpsRoutes.set(p.hostname, { kind: "proxy", service: p.service, port: p.port });
|
|
1613
|
+
}
|
|
1589
1614
|
}
|
|
1590
1615
|
}
|
|
1591
1616
|
// ── HTTP listeners (one per non-443 port).
|
|
@@ -1649,12 +1674,26 @@ function rebindHttpsListener(Bun) {
|
|
|
1649
1674
|
// eslint-disable-next-line no-console
|
|
1650
1675
|
console.log(`[ingress] https :${INGRESS_HTTPS_PORT} for ${[...routes.keys()].join(", ")}`);
|
|
1651
1676
|
}
|
|
1677
|
+
/**
|
|
1678
|
+
* RFC 6125 wildcard match: `*.example.com` covers `api.example.com` but NOT
|
|
1679
|
+
* `a.b.example.com`. Deliberately stricter than {@link matchIngressRoute}'s
|
|
1680
|
+
* suffix matching — this one has to agree with the TLS *client*, and every
|
|
1681
|
+
* client stops at one label. Claiming a deeper name is covered would skip
|
|
1682
|
+
* minting the leaf it actually needs and hand it a cert it rejects.
|
|
1683
|
+
*/
|
|
1684
|
+
function wildcardCoversHost(pattern, hostname) {
|
|
1685
|
+
const suffix = wildcardSuffix(pattern); // "*.example.com" → ".example.com"
|
|
1686
|
+
if (!hostname.endsWith(suffix))
|
|
1687
|
+
return false;
|
|
1688
|
+
const label = hostname.slice(0, -suffix.length);
|
|
1689
|
+
return label.length > 0 && !label.includes(".");
|
|
1690
|
+
}
|
|
1652
1691
|
/** True if an exact or wildcard cert already covers `hostname` for SNI. */
|
|
1653
1692
|
function certCovers(hostname) {
|
|
1654
1693
|
if (HTTPS_CERT_BY_HOST.has(hostname))
|
|
1655
1694
|
return true;
|
|
1656
1695
|
for (const serverName of HTTPS_CERT_BY_HOST.keys()) {
|
|
1657
|
-
if (isWildcard(serverName) &&
|
|
1696
|
+
if (isWildcard(serverName) && wildcardCoversHost(serverName, hostname)) {
|
|
1658
1697
|
return true;
|
|
1659
1698
|
}
|
|
1660
1699
|
}
|
|
@@ -1706,8 +1745,16 @@ async function bindRuntimeTls(hostname, service, port) {
|
|
|
1706
1745
|
rebindHttpsListener(Bun);
|
|
1707
1746
|
}
|
|
1708
1747
|
// Resolve the hostname to the daemon gateway (where :443/:80 listen).
|
|
1748
|
+
// A wildcard can only live in the resolver's suffix table.
|
|
1709
1749
|
const gw = await bridgeGatewayIp();
|
|
1710
|
-
|
|
1750
|
+
if (isWildcard(host)) {
|
|
1751
|
+
const suffix = wildcardSuffix(host);
|
|
1752
|
+
REGISTRY.wildcards = REGISTRY.wildcards.filter((w) => w.suffix !== suffix);
|
|
1753
|
+
REGISTRY.wildcards.push({ suffix, ip: gw });
|
|
1754
|
+
}
|
|
1755
|
+
else {
|
|
1756
|
+
REGISTRY.hosts[host] = gw;
|
|
1757
|
+
}
|
|
1711
1758
|
await writeRegistry();
|
|
1712
1759
|
// eslint-disable-next-line no-console
|
|
1713
1760
|
console.log(`[ingress] runtime https ${host} -> ${service}:${port}`);
|
|
@@ -1722,7 +1769,14 @@ async function unbindRuntimeTls(hostname) {
|
|
|
1722
1769
|
const host = hostname.toLowerCase();
|
|
1723
1770
|
INGRESS_ROUTES_BY_PORT.get(INGRESS_HTTP_PORT)?.delete(host);
|
|
1724
1771
|
INGRESS_ROUTES_BY_PORT.get(INGRESS_HTTPS_PORT)?.delete(host);
|
|
1725
|
-
if (host
|
|
1772
|
+
if (isWildcard(host)) {
|
|
1773
|
+
const suffix = wildcardSuffix(host);
|
|
1774
|
+
const before = REGISTRY.wildcards.length;
|
|
1775
|
+
REGISTRY.wildcards = REGISTRY.wildcards.filter((w) => w.suffix !== suffix);
|
|
1776
|
+
if (REGISTRY.wildcards.length !== before)
|
|
1777
|
+
await writeRegistry();
|
|
1778
|
+
}
|
|
1779
|
+
else if (host in REGISTRY.hosts) {
|
|
1726
1780
|
delete REGISTRY.hosts[host];
|
|
1727
1781
|
await writeRegistry();
|
|
1728
1782
|
}
|
|
@@ -1838,6 +1892,35 @@ Bun, port, byHost, listenerLabel, tlsEntries) {
|
|
|
1838
1892
|
opts.tls = tlsEntries;
|
|
1839
1893
|
return Bun.serve(opts);
|
|
1840
1894
|
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Resolve a Host header to a route: an exact entry first, then the longest
|
|
1897
|
+
* matching `*.suffix` wildcard. Same precedence the resolver applies to DNS
|
|
1898
|
+
* (exact beats wildcard, longest suffix beats shorter), so a name that DNS
|
|
1899
|
+
* pointed at the ingress finds the route that claimed it.
|
|
1900
|
+
*
|
|
1901
|
+
* A wildcard *route* matches any depth of subdomain, while a wildcard
|
|
1902
|
+
* *cert* covers exactly one label ({@link wildcardCoversHost} — RFC 6125,
|
|
1903
|
+
* what TLS clients enforce). So `a.b.example.com` under a `*.example.com`
|
|
1904
|
+
* proxy reverse-proxies over `http://` but needs its own `tls` entry to
|
|
1905
|
+
* present a valid cert over `https://`.
|
|
1906
|
+
*/
|
|
1907
|
+
function matchIngressRoute(byHost, host) {
|
|
1908
|
+
const exact = byHost.get(host);
|
|
1909
|
+
if (exact)
|
|
1910
|
+
return exact;
|
|
1911
|
+
let best;
|
|
1912
|
+
let bestLen = -1;
|
|
1913
|
+
for (const [pattern, route] of byHost) {
|
|
1914
|
+
if (!isWildcard(pattern))
|
|
1915
|
+
continue;
|
|
1916
|
+
const suffix = wildcardSuffix(pattern);
|
|
1917
|
+
if (host.endsWith(suffix) && suffix.length > bestLen) {
|
|
1918
|
+
best = route;
|
|
1919
|
+
bestLen = suffix.length;
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
return best;
|
|
1923
|
+
}
|
|
1841
1924
|
/**
|
|
1842
1925
|
* Per-request dispatch shared by every ingress listener. Looks up the
|
|
1843
1926
|
* Route by Host header (port stripped) and either:
|
|
@@ -1852,7 +1935,7 @@ server, byHost, listenerLabel, proto) {
|
|
|
1852
1935
|
.toLowerCase()
|
|
1853
1936
|
.split(":")[0]
|
|
1854
1937
|
.trim();
|
|
1855
|
-
const route = byHost
|
|
1938
|
+
const route = matchIngressRoute(byHost, host);
|
|
1856
1939
|
if (!route) {
|
|
1857
1940
|
return new Response(`spectest-daemon: no ingress route bound to Host=${JSON.stringify(host)} on ${listenerLabel}\n`, { status: 404, headers: { "content-type": "text/plain" } });
|
|
1858
1941
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -72,6 +72,11 @@ export interface ServiceConfig {
|
|
|
72
72
|
* instead — those names terminate TLS in the daemon and reverse-proxy
|
|
73
73
|
* to the service's HTTP port.
|
|
74
74
|
*
|
|
75
|
+
* A `*.suffix` wildcard (e.g. `"*.example.com"`) points a whole domain
|
|
76
|
+
* at this service. Wildcards are answered by spectest-resolver only —
|
|
77
|
+
* they never land in a container's `/etc/hosts` — which peer containers
|
|
78
|
+
* still reach, since Docker forwards unknown names to that resolver.
|
|
79
|
+
*
|
|
75
80
|
* The `.internal` TLD is reserved: every service automatically
|
|
76
81
|
* answers to `<name>.internal` in addition to its bare `<name>`, and
|
|
77
82
|
* user-supplied hostnames may not end in `.internal`.
|
|
@@ -92,6 +97,15 @@ export interface ServiceConfig {
|
|
|
92
97
|
* tests and from peer services. Hostname rules match {@link hostnames}:
|
|
93
98
|
* multi-label, lowercase, no `.internal` suffix, no collision with
|
|
94
99
|
* services, other service TLS hostnames, or fakes.
|
|
100
|
+
*
|
|
101
|
+
* A `*.suffix` wildcard claims a whole domain with one entry — e.g.
|
|
102
|
+
* `{ hostname: "*.us-east-1.amazonaws.com", port: 4566 }` puts every
|
|
103
|
+
* AWS regional endpoint on one emulator container, so an unmodified SDK
|
|
104
|
+
* reaches it at its production URL. The leaf cert gets a wildcard SAN,
|
|
105
|
+
* which (as in every TLS client) covers exactly **one** label: declare
|
|
106
|
+
* a separate entry for anything deeper. An exact `tls` hostname always
|
|
107
|
+
* beats a wildcard, so a single endpoint can be split off to another
|
|
108
|
+
* service.
|
|
95
109
|
*/
|
|
96
110
|
tls?: readonly ServiceTls[];
|
|
97
111
|
/** Bind-mounted volumes for state that survives snapshot/fork. */
|
|
@@ -625,6 +639,12 @@ export interface VolumeMount {
|
|
|
625
639
|
/**
|
|
626
640
|
* Host path. Relative paths resolve under
|
|
627
641
|
* `.spectest/volumes/<service>/`. Defaults to a path derived from `target`.
|
|
642
|
+
*
|
|
643
|
+
* An absolute path that already exists and is not a directory is
|
|
644
|
+
* bind-mounted as-is — the way to hand a service the VM's docker socket
|
|
645
|
+
* (`source: "/var/run/docker.sock"`), which LocalStack's Lambda executor
|
|
646
|
+
* and dind-style builders need. Such a mount is VM-host infrastructure,
|
|
647
|
+
* so unlike a volume directory it is never created or wiped by spectest.
|
|
628
648
|
*/
|
|
629
649
|
source?: string;
|
|
630
650
|
/** Container path. */
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ import { describeUrlPattern, matchesUrl } from "./url-match.js";
|
|
|
31
31
|
// `tls` / `hostnames` fields and `defineFake(...)` are built on. See
|
|
32
32
|
// `ingress.ts`.
|
|
33
33
|
export { certificate, dnsName, proxy, provides, lowerIngress, isWildcard, SELF_SERVICE_TOKEN, } from "./ingress.js";
|
|
34
|
+
import { isWildcard as isWildcardHost } from "./ingress.js";
|
|
34
35
|
// ──────────────────────────────────────────────────────────────────────────
|
|
35
36
|
// Service groups — one services-map entry that expands to several services.
|
|
36
37
|
//
|
|
@@ -234,8 +235,8 @@ function validateEnvironmentConfig(config) {
|
|
|
234
235
|
}
|
|
235
236
|
for (const raw of svc.hostnames ?? []) {
|
|
236
237
|
const h = raw.toLowerCase();
|
|
237
|
-
if (!HOSTNAME_RE.test(h)) {
|
|
238
|
-
throw new Error(`service "${name}" declares invalid hostname ${JSON.stringify(raw)} — must be a multi-label DNS name (e.g. "api.stripe.com")`);
|
|
238
|
+
if (!HOSTNAME_RE.test(hostPatternBody(h))) {
|
|
239
|
+
throw new Error(`service "${name}" declares invalid hostname ${JSON.stringify(raw)} — must be a multi-label DNS name or a wildcard (e.g. "api.stripe.com", "*.stripe.com")`);
|
|
239
240
|
}
|
|
240
241
|
if (h === "internal" || h.endsWith(".internal")) {
|
|
241
242
|
throw new Error(`service "${name}" declares hostname ${JSON.stringify(raw)} — the ".internal" TLD is reserved; every service already answers to "<name>.internal" automatically`);
|
|
@@ -247,8 +248,8 @@ function validateEnvironmentConfig(config) {
|
|
|
247
248
|
throw new Error(`service "${name}" tls entry must be { hostname: string, port: number }; got ${JSON.stringify(entry)}`);
|
|
248
249
|
}
|
|
249
250
|
const h = entry.hostname.toLowerCase();
|
|
250
|
-
if (!HOSTNAME_RE.test(h)) {
|
|
251
|
-
throw new Error(`service "${name}" tls hostname ${JSON.stringify(entry.hostname)} is not a multi-label DNS name (e.g. "app.test")`);
|
|
251
|
+
if (!HOSTNAME_RE.test(hostPatternBody(h))) {
|
|
252
|
+
throw new Error(`service "${name}" tls hostname ${JSON.stringify(entry.hostname)} is not a multi-label DNS name or wildcard (e.g. "app.test", "*.us-east-1.amazonaws.com")`);
|
|
252
253
|
}
|
|
253
254
|
if (h === "internal" || h.endsWith(".internal")) {
|
|
254
255
|
throw new Error(`service "${name}" tls hostname ${JSON.stringify(entry.hostname)} ends in reserved ".internal" TLD`);
|
|
@@ -263,6 +264,13 @@ function validateEnvironmentConfig(config) {
|
|
|
263
264
|
// Multi-label hostname: at least one dot, each label 1–63 chars of
|
|
264
265
|
// [a-z0-9-], no leading/trailing hyphen.
|
|
265
266
|
const HOSTNAME_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/;
|
|
267
|
+
/** The part of a declared name that must be a valid hostname: a `*.suffix`
|
|
268
|
+
* wildcard is checked by its suffix, so `*.example.com` passes but `*.com`
|
|
269
|
+
* (single-label, too broad) and `*` do not. Keeps the `.internal` checks
|
|
270
|
+
* honest too — `*.foo.internal` reduces to `foo.internal`. */
|
|
271
|
+
function hostPatternBody(lowercased) {
|
|
272
|
+
return isWildcardHost(lowercased) ? lowercased.slice(2) : lowercased;
|
|
273
|
+
}
|
|
266
274
|
/**
|
|
267
275
|
* Define a fake. `defineFake({ ... })` is a thin wrapper that pins the
|
|
268
276
|
* generic `S` and `H` so `helpers`/`handler` see the inferred state type
|
package/dist/ingress.d.ts
CHANGED
|
@@ -38,6 +38,11 @@ export declare function isWildcard(hostname: string): boolean;
|
|
|
38
38
|
* `hostnames`. The daemon binds it on the HTTPS ingress (:443, SNI per
|
|
39
39
|
* hostname). Pairs with a `proxy(...)` (TLS-terminated reverse proxy) or a
|
|
40
40
|
* fake handler; on its own it just makes those hostnames serve HTTPS.
|
|
41
|
+
*
|
|
42
|
+
* A `*.suffix` wildcard is allowed and becomes a wildcard SAN, so one cert
|
|
43
|
+
* covers a whole domain (`*.us-east-1.amazonaws.com`). As in any TLS
|
|
44
|
+
* client, a wildcard SAN covers exactly **one** label — `*.example.com`
|
|
45
|
+
* matches `api.example.com`, not `a.b.example.com`.
|
|
41
46
|
*/
|
|
42
47
|
export declare function certificate(hostnames: string[]): CertificateDecl;
|
|
43
48
|
/**
|
|
@@ -54,6 +59,12 @@ export declare function dnsName(hostname: string, target: DnsTarget): DnsDecl;
|
|
|
54
59
|
* Implies the hostname resolves to the daemon ingress, so containers reach
|
|
55
60
|
* it without any extra `dnsName(...)`. Add a `certificate([hostname])` to
|
|
56
61
|
* serve it over HTTPS as well as HTTP.
|
|
62
|
+
*
|
|
63
|
+
* `hostname` may be a `*.suffix` wildcard, which sends every name under
|
|
64
|
+
* that domain to the same upstream — one route for a whole API surface
|
|
65
|
+
* (`*.us-east-1.amazonaws.com` → a LocalStack container). Exact routes
|
|
66
|
+
* always win over wildcards, and the longest wildcard suffix wins among
|
|
67
|
+
* wildcards.
|
|
57
68
|
*/
|
|
58
69
|
export declare function proxy(hostname: string, upstream: {
|
|
59
70
|
service: string;
|
package/dist/ingress.js
CHANGED
|
@@ -53,13 +53,18 @@ function assertNameOrWildcard(h, ctx) {
|
|
|
53
53
|
* `hostnames`. The daemon binds it on the HTTPS ingress (:443, SNI per
|
|
54
54
|
* hostname). Pairs with a `proxy(...)` (TLS-terminated reverse proxy) or a
|
|
55
55
|
* fake handler; on its own it just makes those hostnames serve HTTPS.
|
|
56
|
+
*
|
|
57
|
+
* A `*.suffix` wildcard is allowed and becomes a wildcard SAN, so one cert
|
|
58
|
+
* covers a whole domain (`*.us-east-1.amazonaws.com`). As in any TLS
|
|
59
|
+
* client, a wildcard SAN covers exactly **one** label — `*.example.com`
|
|
60
|
+
* matches `api.example.com`, not `a.b.example.com`.
|
|
56
61
|
*/
|
|
57
62
|
export function certificate(hostnames) {
|
|
58
63
|
if (!Array.isArray(hostnames) || hostnames.length === 0) {
|
|
59
64
|
throw new Error("certificate(): at least one hostname is required");
|
|
60
65
|
}
|
|
61
66
|
for (const h of hostnames)
|
|
62
|
-
|
|
67
|
+
assertNameOrWildcard(h, "certificate()");
|
|
63
68
|
return { kind: "certificate", hostnames: hostnames.map((h) => h.toLowerCase()) };
|
|
64
69
|
}
|
|
65
70
|
/**
|
|
@@ -82,9 +87,15 @@ export function dnsName(hostname, target) {
|
|
|
82
87
|
* Implies the hostname resolves to the daemon ingress, so containers reach
|
|
83
88
|
* it without any extra `dnsName(...)`. Add a `certificate([hostname])` to
|
|
84
89
|
* serve it over HTTPS as well as HTTP.
|
|
90
|
+
*
|
|
91
|
+
* `hostname` may be a `*.suffix` wildcard, which sends every name under
|
|
92
|
+
* that domain to the same upstream — one route for a whole API surface
|
|
93
|
+
* (`*.us-east-1.amazonaws.com` → a LocalStack container). Exact routes
|
|
94
|
+
* always win over wildcards, and the longest wildcard suffix wins among
|
|
95
|
+
* wildcards.
|
|
85
96
|
*/
|
|
86
97
|
export function proxy(hostname, upstream) {
|
|
87
|
-
|
|
98
|
+
assertNameOrWildcard(hostname, "proxy()");
|
|
88
99
|
if (!upstream || typeof upstream.service !== "string" || typeof upstream.port !== "number") {
|
|
89
100
|
throw new Error(`proxy(${JSON.stringify(hostname)}): upstream must be { service: string, port: number }`);
|
|
90
101
|
}
|
|
@@ -153,8 +164,15 @@ export function lowerIngress(project) {
|
|
|
153
164
|
case "proxy": {
|
|
154
165
|
const service = resolveSelf(decl.upstream.service, selfKey);
|
|
155
166
|
proxies.push({ hostname: decl.hostname, service, port: decl.upstream.port });
|
|
156
|
-
// A proxied hostname must route to the daemon.
|
|
157
|
-
|
|
167
|
+
// A proxied hostname must route to the daemon. A wildcard one can
|
|
168
|
+
// only be answered by the resolver (there is no `--add-host` for a
|
|
169
|
+
// pattern), so it goes in the wildcard table pointed at the ingress.
|
|
170
|
+
if (isWildcard(decl.hostname)) {
|
|
171
|
+
wildcards.push({ pattern: decl.hostname, target: { ingress: true } });
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
ingressSet.add(decl.hostname);
|
|
175
|
+
}
|
|
158
176
|
break;
|
|
159
177
|
}
|
|
160
178
|
case "dns": {
|