autocatalog-plugin-kong 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +20 -2
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +30 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-5OQJGQPX.js +453 -0
- package/dist/chunk-5OQJGQPX.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jag Reehal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
-
#
|
|
1
|
+
# autocatalog-plugin-kong
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Put what a Kong gateway exposes on the company site.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -D autocatalog-plugin-kong
|
|
7
|
+
ac from-kong kong.yml
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
It reads a decK declarative configuration, several files as one when decK merges
|
|
11
|
+
them, or the Kubernetes manifests of the Kong Ingress Controller (Gateway API
|
|
12
|
+
`HTTPRoute`s and Kong's plugin resources), and writes `autocatalog.gateway.json`. Commit it: the site joins each
|
|
13
|
+
route to the operations behind it, and says where the gateway and the contracts
|
|
14
|
+
disagree. See [Gateways](https://jagreehal.github.io/autocatalog/docs/gateways).
|
|
15
|
+
|
|
16
|
+
| Option | Does |
|
|
17
|
+
| -------------------------- | ------------------------------------------------------ |
|
|
18
|
+
| `--root <dir>` | the repository to write into |
|
|
19
|
+
| `--gateway <name>` | the gateway's name in the catalog, `kong` by default |
|
|
20
|
+
| `--service <kong=catalog>` | a Kong service whose catalog name differs, repeatable |
|
|
21
|
+
| `--check` | fail when the committed file does not match the config |
|
package/dist/bin.d.ts
ADDED
package/dist/bin.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
runFromKong
|
|
4
|
+
} from "./chunk-5OQJGQPX.js";
|
|
5
|
+
|
|
6
|
+
// src/bin.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
var program = new Command();
|
|
9
|
+
var collect = (value, previous) => [...previous, value];
|
|
10
|
+
program.name("autocatalog-from-kong").description("Write autocatalog.gateway.json from a Kong declarative configuration").argument(
|
|
11
|
+
"<config...>",
|
|
12
|
+
"decK files, or Kubernetes manifests: files or the folders that hold them"
|
|
13
|
+
).option("--gateway <name>", "the gateway's name in the catalog", "kong").option(
|
|
14
|
+
"--service <kong=catalog>",
|
|
15
|
+
"a Kong service whose catalog name differs, repeatable",
|
|
16
|
+
collect,
|
|
17
|
+
[]
|
|
18
|
+
).option("--check", "fail when the committed file does not match this configuration").option(
|
|
19
|
+
"--same-as <config...>",
|
|
20
|
+
"fail unless another configuration of this gateway lets through the same routes; writes nothing"
|
|
21
|
+
).option("--root <dir>", "the repository to write into", ".").action(
|
|
22
|
+
async (config, opts) => {
|
|
23
|
+
process.exit(await runFromKong({ ...opts, config }));
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
program.parseAsync(process.argv).catch((error) => {
|
|
27
|
+
console.error(error);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
|
30
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bin.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { runFromKong } from './index';\n\nconst program = new Command();\n\nconst collect = (value: string, previous: string[]) => [...previous, value];\n\nprogram\n .name('autocatalog-from-kong')\n .description('Write autocatalog.gateway.json from a Kong declarative configuration')\n .argument(\n '<config...>',\n 'decK files, or Kubernetes manifests: files or the folders that hold them',\n )\n .option('--gateway <name>', \"the gateway's name in the catalog\", 'kong')\n .option(\n '--service <kong=catalog>',\n 'a Kong service whose catalog name differs, repeatable',\n collect,\n [],\n )\n .option('--check', 'fail when the committed file does not match this configuration')\n .option(\n '--same-as <config...>',\n 'fail unless another configuration of this gateway lets through the same routes; writes nothing',\n )\n .option('--root <dir>', 'the repository to write into', '.')\n .action(\n async (\n config: string[],\n opts: {\n gateway: string;\n service: string[];\n check?: boolean;\n root: string;\n sameAs?: string[];\n },\n ) => {\n process.exit(await runFromKong({ ...opts, config }));\n },\n );\n\nprogram.parseAsync(process.argv).catch((error: Error) => {\n console.error(error);\n process.exit(1);\n});\n"],"mappings":";;;;;;AAAA,SAAS,eAAe;AAGxB,IAAM,UAAU,IAAI,QAAQ;AAE5B,IAAM,UAAU,CAAC,OAAe,aAAuB,CAAC,GAAG,UAAU,KAAK;AAE1E,QACG,KAAK,uBAAuB,EAC5B,YAAY,sEAAsE,EAClF;AAAA,EACC;AAAA,EACA;AACF,EACC,OAAO,oBAAoB,qCAAqC,MAAM,EACtE;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA,CAAC;AACH,EACC,OAAO,WAAW,gEAAgE,EAClF;AAAA,EACC;AAAA,EACA;AACF,EACC,OAAO,gBAAgB,gCAAgC,GAAG,EAC1D;AAAA,EACC,OACE,QACA,SAOG;AACH,YAAQ,KAAK,MAAM,YAAY,EAAE,GAAG,MAAM,OAAO,CAAC,CAAC;AAAA,EACrD;AACF;AAEF,QAAQ,WAAW,QAAQ,IAAI,EAAE,MAAM,CAAC,UAAiB;AACvD,UAAQ,MAAM,KAAK;AACnB,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { writeGatewayFile } from "autocatalog-core";
|
|
7
|
+
import kleur from "kleur";
|
|
8
|
+
import { parseAllDocuments, parse as parseYaml } from "yaml";
|
|
9
|
+
import { z as z2 } from "zod";
|
|
10
|
+
|
|
11
|
+
// src/authentication.ts
|
|
12
|
+
var AUTHENTICATION = /* @__PURE__ */ new Set([
|
|
13
|
+
"basic-auth",
|
|
14
|
+
"hmac-auth",
|
|
15
|
+
"jwt",
|
|
16
|
+
"key-auth",
|
|
17
|
+
"key-auth-enc",
|
|
18
|
+
"ldap-auth",
|
|
19
|
+
"ldap-auth-advanced",
|
|
20
|
+
"mtls-auth",
|
|
21
|
+
"oauth2",
|
|
22
|
+
"oauth2-introspection",
|
|
23
|
+
"openid-connect",
|
|
24
|
+
"vault-auth"
|
|
25
|
+
]);
|
|
26
|
+
var authenticates = (instance) => instance.enabled && !instance.config?.anonymous;
|
|
27
|
+
|
|
28
|
+
// src/compare.ts
|
|
29
|
+
function atoms(file) {
|
|
30
|
+
const out = /* @__PURE__ */ new Map();
|
|
31
|
+
for (const route2 of file.routes) {
|
|
32
|
+
const matchers = [
|
|
33
|
+
...(route2.paths ?? []).map((path2) => `${path2}*`),
|
|
34
|
+
...(route2.patterns ?? []).map((pattern) => `~${pattern}`)
|
|
35
|
+
];
|
|
36
|
+
const how = [
|
|
37
|
+
route2.auth?.length ? `auth ${route2.auth.join("+")}` : route2.auth ? "no auth" : "auth not said",
|
|
38
|
+
route2.stripPath ? "strips the path" : "",
|
|
39
|
+
route2.upstreamPath ? `to ${route2.upstreamPath}` : ""
|
|
40
|
+
].filter(Boolean).join(", ");
|
|
41
|
+
for (const method of route2.methods ?? ["ANY"])
|
|
42
|
+
for (const matcher of matchers) {
|
|
43
|
+
const key = `${route2.service} ${method} ${matcher}`;
|
|
44
|
+
out.set(key, (out.get(key) ?? /* @__PURE__ */ new Set()).add(how));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
var shown = (hows) => [...hows].sort().join(" | ");
|
|
50
|
+
var same = (a, b) => a.size === b.size && [...a].every((how) => b.has(how));
|
|
51
|
+
function compareGateways(first, second, names) {
|
|
52
|
+
const [a, b] = [atoms(first), atoms(second)];
|
|
53
|
+
const differences = [];
|
|
54
|
+
for (const [key, how] of a) {
|
|
55
|
+
const other = b.get(key);
|
|
56
|
+
if (other === void 0)
|
|
57
|
+
differences.push(`only ${names[0]} lets through ${key} (${shown(how)})`);
|
|
58
|
+
else if (!same(how, other))
|
|
59
|
+
differences.push(`${key}: ${shown(how)} in ${names[0]}, ${shown(other)} in ${names[1]}`);
|
|
60
|
+
}
|
|
61
|
+
for (const [key, how] of b)
|
|
62
|
+
if (!a.has(key)) differences.push(`only ${names[1]} lets through ${key} (${shown(how)})`);
|
|
63
|
+
return differences.sort();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/kubernetes.ts
|
|
67
|
+
import { z } from "zod";
|
|
68
|
+
var metadata = z.object({
|
|
69
|
+
name: z.string(),
|
|
70
|
+
namespace: z.string().optional(),
|
|
71
|
+
labels: z.record(z.string(), z.string()).default({}),
|
|
72
|
+
annotations: z.record(z.string(), z.string()).default({})
|
|
73
|
+
});
|
|
74
|
+
var manifest = z.looseObject({ apiVersion: z.string(), kind: z.string(), metadata });
|
|
75
|
+
var kongPlugin = z.looseObject({
|
|
76
|
+
plugin: z.string(),
|
|
77
|
+
disabled: z.boolean().default(false),
|
|
78
|
+
config: z.looseObject({ anonymous: z.unknown().optional() }).optional()
|
|
79
|
+
});
|
|
80
|
+
var match = z.object({
|
|
81
|
+
path: z.object({
|
|
82
|
+
type: z.enum(["Exact", "PathPrefix", "RegularExpression"]).default("PathPrefix"),
|
|
83
|
+
value: z.string().default("/")
|
|
84
|
+
}).default({ type: "PathPrefix", value: "/" }),
|
|
85
|
+
method: z.string().optional()
|
|
86
|
+
});
|
|
87
|
+
var httpRoute = z.object({
|
|
88
|
+
spec: z.object({
|
|
89
|
+
parentRefs: z.array(z.object({ name: z.string(), namespace: z.string().optional() })).default([]),
|
|
90
|
+
rules: z.array(
|
|
91
|
+
z.object({
|
|
92
|
+
matches: z.array(match).default([{ path: { type: "PathPrefix", value: "/" } }]),
|
|
93
|
+
filters: z.array(z.object({ type: z.string() })).default([]),
|
|
94
|
+
backendRefs: z.array(z.object({ name: z.string(), namespace: z.string().optional() })).default([])
|
|
95
|
+
})
|
|
96
|
+
).default([])
|
|
97
|
+
})
|
|
98
|
+
});
|
|
99
|
+
var gateway = z.object({
|
|
100
|
+
spec: z.object({
|
|
101
|
+
listeners: z.array(
|
|
102
|
+
z.object({
|
|
103
|
+
allowedRoutes: z.object({
|
|
104
|
+
namespaces: z.object({
|
|
105
|
+
from: z.enum(["All", "Same", "Selector"]).default("Same"),
|
|
106
|
+
selector: z.object({ matchLabels: z.record(z.string(), z.string()).default({}) }).optional()
|
|
107
|
+
}).default({ from: "Same" })
|
|
108
|
+
}).default({ namespaces: { from: "Same" } })
|
|
109
|
+
})
|
|
110
|
+
)
|
|
111
|
+
})
|
|
112
|
+
});
|
|
113
|
+
function isKubernetes(documents) {
|
|
114
|
+
return documents.some((document) => manifest.safeParse(document).success);
|
|
115
|
+
}
|
|
116
|
+
var escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
117
|
+
var METHODS = /* @__PURE__ */ new Set([
|
|
118
|
+
"GET",
|
|
119
|
+
"PUT",
|
|
120
|
+
"POST",
|
|
121
|
+
"DELETE",
|
|
122
|
+
"PATCH",
|
|
123
|
+
"OPTIONS",
|
|
124
|
+
"HEAD",
|
|
125
|
+
"TRACE"
|
|
126
|
+
]);
|
|
127
|
+
var isMethod = (value) => METHODS.has(value);
|
|
128
|
+
var pluginNames = (resource) => (resource.metadata.annotations["konghq.com/plugins"] ?? "").split(",").map((name) => name.trim()).filter(Boolean);
|
|
129
|
+
function readKubernetes(documents, services) {
|
|
130
|
+
const resources = documents.flatMap((document) => {
|
|
131
|
+
const parsed = manifest.safeParse(document);
|
|
132
|
+
return parsed.success ? [parsed.data] : [];
|
|
133
|
+
});
|
|
134
|
+
const warnings = [];
|
|
135
|
+
const of = (kind) => resources.filter((resource) => resource.kind === kind);
|
|
136
|
+
const namespaceOf = (resource) => resource.metadata.namespace ?? "default";
|
|
137
|
+
const instance = (name, namespace) => {
|
|
138
|
+
const found = of("KongPlugin").find((p) => p.metadata.name === name && namespaceOf(p) === namespace) ?? of("KongClusterPlugin").find((p) => p.metadata.name === name);
|
|
139
|
+
const parsed = found && kongPlugin.safeParse(found);
|
|
140
|
+
return parsed?.success ? parsed.data : void 0;
|
|
141
|
+
};
|
|
142
|
+
const authenticating = (names, namespace) => names.flatMap((name) => {
|
|
143
|
+
const plugin2 = instance(name, namespace);
|
|
144
|
+
return plugin2 && AUTHENTICATION.has(plugin2.plugin) ? [plugin2] : [];
|
|
145
|
+
});
|
|
146
|
+
const global = of("KongClusterPlugin").flatMap((resource) => {
|
|
147
|
+
const parsed = kongPlugin.safeParse(resource);
|
|
148
|
+
return parsed.success && resource.metadata.labels.global === "true" && AUTHENTICATION.has(parsed.data.plugin) ? [parsed.data] : [];
|
|
149
|
+
});
|
|
150
|
+
const namespaceLabels = new Map(
|
|
151
|
+
of("Namespace").map((resource) => [resource.metadata.name, resource.metadata.labels])
|
|
152
|
+
);
|
|
153
|
+
const gateways = new Map(
|
|
154
|
+
of("Gateway").flatMap((resource) => {
|
|
155
|
+
const parsed = gateway.safeParse(resource);
|
|
156
|
+
return parsed.success ? [[`${namespaceOf(resource)}/${resource.metadata.name}`, parsed.data]] : [];
|
|
157
|
+
})
|
|
158
|
+
);
|
|
159
|
+
const refused = (namespace, ref) => {
|
|
160
|
+
const gatewayNamespace = ref.namespace ?? namespace;
|
|
161
|
+
const parent = gateways.get(`${gatewayNamespace}/${ref.name}`);
|
|
162
|
+
if (!parent)
|
|
163
|
+
return gateways.size > 0 ? `its Gateway ${gatewayNamespace}/${ref.name} is not in these files` : void 0;
|
|
164
|
+
const allowed = parent.spec.listeners.some(({ allowedRoutes: { namespaces } }) => {
|
|
165
|
+
if (namespaces.from === "All") return true;
|
|
166
|
+
if (namespaces.from === "Same") return namespace === gatewayNamespace;
|
|
167
|
+
const labels = namespaceLabels.get(namespace);
|
|
168
|
+
return labels !== void 0 && Object.entries(namespaces.selector?.matchLabels ?? {}).every(
|
|
169
|
+
([key, value]) => labels[key] === value
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
return allowed ? void 0 : `namespace ${namespace} may not attach routes to the Gateway ${gatewayNamespace}/${ref.name}`;
|
|
173
|
+
};
|
|
174
|
+
const routes = [];
|
|
175
|
+
const taken = /* @__PURE__ */ new Set();
|
|
176
|
+
for (const resource of of("HTTPRoute")) {
|
|
177
|
+
const parsed = httpRoute.safeParse(resource);
|
|
178
|
+
const namespace = namespaceOf(resource);
|
|
179
|
+
const name = resource.metadata.name;
|
|
180
|
+
if (!parsed.success) {
|
|
181
|
+
warnings.push(
|
|
182
|
+
`HTTPRoute ${namespace}/${name}: not read (${parsed.error.issues[0]?.message ?? "invalid"})`
|
|
183
|
+
);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const reasons = parsed.data.spec.parentRefs.flatMap((ref) => refused(namespace, ref) ?? []);
|
|
187
|
+
if (parsed.data.spec.parentRefs.length > 0 && reasons.length === parsed.data.spec.parentRefs.length) {
|
|
188
|
+
warnings.push(
|
|
189
|
+
`HTTPRoute ${namespace}/${name} is not attached, so it was left out: ${reasons.join("; ")}`
|
|
190
|
+
);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
for (const [index, rule] of parsed.data.spec.rules.entries()) {
|
|
194
|
+
const [backend, ...more] = rule.backendRefs;
|
|
195
|
+
if (!backend) continue;
|
|
196
|
+
if (more.length)
|
|
197
|
+
warnings.push(
|
|
198
|
+
`HTTPRoute ${namespace}/${name}: only its first backend, ${backend.name}, was read`
|
|
199
|
+
);
|
|
200
|
+
const rewrites = rule.filters.filter((filter) => filter.type === "URLRewrite");
|
|
201
|
+
if (rewrites.length)
|
|
202
|
+
warnings.push(
|
|
203
|
+
`HTTPRoute ${namespace}/${name}: a URLRewrite filter changes the path, which the catalog does not follow`
|
|
204
|
+
);
|
|
205
|
+
const backendNamespace = backend.namespace ?? namespace;
|
|
206
|
+
const service2 = of("Service").find(
|
|
207
|
+
(s) => s.metadata.name === backend.name && namespaceOf(s) === backendNamespace
|
|
208
|
+
);
|
|
209
|
+
const levels = [
|
|
210
|
+
authenticating(pluginNames(resource), namespace),
|
|
211
|
+
service2 ? authenticating(pluginNames(service2), backendNamespace) : [],
|
|
212
|
+
global
|
|
213
|
+
];
|
|
214
|
+
const auth = [...AUTHENTICATION].filter((plugin2) => {
|
|
215
|
+
const chosen = levels.map((level) => level.find((p) => p.plugin === plugin2)).find(Boolean);
|
|
216
|
+
return chosen !== void 0 && authenticates({ enabled: !chosen.disabled, config: chosen.config });
|
|
217
|
+
});
|
|
218
|
+
const byMethod = /* @__PURE__ */ new Map();
|
|
219
|
+
for (const entry of rule.matches) {
|
|
220
|
+
const method = entry.method?.toUpperCase() ?? "*";
|
|
221
|
+
byMethod.set(method, [...byMethod.get(method) ?? [], entry]);
|
|
222
|
+
}
|
|
223
|
+
for (const [method, entries] of byMethod) {
|
|
224
|
+
const paths = entries.flatMap(
|
|
225
|
+
({ path: path2 }) => path2.type === "PathPrefix" ? [path2.value] : []
|
|
226
|
+
);
|
|
227
|
+
const patterns = entries.flatMap(
|
|
228
|
+
({ path: path2 }) => path2.type === "Exact" ? [`${escape(path2.value)}$`] : path2.type === "RegularExpression" ? [path2.value.endsWith("$") ? path2.value : `${path2.value}$`] : []
|
|
229
|
+
);
|
|
230
|
+
const base = [
|
|
231
|
+
name,
|
|
232
|
+
parsed.data.spec.rules.length > 1 ? String(index + 1) : "",
|
|
233
|
+
byMethod.size > 1 ? method.toLowerCase() : ""
|
|
234
|
+
].filter(Boolean).join("-");
|
|
235
|
+
const routeName = taken.has(base) ? `${namespace}.${base}` : base;
|
|
236
|
+
taken.add(routeName);
|
|
237
|
+
routes.push({
|
|
238
|
+
name: routeName,
|
|
239
|
+
service: services[backend.name] ?? backend.name,
|
|
240
|
+
...paths.length > 0 && { paths },
|
|
241
|
+
...patterns.length > 0 && { patterns },
|
|
242
|
+
...isMethod(method) && { methods: [method] },
|
|
243
|
+
stripPath: resource.metadata.annotations["konghq.com/strip-path"] === "true",
|
|
244
|
+
auth
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return { routes, warnings };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/index.ts
|
|
253
|
+
var reference = z2.union([z2.string(), z2.object({ name: z2.string() }).transform(({ name }) => name)]).optional();
|
|
254
|
+
var plugin = z2.object({
|
|
255
|
+
name: z2.string(),
|
|
256
|
+
enabled: z2.boolean().default(true),
|
|
257
|
+
service: reference,
|
|
258
|
+
route: reference,
|
|
259
|
+
consumer: reference,
|
|
260
|
+
/** Only `anonymous` is read: set, the plugin lets a request through without credentials. */
|
|
261
|
+
config: z2.looseObject({ anonymous: z2.unknown().optional() }).optional()
|
|
262
|
+
});
|
|
263
|
+
var route = z2.object({
|
|
264
|
+
name: z2.string().optional(),
|
|
265
|
+
paths: z2.array(z2.string()).optional(),
|
|
266
|
+
methods: z2.array(z2.string()).optional(),
|
|
267
|
+
strip_path: z2.boolean().default(true),
|
|
268
|
+
service: reference,
|
|
269
|
+
plugins: z2.array(plugin).default([])
|
|
270
|
+
});
|
|
271
|
+
var service = z2.object({
|
|
272
|
+
name: z2.string(),
|
|
273
|
+
url: z2.string().optional(),
|
|
274
|
+
path: z2.string().optional(),
|
|
275
|
+
routes: z2.array(route).default([]),
|
|
276
|
+
plugins: z2.array(plugin).default([])
|
|
277
|
+
});
|
|
278
|
+
var configuration = z2.object({
|
|
279
|
+
_format_version: z2.string(),
|
|
280
|
+
services: z2.array(service).default([]),
|
|
281
|
+
routes: z2.array(route).default([]),
|
|
282
|
+
plugins: z2.array(plugin).default([])
|
|
283
|
+
});
|
|
284
|
+
var DECK_TEMPLATE = /\$\{\{[^}]*\}\}/g;
|
|
285
|
+
var METHODS2 = /* @__PURE__ */ new Set([
|
|
286
|
+
"GET",
|
|
287
|
+
"PUT",
|
|
288
|
+
"POST",
|
|
289
|
+
"DELETE",
|
|
290
|
+
"PATCH",
|
|
291
|
+
"OPTIONS",
|
|
292
|
+
"HEAD",
|
|
293
|
+
"TRACE"
|
|
294
|
+
]);
|
|
295
|
+
var isMethod2 = (value) => METHODS2.has(value);
|
|
296
|
+
var NAME = /^[A-Za-z0-9][\w.:-]{0,199}$/;
|
|
297
|
+
function upstreamPathOf(entry) {
|
|
298
|
+
if (entry.path) return entry.path;
|
|
299
|
+
if (!entry.url) return void 0;
|
|
300
|
+
try {
|
|
301
|
+
const { pathname } = new URL(entry.url);
|
|
302
|
+
return pathname === "/" ? void 0 : pathname;
|
|
303
|
+
} catch {
|
|
304
|
+
return void 0;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function readKong(texts, options = {}) {
|
|
308
|
+
const documents = texts.flatMap(
|
|
309
|
+
(text) => parseAllDocuments(text.replace(DECK_TEMPLATE, "from-environment")).map(
|
|
310
|
+
(document) => document.toJS()
|
|
311
|
+
)
|
|
312
|
+
);
|
|
313
|
+
if (isKubernetes(documents)) {
|
|
314
|
+
const { routes: routes2, warnings: warnings2 } = readKubernetes(documents, options.services ?? {});
|
|
315
|
+
return { file: { gateway: options.gateway ?? "kong", routes: routes2 }, warnings: warnings2 };
|
|
316
|
+
}
|
|
317
|
+
const parts = texts.map((text, index) => {
|
|
318
|
+
const parsed = configuration.safeParse(
|
|
319
|
+
parseYaml(text.replace(DECK_TEMPLATE, "from-environment"))
|
|
320
|
+
);
|
|
321
|
+
if (parsed.success) return parsed.data;
|
|
322
|
+
const issue = parsed.error.issues[0];
|
|
323
|
+
throw new Error(
|
|
324
|
+
`file ${index + 1} is not a Kong declarative configuration (${issue?.path.join(".") || "root"}: ${issue?.message ?? "invalid"})`
|
|
325
|
+
);
|
|
326
|
+
});
|
|
327
|
+
const config = {
|
|
328
|
+
services: parts.flatMap((part) => part.services),
|
|
329
|
+
routes: parts.flatMap((part) => part.routes),
|
|
330
|
+
plugins: parts.flatMap((part) => part.plugins)
|
|
331
|
+
};
|
|
332
|
+
const warnings = [];
|
|
333
|
+
const scoped = (list) => list.filter((entry) => AUTHENTICATION.has(entry.name) && !entry.consumer);
|
|
334
|
+
const global = scoped(config.plugins).filter((entry) => !entry.service && !entry.route);
|
|
335
|
+
const routes = [
|
|
336
|
+
...config.services.flatMap((owner) => owner.routes.map((entry) => ({ entry, owner }))),
|
|
337
|
+
...config.routes.map((entry) => ({
|
|
338
|
+
entry,
|
|
339
|
+
owner: config.services.find(({ name }) => name === entry.service)
|
|
340
|
+
}))
|
|
341
|
+
];
|
|
342
|
+
const out = [];
|
|
343
|
+
for (const [index, { entry, owner }] of routes.entries()) {
|
|
344
|
+
const name = entry.name ?? `${owner?.name ?? "route"}-${index + 1}`;
|
|
345
|
+
if (!owner) {
|
|
346
|
+
warnings.push(`route ${name}: names no service in this configuration, so it was left out`);
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (!NAME.test(name)) {
|
|
350
|
+
warnings.push(
|
|
351
|
+
`route ${JSON.stringify(name)}: not a name the catalog takes, so it was left out`
|
|
352
|
+
);
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const patterns = (entry.paths ?? []).filter((p) => p.startsWith("~")).map((p) => p.slice(1));
|
|
356
|
+
const paths = entry.paths ? entry.paths.filter((p) => !p.startsWith("~")) : ["/"];
|
|
357
|
+
const methods = entry.methods?.map((m) => m.toUpperCase()).filter(isMethod2);
|
|
358
|
+
const levels = [
|
|
359
|
+
[
|
|
360
|
+
...scoped(entry.plugins),
|
|
361
|
+
...scoped(config.plugins).filter((p) => p.route !== void 0 && p.route === entry.name)
|
|
362
|
+
],
|
|
363
|
+
[
|
|
364
|
+
...scoped(owner.plugins),
|
|
365
|
+
...scoped(config.plugins).filter((p) => !p.route && p.service === owner.name)
|
|
366
|
+
],
|
|
367
|
+
global
|
|
368
|
+
];
|
|
369
|
+
const auth = [...AUTHENTICATION].filter((name2) => {
|
|
370
|
+
const instance = levels.map((level) => level.find((p) => p.name === name2)).find(Boolean);
|
|
371
|
+
return instance !== void 0 && authenticates(instance);
|
|
372
|
+
});
|
|
373
|
+
const upstreamPath = upstreamPathOf(owner);
|
|
374
|
+
out.push({
|
|
375
|
+
name,
|
|
376
|
+
service: options.services?.[owner.name] ?? owner.name,
|
|
377
|
+
...paths.length > 0 && { paths },
|
|
378
|
+
...patterns.length > 0 && { patterns },
|
|
379
|
+
...methods?.length && { methods },
|
|
380
|
+
stripPath: entry.strip_path,
|
|
381
|
+
...upstreamPath && { upstreamPath },
|
|
382
|
+
auth
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
return { file: { gateway: options.gateway ?? "kong", routes: out }, warnings };
|
|
386
|
+
}
|
|
387
|
+
async function configFiles(given) {
|
|
388
|
+
if (!(await fs.stat(given)).isDirectory()) return [given];
|
|
389
|
+
const entries = await fs.readdir(given, { recursive: true, withFileTypes: true });
|
|
390
|
+
return entries.filter((entry) => entry.isFile() && /\.ya?ml$/i.test(entry.name)).map((entry) => path.join(entry.parentPath, entry.name)).sort();
|
|
391
|
+
}
|
|
392
|
+
async function runFromKong(opts) {
|
|
393
|
+
const services = Object.fromEntries(
|
|
394
|
+
(opts.service ?? []).flatMap((pair) => {
|
|
395
|
+
const [kong, catalog] = pair.split("=");
|
|
396
|
+
return kong && catalog ? [[kong, catalog]] : [];
|
|
397
|
+
})
|
|
398
|
+
);
|
|
399
|
+
const readConfig = async (given) => {
|
|
400
|
+
try {
|
|
401
|
+
const files = (await Promise.all(given.map(configFiles))).flat();
|
|
402
|
+
const result2 = readKong(await Promise.all(files.map((file) => fs.readFile(file, "utf8"))), {
|
|
403
|
+
...opts.gateway && { gateway: opts.gateway },
|
|
404
|
+
services
|
|
405
|
+
});
|
|
406
|
+
for (const warning of result2.warnings) console.warn(kleur.yellow(`! ${warning}`));
|
|
407
|
+
return result2;
|
|
408
|
+
} catch (error) {
|
|
409
|
+
console.error(
|
|
410
|
+
kleur.red(
|
|
411
|
+
`\u2717 ${given.join(", ")}: ${error instanceof Error ? error.message : String(error)}`
|
|
412
|
+
)
|
|
413
|
+
);
|
|
414
|
+
return void 0;
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
const read = await readConfig(opts.config);
|
|
418
|
+
if (!read) return 1;
|
|
419
|
+
if (opts.sameAs) {
|
|
420
|
+
const other = await readConfig(opts.sameAs);
|
|
421
|
+
if (!other) return 1;
|
|
422
|
+
const differences = compareGateways(read.file, other.file, [
|
|
423
|
+
opts.config.join(", "),
|
|
424
|
+
opts.sameAs.join(", ")
|
|
425
|
+
]);
|
|
426
|
+
for (const difference of differences) console.error(kleur.red(`\u2717 ${difference}`));
|
|
427
|
+
if (differences.length > 0) return 1;
|
|
428
|
+
console.log(
|
|
429
|
+
`\u2713 both let through the same ${read.file.routes.length > 0 ? "routes" : "nothing"}`
|
|
430
|
+
);
|
|
431
|
+
return 0;
|
|
432
|
+
}
|
|
433
|
+
const root = path.resolve(opts.root);
|
|
434
|
+
const source = opts.config.map((file) => path.relative(root, path.resolve(file)).split(path.sep).join("/")).join(", ");
|
|
435
|
+
const result = await writeGatewayFile(root, { ...read.file, source }, { check: opts.check });
|
|
436
|
+
if (result === "stale") {
|
|
437
|
+
console.error(
|
|
438
|
+
kleur.red("\u2717 autocatalog.gateway.json does not match the configuration: run ac from-kong")
|
|
439
|
+
);
|
|
440
|
+
return 1;
|
|
441
|
+
}
|
|
442
|
+
console.log(
|
|
443
|
+
`${result === "current" ? "\u2713 autocatalog.gateway.json is current" : "\u2713 wrote autocatalog.gateway.json"}: ${read.file.routes.length} routes`
|
|
444
|
+
);
|
|
445
|
+
return 0;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export {
|
|
449
|
+
compareGateways,
|
|
450
|
+
readKong,
|
|
451
|
+
runFromKong
|
|
452
|
+
};
|
|
453
|
+
//# sourceMappingURL=chunk-5OQJGQPX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/authentication.ts","../src/compare.ts","../src/kubernetes.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { writeGatewayFile, type GatewayFile, type GatewayRoute } from 'autocatalog-core';\nimport kleur from 'kleur';\nimport { parseAllDocuments, parse as parseYaml } from 'yaml';\nimport { z } from 'zod';\nimport { AUTHENTICATION, authenticates } from './authentication';\nimport { compareGateways } from './compare';\nimport { isKubernetes, readKubernetes } from './kubernetes';\n\n// A decK declarative configuration, as `deck gateway dump` writes it and DB-less\n// Kong reads it. Routes sit under their service or at the top naming one, and\n// plugins sit under a service or route or at the top naming either, or neither,\n// which applies them to every route.\n\n/** A reference to a service or route, by name or as `{ name }`. */\nconst reference = z\n .union([z.string(), z.object({ name: z.string() }).transform(({ name }) => name)])\n .optional();\n\nconst plugin = z.object({\n name: z.string(),\n enabled: z.boolean().default(true),\n service: reference,\n route: reference,\n consumer: reference,\n /** Only `anonymous` is read: set, the plugin lets a request through without credentials. */\n config: z.looseObject({ anonymous: z.unknown().optional() }).optional(),\n});\n\ntype Plugin = z.infer<typeof plugin>;\n\nconst route = z.object({\n name: z.string().optional(),\n paths: z.array(z.string()).optional(),\n methods: z.array(z.string()).optional(),\n strip_path: z.boolean().default(true),\n service: reference,\n plugins: z.array(plugin).default([]),\n});\n\nconst service = z.object({\n name: z.string(),\n url: z.string().optional(),\n path: z.string().optional(),\n routes: z.array(route).default([]),\n plugins: z.array(plugin).default([]),\n});\n\nconst configuration = z.object({\n _format_version: z.string(),\n services: z.array(service).default([]),\n routes: z.array(route).default([]),\n plugins: z.array(plugin).default([]),\n});\n\n/** A decK template, such as `${{ env \"DECK_JWT_PUBLIC_KEY\" }}`. */\nconst DECK_TEMPLATE = /\\$\\{\\{[^}]*\\}\\}/g;\n\ntype Method = NonNullable<GatewayRoute['methods']>[number];\n\nconst METHODS = new Set<string>([\n 'GET',\n 'PUT',\n 'POST',\n 'DELETE',\n 'PATCH',\n 'OPTIONS',\n 'HEAD',\n 'TRACE',\n]);\n\nconst isMethod = (value: string): value is Method => METHODS.has(value);\n\nconst NAME = /^[A-Za-z0-9][\\w.:-]{0,199}$/;\n\n/** The path of a service's URL, which Kong forwards below. */\nfunction upstreamPathOf(entry: z.infer<typeof service>): string | undefined {\n if (entry.path) return entry.path;\n\n if (!entry.url) return undefined;\n\n try {\n const { pathname } = new URL(entry.url);\n\n return pathname === '/' ? undefined : pathname;\n } catch {\n return undefined;\n }\n}\n\nexport interface ReadKongOptions {\n /** The gateway's name in the catalog. */\n gateway?: string;\n /** Kong service names that differ from the catalog's, Kong's first. */\n services?: Record<string, string>;\n}\n\n/** What a configuration routes where, and what it could not say. */\n/** {@link readKong}'s answer. */\nexport interface KongRead {\n file: GatewayFile;\n /** What the configuration holds that the file cannot say, in words. */\n warnings: string[];\n}\n\n/**\n * What a configuration routes where, and what it could not say. Several files\n * are read as one, the way decK merges a platform file with each team's.\n */\nexport { compareGateways } from './compare';\n\nexport function readKong(texts: string[], options: ReadKongOptions = {}): KongRead {\n // Kubernetes manifests come several to a file, a decK configuration one.\n const documents = texts.flatMap((text) =>\n parseAllDocuments(text.replace(DECK_TEMPLATE, 'from-environment')).map((document) =>\n document.toJS(),\n ),\n );\n\n if (isKubernetes(documents)) {\n const { routes, warnings } = readKubernetes(documents, options.services ?? {});\n\n return { file: { gateway: options.gateway ?? 'kong', routes }, warnings };\n }\n\n const parts = texts.map((text, index) => {\n // decK fills `${{ env \"NAME\" }}` before it parses, so do the same. Routes and\n // plugins are all this reads, so no value is looked up.\n const parsed = configuration.safeParse(\n parseYaml(text.replace(DECK_TEMPLATE, 'from-environment')),\n );\n\n if (parsed.success) return parsed.data;\n\n const issue = parsed.error.issues[0];\n\n throw new Error(\n `file ${index + 1} is not a Kong declarative configuration (${issue?.path.join('.') || 'root'}: ${issue?.message ?? 'invalid'})`,\n );\n });\n\n const config = {\n services: parts.flatMap((part) => part.services),\n routes: parts.flatMap((part) => part.routes),\n plugins: parts.flatMap((part) => part.plugins),\n };\n\n const warnings: string[] = [];\n\n // A plugin scoped to a consumer applies to that consumer only, never to a route as such.\n const scoped = (list: Plugin[]) =>\n list.filter((entry) => AUTHENTICATION.has(entry.name) && !entry.consumer);\n\n const global = scoped(config.plugins).filter((entry) => !entry.service && !entry.route);\n\n const routes = [\n ...config.services.flatMap((owner) => owner.routes.map((entry) => ({ entry, owner }))),\n ...config.routes.map((entry) => ({\n entry,\n owner: config.services.find(({ name }) => name === entry.service),\n })),\n ];\n\n const out: GatewayRoute[] = [];\n\n for (const [index, { entry, owner }] of routes.entries()) {\n const name = entry.name ?? `${owner?.name ?? 'route'}-${index + 1}`;\n\n if (!owner) {\n warnings.push(`route ${name}: names no service in this configuration, so it was left out`);\n continue;\n }\n\n if (!NAME.test(name)) {\n warnings.push(\n `route ${JSON.stringify(name)}: not a name the catalog takes, so it was left out`,\n );\n continue;\n }\n\n // Kong marks a regular expression with `~` and anchors it at the start.\n const patterns = (entry.paths ?? []).filter((p) => p.startsWith('~')).map((p) => p.slice(1));\n const paths = entry.paths ? entry.paths.filter((p) => !p.startsWith('~')) : ['/'];\n\n const methods = entry.methods?.map((m) => m.toUpperCase()).filter(isMethod);\n\n // Kong runs the most specific instance of each plugin: the route's, then the\n // service's, then the global one. A more specific instance that is disabled,\n // or lets anonymous requests through, is what the route gets.\n const levels = [\n [\n ...scoped(entry.plugins),\n ...scoped(config.plugins).filter((p) => p.route !== undefined && p.route === entry.name),\n ],\n [\n ...scoped(owner.plugins),\n ...scoped(config.plugins).filter((p) => !p.route && p.service === owner.name),\n ],\n global,\n ];\n\n const auth = [...AUTHENTICATION].filter((name) => {\n const instance = levels.map((level) => level.find((p) => p.name === name)).find(Boolean);\n\n return instance !== undefined && authenticates(instance);\n });\n\n const upstreamPath = upstreamPathOf(owner);\n\n out.push({\n name,\n service: options.services?.[owner.name] ?? owner.name,\n ...(paths.length > 0 && { paths }),\n ...(patterns.length > 0 && { patterns }),\n ...(methods?.length && { methods }),\n stripPath: entry.strip_path,\n ...(upstreamPath && { upstreamPath }),\n auth,\n });\n }\n\n return { file: { gateway: options.gateway ?? 'kong', routes: out }, warnings };\n}\n\n/** A file as given, or every YAML file below a folder, in path order. */\nasync function configFiles(given: string): Promise<string[]> {\n if (!(await fs.stat(given)).isDirectory()) return [given];\n\n const entries = await fs.readdir(given, { recursive: true, withFileTypes: true });\n\n return entries\n .filter((entry) => entry.isFile() && /\\.ya?ml$/i.test(entry.name))\n .map((entry) => path.join(entry.parentPath, entry.name))\n .sort();\n}\n\nexport async function runFromKong(opts: {\n config: string[];\n root: string;\n gateway?: string;\n service?: string[];\n check?: boolean;\n /** Another configuration of the same gateway that must let through the same routes. */\n sameAs?: string[];\n}): Promise<number> {\n const services = Object.fromEntries(\n (opts.service ?? []).flatMap((pair) => {\n const [kong, catalog] = pair.split('=');\n\n return kong && catalog ? [[kong, catalog]] : [];\n }),\n );\n\n const readConfig = async (given: string[]): Promise<KongRead | undefined> => {\n try {\n const files = (await Promise.all(given.map(configFiles))).flat();\n\n const result = readKong(await Promise.all(files.map((file) => fs.readFile(file, 'utf8'))), {\n ...(opts.gateway && { gateway: opts.gateway }),\n services,\n });\n\n for (const warning of result.warnings) console.warn(kleur.yellow(`! ${warning}`));\n\n return result;\n } catch (error) {\n console.error(\n kleur.red(\n `✗ ${given.join(', ')}: ${error instanceof Error ? error.message : String(error)}`,\n ),\n );\n\n return undefined;\n }\n };\n\n const read = await readConfig(opts.config);\n\n if (!read) return 1;\n\n // Two configurations of one gateway, such as Compose and Kubernetes: compared, nothing written.\n if (opts.sameAs) {\n const other = await readConfig(opts.sameAs);\n\n if (!other) return 1;\n\n const differences = compareGateways(read.file, other.file, [\n opts.config.join(', '),\n opts.sameAs.join(', '),\n ]);\n\n for (const difference of differences) console.error(kleur.red(`✗ ${difference}`));\n\n if (differences.length > 0) return 1;\n\n console.log(\n `✓ both let through the same ${read.file.routes.length > 0 ? 'routes' : 'nothing'}`,\n );\n\n return 0;\n }\n\n const root = path.resolve(opts.root);\n\n const source = opts.config\n .map((file) => path.relative(root, path.resolve(file)).split(path.sep).join('/'))\n .join(', ');\n\n const result = await writeGatewayFile(root, { ...read.file, source }, { check: opts.check });\n\n if (result === 'stale') {\n console.error(\n kleur.red('✗ autocatalog.gateway.json does not match the configuration: run ac from-kong'),\n );\n\n return 1;\n }\n\n console.log(\n `${result === 'current' ? '✓ autocatalog.gateway.json is current' : '✓ wrote autocatalog.gateway.json'}: ${read.file.routes.length} routes`,\n );\n\n return 0;\n}\n","/** Kong's plugins that authenticate a request, open source and Enterprise. */\nexport const AUTHENTICATION = new Set([\n 'basic-auth',\n 'hmac-auth',\n 'jwt',\n 'key-auth',\n 'key-auth-enc',\n 'ldap-auth',\n 'ldap-auth-advanced',\n 'mtls-auth',\n 'oauth2',\n 'oauth2-introspection',\n 'openid-connect',\n 'vault-auth',\n]);\n\n/**\n * Whether a plugin instance authenticates a request: an authentication plugin,\n * switched on, that does not let an anonymous request through.\n */\nexport const authenticates = (instance: {\n enabled: boolean;\n config?: { anonymous?: unknown } | undefined;\n}) => instance.enabled && !instance.config?.anonymous;\n","import type { GatewayFile } from 'autocatalog-core';\n\n/**\n * Each route as method × matcher, so two configurations compare whatever their\n * route names. One matcher two routes cover keeps both: either can serve it.\n */\nfunction atoms(file: GatewayFile): Map<string, Set<string>> {\n const out = new Map<string, Set<string>>();\n\n for (const route of file.routes) {\n const matchers = [\n ...(route.paths ?? []).map((path) => `${path}*`),\n ...(route.patterns ?? []).map((pattern) => `~${pattern}`),\n ];\n\n const how = [\n route.auth?.length\n ? `auth ${route.auth.join('+')}`\n : route.auth\n ? 'no auth'\n : 'auth not said',\n route.stripPath ? 'strips the path' : '',\n route.upstreamPath ? `to ${route.upstreamPath}` : '',\n ]\n .filter(Boolean)\n .join(', ');\n\n for (const method of route.methods ?? ['ANY'])\n for (const matcher of matchers) {\n const key = `${route.service} ${method} ${matcher}`;\n\n out.set(key, (out.get(key) ?? new Set()).add(how));\n }\n }\n\n return out;\n}\n\nconst shown = (hows: Set<string>) => [...hows].sort().join(' | ');\n\nconst same = (a: Set<string>, b: Set<string>) =>\n a.size === b.size && [...a].every((how) => b.has(how));\n\n/**\n * Where two configurations of one gateway disagree: a route one lets through\n * and the other does not, or the same route with other authentication, path\n * stripping or upstream path. Written the same but spelt differently, such as a\n * prefix in one and a pattern in the other, reads as a difference.\n */\nexport function compareGateways(\n first: GatewayFile,\n second: GatewayFile,\n names: [string, string],\n): string[] {\n const [a, b] = [atoms(first), atoms(second)];\n const differences: string[] = [];\n\n for (const [key, how] of a) {\n const other = b.get(key);\n\n if (other === undefined)\n differences.push(`only ${names[0]} lets through ${key} (${shown(how)})`);\n else if (!same(how, other))\n differences.push(`${key}: ${shown(how)} in ${names[0]}, ${shown(other)} in ${names[1]}`);\n }\n\n for (const [key, how] of b)\n if (!a.has(key)) differences.push(`only ${names[1]} lets through ${key} (${shown(how)})`);\n\n return differences.sort();\n}\n","import type { GatewayRoute } from 'autocatalog-core';\nimport { z } from 'zod';\nimport { AUTHENTICATION, authenticates } from './authentication';\n\n// Kong on Kubernetes: routes are Gateway API HTTPRoutes, which any controller\n// reads, and Kong adds its plugins as KongPlugin and KongClusterPlugin resources,\n// attached by the `konghq.com/plugins` annotation on a route or a Service, or to\n// every route by the label `global: \"true\"`.\n\nconst metadata = z.object({\n name: z.string(),\n namespace: z.string().optional(),\n labels: z.record(z.string(), z.string()).default({}),\n annotations: z.record(z.string(), z.string()).default({}),\n});\n\nconst manifest = z.looseObject({ apiVersion: z.string(), kind: z.string(), metadata });\n\ntype Manifest = z.infer<typeof manifest>;\n\nconst kongPlugin = z.looseObject({\n plugin: z.string(),\n disabled: z.boolean().default(false),\n config: z.looseObject({ anonymous: z.unknown().optional() }).optional(),\n});\n\nconst match = z.object({\n path: z\n .object({\n type: z.enum(['Exact', 'PathPrefix', 'RegularExpression']).default('PathPrefix'),\n value: z.string().default('/'),\n })\n .default({ type: 'PathPrefix', value: '/' }),\n method: z.string().optional(),\n});\n\nconst httpRoute = z.object({\n spec: z.object({\n parentRefs: z\n .array(z.object({ name: z.string(), namespace: z.string().optional() }))\n .default([]),\n rules: z\n .array(\n z.object({\n matches: z.array(match).default([{ path: { type: 'PathPrefix', value: '/' } }]),\n filters: z.array(z.object({ type: z.string() })).default([]),\n backendRefs: z\n .array(z.object({ name: z.string(), namespace: z.string().optional() }))\n .default([]),\n }),\n )\n .default([]),\n }),\n});\n\nconst gateway = z.object({\n spec: z.object({\n listeners: z.array(\n z.object({\n allowedRoutes: z\n .object({\n namespaces: z\n .object({\n from: z.enum(['All', 'Same', 'Selector']).default('Same'),\n selector: z\n .object({ matchLabels: z.record(z.string(), z.string()).default({}) })\n .optional(),\n })\n .default({ from: 'Same' }),\n })\n .default({ namespaces: { from: 'Same' } }),\n }),\n ),\n }),\n});\n\n/** Whether parsed YAML documents are Kubernetes manifests rather than a decK file. */\nexport function isKubernetes(documents: unknown[]): boolean {\n return documents.some((document) => manifest.safeParse(document).success);\n}\n\nconst escape = (value: string) => value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nconst METHODS = new Set<string>([\n 'GET',\n 'PUT',\n 'POST',\n 'DELETE',\n 'PATCH',\n 'OPTIONS',\n 'HEAD',\n 'TRACE',\n]);\n\ntype Method = NonNullable<GatewayRoute['methods']>[number];\n\nconst isMethod = (value: string): value is Method => METHODS.has(value);\n\nconst pluginNames = (resource: Manifest) =>\n (resource.metadata.annotations['konghq.com/plugins'] ?? '')\n .split(',')\n .map((name) => name.trim())\n .filter(Boolean);\n\n/** {@link readKubernetes}'s answer. */\nexport interface KubernetesRead {\n routes: GatewayRoute[];\n /** What the manifests hold that the file cannot say, in words. */\n warnings: string[];\n}\n\n/**\n * The routes Kong serves from these manifests, and what they hold that the file\n * cannot say. `services` maps a Kubernetes Service name to the catalog's.\n */\nexport function readKubernetes(\n documents: unknown[],\n services: Record<string, string>,\n): KubernetesRead {\n const resources = documents.flatMap((document) => {\n const parsed = manifest.safeParse(document);\n\n return parsed.success ? [parsed.data] : [];\n });\n\n const warnings: string[] = [];\n const of = (kind: string) => resources.filter((resource) => resource.kind === kind);\n const namespaceOf = (resource: Manifest) => resource.metadata.namespace ?? 'default';\n\n // A KongPlugin is found in the namespace of what names it; a KongClusterPlugin anywhere.\n const instance = (name: string, namespace: string) => {\n const found =\n of('KongPlugin').find((p) => p.metadata.name === name && namespaceOf(p) === namespace) ??\n of('KongClusterPlugin').find((p) => p.metadata.name === name);\n\n const parsed = found && kongPlugin.safeParse(found);\n\n return parsed?.success ? parsed.data : undefined;\n };\n\n const authenticating = (names: string[], namespace: string) =>\n names.flatMap((name) => {\n const plugin = instance(name, namespace);\n\n return plugin && AUTHENTICATION.has(plugin.plugin) ? [plugin] : [];\n });\n\n const global = of('KongClusterPlugin').flatMap((resource) => {\n const parsed = kongPlugin.safeParse(resource);\n\n return parsed.success &&\n resource.metadata.labels.global === 'true' &&\n AUTHENTICATION.has(parsed.data.plugin)\n ? [parsed.data]\n : [];\n });\n\n const namespaceLabels = new Map(\n of('Namespace').map((resource) => [resource.metadata.name, resource.metadata.labels]),\n );\n\n const gateways = new Map(\n of('Gateway').flatMap((resource) => {\n const parsed = gateway.safeParse(resource);\n\n return parsed.success\n ? [[`${namespaceOf(resource)}/${resource.metadata.name}`, parsed.data]]\n : [];\n }),\n );\n\n /** Why a route's namespace may not attach to its Gateway, or nothing when it may. */\n const refused = (namespace: string, ref: { name: string; namespace?: string | undefined }) => {\n const gatewayNamespace = ref.namespace ?? namespace;\n const parent = gateways.get(`${gatewayNamespace}/${ref.name}`);\n\n // Without the Gateway in these files there is nothing to check against.\n if (!parent)\n return gateways.size > 0\n ? `its Gateway ${gatewayNamespace}/${ref.name} is not in these files`\n : undefined;\n\n const allowed = parent.spec.listeners.some(({ allowedRoutes: { namespaces } }) => {\n if (namespaces.from === 'All') return true;\n\n if (namespaces.from === 'Same') return namespace === gatewayNamespace;\n\n const labels = namespaceLabels.get(namespace);\n\n return (\n labels !== undefined &&\n Object.entries(namespaces.selector?.matchLabels ?? {}).every(\n ([key, value]) => labels[key] === value,\n )\n );\n });\n\n return allowed\n ? undefined\n : `namespace ${namespace} may not attach routes to the Gateway ${gatewayNamespace}/${ref.name}`;\n };\n\n const routes: GatewayRoute[] = [];\n const taken = new Set<string>();\n\n for (const resource of of('HTTPRoute')) {\n const parsed = httpRoute.safeParse(resource);\n const namespace = namespaceOf(resource);\n const name = resource.metadata.name;\n\n if (!parsed.success) {\n warnings.push(\n `HTTPRoute ${namespace}/${name}: not read (${parsed.error.issues[0]?.message ?? 'invalid'})`,\n );\n continue;\n }\n\n const reasons = parsed.data.spec.parentRefs.flatMap((ref) => refused(namespace, ref) ?? []);\n\n if (\n parsed.data.spec.parentRefs.length > 0 &&\n reasons.length === parsed.data.spec.parentRefs.length\n ) {\n warnings.push(\n `HTTPRoute ${namespace}/${name} is not attached, so it was left out: ${reasons.join('; ')}`,\n );\n continue;\n }\n\n for (const [index, rule] of parsed.data.spec.rules.entries()) {\n const [backend, ...more] = rule.backendRefs;\n\n if (!backend) continue;\n\n if (more.length)\n warnings.push(\n `HTTPRoute ${namespace}/${name}: only its first backend, ${backend.name}, was read`,\n );\n\n const rewrites = rule.filters.filter((filter) => filter.type === 'URLRewrite');\n\n if (rewrites.length)\n warnings.push(\n `HTTPRoute ${namespace}/${name}: a URLRewrite filter changes the path, which the catalog does not follow`,\n );\n\n const backendNamespace = backend.namespace ?? namespace;\n\n const service = of('Service').find(\n (s) => s.metadata.name === backend.name && namespaceOf(s) === backendNamespace,\n );\n\n // Kong runs the most specific instance of each plugin: the route's, then the Service's, then the global one.\n const levels = [\n authenticating(pluginNames(resource), namespace),\n service ? authenticating(pluginNames(service), backendNamespace) : [],\n global,\n ];\n\n const auth = [...AUTHENTICATION].filter((plugin) => {\n const chosen = levels.map((level) => level.find((p) => p.plugin === plugin)).find(Boolean);\n\n return (\n chosen !== undefined &&\n authenticates({ enabled: !chosen.disabled, config: chosen.config })\n );\n });\n\n // One gateway route per method: each match carries its own.\n const byMethod = new Map<string, z.infer<typeof match>[]>();\n\n for (const entry of rule.matches) {\n const method = entry.method?.toUpperCase() ?? '*';\n\n byMethod.set(method, [...(byMethod.get(method) ?? []), entry]);\n }\n\n for (const [method, entries] of byMethod) {\n const paths = entries.flatMap(({ path }) =>\n path.type === 'PathPrefix' ? [path.value] : [],\n );\n\n // Kong matches a Gateway API regular expression against the whole path.\n const patterns = entries.flatMap(({ path }) =>\n path.type === 'Exact'\n ? [`${escape(path.value)}$`]\n : path.type === 'RegularExpression'\n ? [path.value.endsWith('$') ? path.value : `${path.value}$`]\n : [],\n );\n\n const base = [\n name,\n parsed.data.spec.rules.length > 1 ? String(index + 1) : '',\n byMethod.size > 1 ? method.toLowerCase() : '',\n ]\n .filter(Boolean)\n .join('-');\n\n const routeName = taken.has(base) ? `${namespace}.${base}` : base;\n\n taken.add(routeName);\n routes.push({\n name: routeName,\n service: services[backend.name] ?? backend.name,\n ...(paths.length > 0 && { paths }),\n ...(patterns.length > 0 && { patterns }),\n ...(isMethod(method) && { methods: [method] }),\n stripPath: resource.metadata.annotations['konghq.com/strip-path'] === 'true',\n auth,\n });\n }\n }\n }\n\n return { routes, warnings };\n}\n"],"mappings":"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","names":["z","route","path","plugin","service","path","z","METHODS","isMethod","routes","warnings","name","result"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { GatewayFile } from 'autocatalog-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Where two configurations of one gateway disagree: a route one lets through
|
|
5
|
+
* and the other does not, or the same route with other authentication, path
|
|
6
|
+
* stripping or upstream path. Written the same but spelt differently, such as a
|
|
7
|
+
* prefix in one and a pattern in the other, reads as a difference.
|
|
8
|
+
*/
|
|
9
|
+
declare function compareGateways(first: GatewayFile, second: GatewayFile, names: [string, string]): string[];
|
|
10
|
+
|
|
11
|
+
interface ReadKongOptions {
|
|
12
|
+
/** The gateway's name in the catalog. */
|
|
13
|
+
gateway?: string;
|
|
14
|
+
/** Kong service names that differ from the catalog's, Kong's first. */
|
|
15
|
+
services?: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
/** What a configuration routes where, and what it could not say. */
|
|
18
|
+
/** {@link readKong}'s answer. */
|
|
19
|
+
interface KongRead {
|
|
20
|
+
file: GatewayFile;
|
|
21
|
+
/** What the configuration holds that the file cannot say, in words. */
|
|
22
|
+
warnings: string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
declare function readKong(texts: string[], options?: ReadKongOptions): KongRead;
|
|
26
|
+
declare function runFromKong(opts: {
|
|
27
|
+
config: string[];
|
|
28
|
+
root: string;
|
|
29
|
+
gateway?: string;
|
|
30
|
+
service?: string[];
|
|
31
|
+
check?: boolean;
|
|
32
|
+
/** Another configuration of the same gateway that must let through the same routes. */
|
|
33
|
+
sameAs?: string[];
|
|
34
|
+
}): Promise<number>;
|
|
35
|
+
|
|
36
|
+
export { type KongRead, type ReadKongOptions, compareGateways, readKong, runFromKong };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autocatalog-plugin-kong",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ac from-kong: put what a Kong gateway exposes on the company site.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/jagreehal/autocatalog.git",
|
|
9
|
+
"directory": "packages/events/plugin-kong"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://jagreehal.github.io/autocatalog",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/jagreehal/autocatalog/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"bin": {
|
|
19
|
+
"autocatalog-from-kong": "./dist/bin.js"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"autocatalog": {
|
|
31
|
+
"plugin": {
|
|
32
|
+
"command": "from-kong",
|
|
33
|
+
"description": "Read the routes of a Kong declarative configuration",
|
|
34
|
+
"entry": "./dist/bin.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"commander": "^15.0.0",
|
|
39
|
+
"kleur": "^4.1.5",
|
|
40
|
+
"yaml": "^2.9.1",
|
|
41
|
+
"zod": "^4.6.5",
|
|
42
|
+
"autocatalog-core": "0.10.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^26.6.4",
|
|
46
|
+
"tsup": "^8.5.1",
|
|
47
|
+
"typescript": "^6.0.3",
|
|
48
|
+
"vitest": "^5.0.3"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"test": "vitest run"
|
|
53
|
+
}
|
|
6
54
|
}
|