@takodotid/circuit 0.9.0 → 0.11.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.
Files changed (48) hide show
  1. package/README.md +3 -2
  2. package/dist/chunks/index-ex1wcst7.js +109 -0
  3. package/dist/chunks/index-fq57hya4.js +20 -0
  4. package/dist/chunks/new-7n8qvtjd.js +168 -0
  5. package/dist/cli/index.js +4079 -0
  6. package/dist/index.js +48 -0
  7. package/dist/presets/index.js +269 -0
  8. package/dist/types/adapters/devices/catalog.d.ts +58 -0
  9. package/dist/types/adapters/devices/derive.d.ts +5 -0
  10. package/dist/types/adapters/devices/index.d.ts +3 -0
  11. package/dist/types/adapters/devices/lines.d.ts +56 -0
  12. package/dist/types/adapters/devices/raisecom-ros/index.d.ts +23 -0
  13. package/dist/types/adapters/devices/raisecom-ros/models.d.ts +14 -0
  14. package/dist/types/adapters/devices/routeros/export.d.ts +39 -0
  15. package/dist/types/adapters/devices/routeros/index.d.ts +17 -0
  16. package/dist/types/adapters/devices/routeros/models.d.ts +14 -0
  17. package/dist/types/adapters/devices/routeros/plan.d.ts +4 -0
  18. package/dist/types/adapters/devices/routeros/render.d.ts +96 -0
  19. package/dist/types/adapters/devices/types.d.ts +34 -0
  20. package/dist/types/adapters/devices/vrp/index.d.ts +24 -0
  21. package/dist/types/adapters/devices/vrp/models.d.ts +12 -0
  22. package/dist/types/adapters/registries/index.d.ts +3 -0
  23. package/dist/types/adapters/registries/peeringdb.d.ts +16 -0
  24. package/dist/types/core/addr.d.ts +24 -0
  25. package/dist/types/core/define.d.ts +50 -0
  26. package/dist/types/core/merge.d.ts +4 -0
  27. package/dist/types/core/secrets.d.ts +21 -0
  28. package/dist/types/core/time.d.ts +2 -0
  29. package/dist/types/core/validate.d.ts +2 -0
  30. package/dist/types/index.d.ts +6 -0
  31. package/dist/types/presets/index.d.ts +101 -0
  32. package/dist/types/schema/common.d.ts +24 -0
  33. package/dist/types/schema/device.d.ts +80 -0
  34. package/dist/types/schema/firewall.d.ts +118 -0
  35. package/dist/types/schema/index.d.ts +7 -0
  36. package/dist/types/schema/interfaces.d.ts +181 -0
  37. package/dist/types/schema/policy.d.ts +88 -0
  38. package/dist/types/schema/routing.d.ts +115 -0
  39. package/dist/types/schema/system.d.ts +186 -0
  40. package/dist/types/transport/ssh.d.ts +22 -0
  41. package/package.json +19 -5
  42. package/src/cli/index.ts +10 -2
  43. package/src/cli/new.ts +60 -12
  44. package/src/core/secrets.ts +7 -9
  45. package/src/presets/index.ts +17 -3
  46. package/templates/project/AGENTS.md +10 -10
  47. package/templates/project/README.md +7 -7
  48. package/templates/project/tsconfig.json +1 -1
package/dist/index.js ADDED
@@ -0,0 +1,48 @@
1
+ import {
2
+ contains,
3
+ familyOf,
4
+ host,
5
+ isAddress,
6
+ isCidr,
7
+ isGlobal,
8
+ network
9
+ } from "./chunks/index-ex1wcst7.js";
10
+ import"./chunks/index-fq57hya4.js";
11
+
12
+ // src/core/define.ts
13
+ var secret = (name) => ({ secret: name });
14
+ var secretFile = (path) => {
15
+ if (!/^[A-Za-z0-9_./-]+$/.test(path))
16
+ throw new Error(`secret file path ${path} may hold only letters, digits, _ . / and -`);
17
+ return { secret_file: path };
18
+ };
19
+ function defineDevice(device) {
20
+ return device;
21
+ }
22
+ var defineNetwork = (network2) => network2;
23
+ // src/core/merge.ts
24
+ function merge(...records) {
25
+ const result = {};
26
+ for (const record of records) {
27
+ for (const [key, value] of Object.entries(record)) {
28
+ if (key in result)
29
+ throw new Error(`merge: ${key} is declared twice`);
30
+ result[key] = value;
31
+ }
32
+ }
33
+ return result;
34
+ }
35
+ export {
36
+ secretFile,
37
+ secret,
38
+ network as prefixOf,
39
+ merge,
40
+ isGlobal,
41
+ isCidr,
42
+ isAddress,
43
+ host,
44
+ familyOf,
45
+ defineNetwork,
46
+ defineDevice,
47
+ contains
48
+ };
@@ -0,0 +1,269 @@
1
+ import {
2
+ contains
3
+ } from "../chunks/index-ex1wcst7.js";
4
+ import"../chunks/index-fq57hya4.js";
5
+
6
+ // src/presets/index.ts
7
+ var MARTIANS = ["127.0.0.0/8", "169.254.0.0/16", "0.0.0.0/8"];
8
+ var PRIVATE_RANGES = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"];
9
+ var antiSpoofing = (vlan, sources) => sources.map((src) => ({
10
+ description: `${vlan}: drop source ${src}`,
11
+ match: { vlan, src },
12
+ action: "drop"
13
+ }));
14
+ var BAD_TCP_FLAGS = [
15
+ ["FIN+SYN", ["fin", "syn"], []],
16
+ ["SYN+RST", ["syn", "rst"], []],
17
+ ["FIN+RST", ["fin", "rst"], []],
18
+ ["FIN without ACK", ["fin"], ["ack"]],
19
+ ["FIN+URG", ["fin", "urg"], []],
20
+ ["Xmas scan", ["fin", "syn", "psh", "urg"], []]
21
+ ];
22
+ var badTcpFlags = () => BAD_TCP_FLAGS.map(([name, set, unset]) => ({
23
+ description: `Bad flags ${name}`,
24
+ match: { protocol: "tcp", tcp_flags: { set, unset } },
25
+ action: "drop"
26
+ }));
27
+ function bgpSanity(lengths = {}) {
28
+ const ipv4 = lengths.ipv4 ?? { min: 8, max: 24 };
29
+ const ipv6 = lengths.ipv6 ?? { min: 16, max: 48 };
30
+ return [
31
+ {
32
+ description: `IPv4 shorter than /${ipv4.min}`,
33
+ match: { family: "ipv4", prefix_length: { max: ipv4.min - 1 } },
34
+ action: "reject"
35
+ },
36
+ { description: `IPv4 longer than /${ipv4.max}`, match: { family: "ipv4", prefix_length: { min: ipv4.max + 1 } }, action: "reject" },
37
+ {
38
+ description: `IPv6 shorter than /${ipv6.min}`,
39
+ match: { family: "ipv6", prefix_length: { max: ipv6.min - 1 } },
40
+ action: "reject"
41
+ },
42
+ { description: `IPv6 longer than /${ipv6.max}`, match: { family: "ipv6", prefix_length: { min: ipv6.max + 1 } }, action: "reject" },
43
+ { description: "RPKI invalid", match: { rpki: "invalid" }, action: "reject" }
44
+ ];
45
+ }
46
+ function neighborPolicies(device) {
47
+ const bgp = device.routing?.bgp;
48
+ return Object.entries(bgp?.neighbors ?? {}).map(([name, neighbor]) => {
49
+ const group = neighbor.group ? bgp?.groups?.[neighbor.group] : undefined;
50
+ return { name, address: neighbor.address, import: neighbor.import ?? group?.import, export: neighbor.export ?? group?.export };
51
+ });
52
+ }
53
+ function offered(device, policy) {
54
+ const rules = policy ? device.policies?.[policy] ?? [] : [];
55
+ return rules.filter((rule) => rule.action === "accept" && rule.match?.prefix).map((rule) => rule.match.prefix);
56
+ }
57
+ var exportsEndInReject = (devices) => {
58
+ const findings = [];
59
+ for (const device of devices) {
60
+ for (const neighbor of neighborPolicies(device)) {
61
+ const last = neighbor.export ? device.policies?.[neighbor.export]?.at(-1) : undefined;
62
+ if (neighbor.export && (last?.action !== "reject" || last.match)) {
63
+ findings.push({
64
+ level: "error",
65
+ device: device.name,
66
+ message: `${neighbor.name}: export ${neighbor.export} does not end in an unconditional reject`
67
+ });
68
+ }
69
+ }
70
+ }
71
+ return findings;
72
+ };
73
+ var vlansOf = (port) => [port.access_vlan, ...port.trunk_vlans ?? []].filter((vlan) => vlan !== undefined);
74
+ var trustBoundary = (boundary) => (devices) => {
75
+ const untrusted = new Set(boundary.untrusted);
76
+ const routers = new Set(boundary.routers);
77
+ const findings = [];
78
+ for (const device of devices) {
79
+ const isRouter = routers.has(device.name);
80
+ const ports = Object.entries(device.ports ?? {}).flatMap(([name, port]) => port ? [{ name, port }] : []);
81
+ const carriers = [
82
+ ...ports.filter(({ port }) => !port.lag).map(({ name, port }) => ({
83
+ what: `port ${name}`,
84
+ carried: vlansOf(port),
85
+ faces: port.link ? [port.link.device] : []
86
+ })),
87
+ ...Object.entries(device.interfaces ?? {}).flatMap(([name, iface]) => iface.type === "lag" ? [
88
+ {
89
+ what: `LAG ${name}`,
90
+ carried: vlansOf(iface),
91
+ faces: ports.filter(({ port }) => port.lag === name && port.link).map(({ port }) => port.link.device)
92
+ }
93
+ ] : [])
94
+ ];
95
+ for (const { what, carried, faces } of carriers) {
96
+ const mixes = carried.some((vlan) => untrusted.has(vlan)) && carried.some((vlan) => !untrusted.has(vlan));
97
+ const facesRouter = isRouter || faces.some((name) => routers.has(name));
98
+ if (mixes && !facesRouter) {
99
+ findings.push({
100
+ level: "error",
101
+ device: device.name,
102
+ message: `${what} carries an untrusted VLAN beside a trusted one and does not face a router`
103
+ });
104
+ }
105
+ }
106
+ if (isRouter)
107
+ continue;
108
+ for (const [name, iface] of Object.entries(device.interfaces ?? {})) {
109
+ if (iface.type === "vlan" && untrusted.has(iface.vlan)) {
110
+ findings.push({
111
+ level: "error",
112
+ device: device.name,
113
+ message: `interface ${name} puts an address on untrusted VLAN ${iface.vlan}; only a router may`
114
+ });
115
+ }
116
+ }
117
+ }
118
+ return findings;
119
+ };
120
+ var tunnelsOutsideOffered = (devices) => {
121
+ const findings = [];
122
+ for (const device of devices) {
123
+ for (const [name, iface] of Object.entries(device.interfaces ?? {})) {
124
+ if (iface.type !== "gre" && iface.type !== "vxlan")
125
+ continue;
126
+ const subnets = (iface.addresses ?? []).map((entry) => typeof entry === "string" ? entry : entry.address);
127
+ for (const neighbor of neighborPolicies(device)) {
128
+ if (!subnets.some((subnet) => contains(subnet, neighbor.address)))
129
+ continue;
130
+ for (const prefix of offered(device, neighbor.export)) {
131
+ if (contains(prefix, iface.local)) {
132
+ findings.push({
133
+ level: "error",
134
+ device: device.name,
135
+ message: `${name} is sourced from ${iface.local}, inside ${prefix}, which ${neighbor.name} is offered`
136
+ });
137
+ }
138
+ }
139
+ }
140
+ }
141
+ }
142
+ return findings;
143
+ };
144
+ var PREPENDS = [
145
+ ["once", 1],
146
+ ["twice", 2],
147
+ ["three_times", 3]
148
+ ];
149
+ var article = (word) => /^[aeiou]/i.test(word) ? `an ${word}` : `a ${word}`;
150
+ function communityScheme(scheme) {
151
+ const { asn } = scheme;
152
+ const community = (fn, parameter) => `${asn}:${fn}:${parameter}`;
153
+ const ours = { standard: [`${asn}:*`], large: [`${asn}:*:*`] };
154
+ const information = [scheme.learned_from?.function, scheme.learned_at?.function, scheme.learned_from_as].filter((fn) => fn !== undefined);
155
+ const tag = (neighborClass, neighborAs, options = {}) => {
156
+ const { site, keepActions = false } = options;
157
+ if (scheme.learned_at && site === undefined)
158
+ throw new Error(`communityScheme: tag for AS${neighborAs} needs a site, for learned_at`);
159
+ return {
160
+ description: `Learned from AS${neighborAs}`,
161
+ set: {
162
+ remove_communities: ours.standard,
163
+ remove_large_communities: keepActions ? information.map((fn) => community(fn, "*")) : ours.large,
164
+ add_large_communities: [
165
+ ...scheme.learned_from ? [community(scheme.learned_from.function, scheme.learned_from.classes[neighborClass])] : [],
166
+ ...scheme.learned_at ? [community(scheme.learned_at.function, site)] : [],
167
+ ...scheme.learned_from_as !== undefined ? [community(scheme.learned_from_as, neighborAs)] : []
168
+ ]
169
+ }
170
+ };
171
+ };
172
+ const blackhole = (prefixSet) => {
173
+ if (scheme.blackhole === undefined)
174
+ throw new Error("communityScheme: blackhole needs a function number in the scheme");
175
+ return {
176
+ description: "Blackhole, asked by the customer",
177
+ match: { large_community: community(scheme.blackhole, 0), prefix_set: prefixSet },
178
+ set: { blackhole: true },
179
+ action: "accept"
180
+ };
181
+ };
182
+ const actions = (neighborAs, options = {}) => [
183
+ ...scheme.blackhole !== undefined && !options.blackhole ? [
184
+ {
185
+ description: "Blackholed here, never announced",
186
+ match: { large_community: community(scheme.blackhole, 0) },
187
+ action: "reject"
188
+ }
189
+ ] : [],
190
+ ...scheme.do_not_announce !== undefined ? [
191
+ {
192
+ description: "Asked not to be announced anywhere",
193
+ match: { large_community: community(scheme.do_not_announce, 0) },
194
+ action: "reject"
195
+ },
196
+ {
197
+ description: `Asked not to be announced to AS${neighborAs}`,
198
+ match: { large_community: community(scheme.do_not_announce, neighborAs) },
199
+ action: "reject"
200
+ }
201
+ ] : [],
202
+ ...scheme.blackhole !== undefined && options.blackhole ? [
203
+ {
204
+ description: `Blackhole passed on to AS${neighborAs}`,
205
+ match: { large_community: community(scheme.blackhole, 0) },
206
+ set: {
207
+ remove_communities: ours.standard,
208
+ remove_large_communities: ours.large,
209
+ add_communities: [options.blackhole]
210
+ },
211
+ action: "accept"
212
+ }
213
+ ] : [],
214
+ ...PREPENDS.flatMap(([name, times]) => {
215
+ const fn = scheme.prepend?.[name];
216
+ if (fn === undefined)
217
+ return [];
218
+ return [
219
+ {
220
+ description: `Asked to prepend ${name.replace("_", " ")} to AS${neighborAs}`,
221
+ match: { large_community: community(fn, neighborAs) },
222
+ set: { prepend: times }
223
+ }
224
+ ];
225
+ }),
226
+ {
227
+ description: "Our communities stay inside our network",
228
+ set: { remove_communities: ours.standard, remove_large_communities: ours.large }
229
+ }
230
+ ];
231
+ const catalogue = [
232
+ ...Object.entries(scheme.learned_from?.classes ?? {}).map(([name, value]) => ({
233
+ community: community(scheme.learned_from.function, value),
234
+ description: `Learned from ${article(name)}`
235
+ })),
236
+ ...scheme.learned_at ? [
237
+ ...Object.entries(scheme.learned_at.sites ?? {}).map(([site, name]) => ({
238
+ community: community(scheme.learned_at.function, site),
239
+ description: `Learned in ${name}`
240
+ })),
241
+ { community: community(scheme.learned_at.function, "nnn"), description: "Learned at site $0" }
242
+ ] : [],
243
+ ...scheme.learned_from_as !== undefined ? [{ community: community(scheme.learned_from_as, "nnn"), description: "Learned from AS$0" }] : [],
244
+ ...scheme.do_not_announce !== undefined ? [
245
+ { community: community(scheme.do_not_announce, 0), description: "Do not announce to anyone" },
246
+ { community: community(scheme.do_not_announce, "nnn"), description: "Do not announce to AS$0" }
247
+ ] : [],
248
+ ...PREPENDS.flatMap(([name]) => {
249
+ const fn = scheme.prepend?.[name];
250
+ return fn === undefined ? [] : [{ community: community(fn, "nnn"), description: `Prepend ${name.replace("_", " ")} to AS$0` }];
251
+ }),
252
+ ...scheme.blackhole !== undefined ? [{ community: community(scheme.blackhole, 0), description: `Blackhole: dropped in AS${asn}, not announced further` }] : []
253
+ ];
254
+ return { community, tag, actions, blackhole, catalogue };
255
+ }
256
+ export {
257
+ vlansOf,
258
+ tunnelsOutsideOffered,
259
+ trustBoundary,
260
+ offered,
261
+ neighborPolicies,
262
+ exportsEndInReject,
263
+ communityScheme,
264
+ bgpSanity,
265
+ badTcpFlags,
266
+ antiSpoofing,
267
+ PRIVATE_RANGES,
268
+ MARTIANS
269
+ };
@@ -0,0 +1,58 @@
1
+ export declare const catalog: {
2
+ readonly routeros: {
3
+ readonly "CCR2216-1G-12XS-2XQ": {
4
+ readonly ports: {
5
+ readonly "100g": 2;
6
+ readonly "25g": 12;
7
+ readonly "1g": 1;
8
+ };
9
+ readonly spelling: {
10
+ readonly "100g": "qsfp28-{n}-1";
11
+ readonly "25g": "sfp28-{n}";
12
+ readonly "1g": "ether{n}";
13
+ };
14
+ };
15
+ };
16
+ readonly vrp: {
17
+ readonly "CE6855-48S6Q-HI": {
18
+ readonly ports: {
19
+ readonly "10g": 48;
20
+ readonly "40g": 6;
21
+ };
22
+ readonly spelling: {
23
+ readonly "10g": "10GE1/0/{n}";
24
+ readonly "40g": "40GE1/0/{n}";
25
+ };
26
+ };
27
+ };
28
+ readonly "raisecom-ros": {
29
+ readonly "RAX721-C-6C48": {
30
+ readonly ports: {
31
+ readonly "100g": 6;
32
+ readonly "25g": 24;
33
+ readonly "10g": 24;
34
+ };
35
+ readonly spelling: {
36
+ readonly "100g": "hundredgige 1/1/{n}";
37
+ readonly "25g": "twenty-fivegige 1/2/{n}";
38
+ readonly "10g": "tengigabitethernet 1/3/{n}";
39
+ };
40
+ };
41
+ };
42
+ };
43
+ export type Catalog = typeof catalog;
44
+ export type PlatformName = keyof Catalog;
45
+ export type ModelName<Pl extends PlatformName> = keyof Catalog[Pl] & string;
46
+ type Upto<N extends number, A extends unknown[] = [], R = never> = A["length"] extends N ? R : Upto<N, [...A, 0], R | `${[...A, 0]["length"]}`>;
47
+ type PortsOf<Pl extends PlatformName, M extends ModelName<Pl>> = Catalog[Pl][M] extends {
48
+ ports: infer P;
49
+ } ? P : never;
50
+ /** `10g-1` to `10g-48` on a model with 48 10G ports. */
51
+ export type PortName<Pl extends PlatformName, M extends ModelName<Pl>> = {
52
+ [S in keyof PortsOf<Pl, M>]: `${S & string}-${Upto<PortsOf<Pl, M>[S] & number>}`;
53
+ }[keyof PortsOf<Pl, M>];
54
+ /** Every port a model has, in order. */
55
+ export declare function portsOf(platform: string, model: string): string[];
56
+ /** The platform's own name for a port. */
57
+ export declare function spell(platform: string, model: string, port: string): string;
58
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { Device } from "../../schema";
2
+ /** Each LAG's number: its own `id`, or its position among the device's LAGs. */
3
+ export declare function lagIds(device: Device): Map<string, number>;
4
+ /** The IPv4 networks of a port's or an interface's addresses. */
5
+ export declare function ipv4Networks(device: Device, iface: string): string[];
@@ -0,0 +1,3 @@
1
+ import type { DeviceAdapter } from "./types";
2
+ /** Every platform the framework can render and apply, by name. */
3
+ export declare const adapters: Record<string, DeviceAdapter>;
@@ -0,0 +1,56 @@
1
+ import type { Plan } from "./types";
2
+ export type Dialect = {
3
+ /** A line at the margin that opens a block, for platforms that do not indent a block's lines. */
4
+ block: RegExp;
5
+ /** Whether a block's lines are indented under it. When they are, any line followed by deeper lines opens a block. */
6
+ indented: boolean;
7
+ /** A line at the margin that closes every open block. */
8
+ terminator: string;
9
+ /** Lines that describe the device rather than configure it, or that the platform prints whatever is set. */
10
+ ignore: (line: string) => boolean;
11
+ /** The command that removes a line. */
12
+ undo: (line: string) => string;
13
+ /** Leaves one level of block. */
14
+ leave: string;
15
+ /** Lines sent before the undos of their block, because the platform refuses an undo until they are in place. */
16
+ first?: RegExp;
17
+ /** A line in the form both sides compare in, for example a secret the device prints as a cipher. */
18
+ canon?: (line: string) => string;
19
+ /** Where a block is sent among the others, lowest first, so that nothing is referenced before it exists. In the order they differ when absent. */
20
+ order?: (path: string[]) => number;
21
+ };
22
+ /** One line, with the blocks it sits in, outermost first. */
23
+ export type Entry = {
24
+ path: string[];
25
+ line: string;
26
+ };
27
+ export declare function entries(text: string, dialect: Dialect): Entry[];
28
+ /** The part of a line that names what it sets: `rule 5`, a free-text keyword, or the line up to its value. Two lines with one head are one setting with two values. */
29
+ export declare function head(line: string): string;
30
+ /** What changes inside one block. */
31
+ export type Group = {
32
+ path: string[];
33
+ remove: string[];
34
+ add: string[];
35
+ };
36
+ /**
37
+ * What differs, by block.
38
+ *
39
+ * A device line replaced by a desired line with the same head is not undone; sending the new one is enough. A block the render does not hold goes with its header, and its lines with it.
40
+ */
41
+ export declare function compare(desired: string, current: string, dialect: Dialect): Group[];
42
+ /**
43
+ * Replace a changed VLAN list line with only what changed.
44
+ *
45
+ * A platform keeps its VLANs as one line, such as `vlan batch 10 20 to 30`. Undoing the old line would delete VLANs still in use, so the plan adds the new IDs and removes the gone ones instead.
46
+ */
47
+ export declare function vlanListChange(groups: Group[], prefix: string, parse: (line: string) => number[], format: (ids: number[]) => string[], removal: (ids: number[]) => string[]): void;
48
+ /** Commands a platform needs around one group, such as releasing a binding before a change and restoring it after. */
49
+ export type Around = (group: Group) => {
50
+ before: string[];
51
+ after: string[];
52
+ };
53
+ /** The steps for the groups: enter each block of the path, undo, add, then leave each level. */
54
+ export declare function steps(groups: Group[], dialect: Dialect, around?: Around): Plan["steps"];
55
+ /** A device prints stored credentials as ciphers. They never leave the read. */
56
+ export declare const scrub: (text: string) => string;
@@ -0,0 +1,23 @@
1
+ import type { ClientChannel } from "ssh2";
2
+ import type { Device } from "../../../schema";
3
+ import { type Dialect } from "../lines";
4
+ import type { DeviceAdapter, Plan } from "../types";
5
+ export declare const dialect: Dialect;
6
+ /** The access-list that guards every management service. */
7
+ export declare const MANAGEMENT_ACL = 1000;
8
+ /** `99,200-201,999`, the platform's compression of a VLAN list. */
9
+ export declare function vlanList(ids: number[]): string;
10
+ export declare function render(device: Device): string;
11
+ export type Session = {
12
+ channel: ClientChannel;
13
+ close: () => void;
14
+ };
15
+ /** An interactive shell, paging off. A session that does not answer as ROS is refused, so nothing meant for it can reach another shell. */
16
+ export declare function open(device: Device): Promise<Session>;
17
+ /** What the device runs, credentials scrubbed. */
18
+ export declare function read(device: Device): Promise<string>;
19
+ export declare function unsupported(device: Device): string[];
20
+ export declare function apply(device: Device, _network: readonly Device[], plan: Plan): Promise<void>;
21
+ /** The VLAN IDs of a `create vlan 10,20-30 active` line. */
22
+ export declare function createdIds(line: string): number[];
23
+ export declare const raisecom: DeviceAdapter;
@@ -0,0 +1,14 @@
1
+ export declare const models: {
2
+ readonly "RAX721-C-6C48": {
3
+ readonly ports: {
4
+ readonly "100g": 6;
5
+ readonly "25g": 24;
6
+ readonly "10g": 24;
7
+ };
8
+ readonly spelling: {
9
+ readonly "100g": "hundredgige 1/1/{n}";
10
+ readonly "25g": "twenty-fivegige 1/2/{n}";
11
+ readonly "10g": "tengigabitethernet 1/3/{n}";
12
+ };
13
+ };
14
+ };
@@ -0,0 +1,39 @@
1
+ export type Command = {
2
+ /** The menu the command belongs to, such as `/ip address`. */
3
+ menu: string;
4
+ verb: "add" | "set";
5
+ /** What a `set` addresses: `[ find default-name=ether1 ]`, `ftp`, `1`, or nothing for a menu of one. */
6
+ selector: string;
7
+ /** Words without a value, such as `blackhole`. */
8
+ flags: string[];
9
+ /** Properties, unquoted and unescaped. */
10
+ attrs: Record<string, string>;
11
+ };
12
+ export type Parsed = {
13
+ commands: Command[];
14
+ /** Address lists whose entries are filled by `refresh`, not by the render. */
15
+ generated: Set<string>;
16
+ };
17
+ export declare function parse(text: string): Parsed;
18
+ /**
19
+ * Values the platform holds when nothing was set, per menu, and under `*` for every menu.
20
+ *
21
+ * A property at its default is dropped from both sides of a comparison, and this is the value a property the config does not state is reset to. A property the device holds that is not here cannot be reset, which the plan reports as a problem.
22
+ */
23
+ export declare const DEFAULTS: Record<string, Record<string, string>>;
24
+ /** The default of one property of a command, or undefined when it is not known. */
25
+ export declare function defaultOf(command: Command, property: string): string | undefined;
26
+ /** A property the device stops printing once another returns to its default. Resetting that other one clears it, so it is never reset on its own. */
27
+ export declare const CLEARED_WITH: Record<string, Record<string, string>>;
28
+ /** Properties whose value cannot be read back from the device. They are compared only by the presence of what holds them. */
29
+ export declare const SECRET_KEYS: Set<string>;
30
+ /** How the renderer writes a secret. */
31
+ export declare const isSecretMarker: (value: string) => boolean;
32
+ /** A command's properties without defaults or secrets, in the form both sides compare in. */
33
+ export declare function normal(command: Command): Record<string, string>;
34
+ /** What identifies an `add` entry, so that the same entry is found on both sides. */
35
+ export declare function identity(command: Command): Record<string, string>;
36
+ /** Menus whose entries are an ordered list without names. They are rewritten whole, in the order the render gives. */
37
+ export declare const ORDERED: Set<string>;
38
+ /** One command as a string that is equal for equal commands. */
39
+ export declare function canonical(command: Command): string;
@@ -0,0 +1,17 @@
1
+ import type { Client } from "ssh2";
2
+ import type { Device, Family } from "../../../schema";
3
+ import type { ApplyOptions, DeviceAdapter, Plan } from "../types";
4
+ /** The major version this adapter renders for. Version 6 has a different BGP and filter language altogether. */
5
+ export declare const SUPPORTED_MAJOR = 7;
6
+ export declare function login(device: Device): Promise<Client>;
7
+ /** Refuse a device on a version this adapter does not render for, before anything is read or sent. */
8
+ export declare function checkVersion(client: Client, device: Device): Promise<void>;
9
+ /**
10
+ * What the device runs: its export, its users, and its certificates. Secrets are not part of an export.
11
+ */
12
+ export declare function read(device: Device): Promise<string>;
13
+ export declare function unsupported(device: Device): string[];
14
+ export declare function apply(device: Device, _network: readonly Device[], changes: Plan, options: ApplyOptions): Promise<void>;
15
+ /** Replace a list's entries in batches, so each command stays a sane length. */
16
+ export declare function fill(device: Device, _network: readonly Device[], set: string, family: Family, prefixes: string[]): Promise<void>;
17
+ export declare const routeros: DeviceAdapter;
@@ -0,0 +1,14 @@
1
+ export declare const models: {
2
+ readonly "CCR2216-1G-12XS-2XQ": {
3
+ readonly ports: {
4
+ readonly "100g": 2;
5
+ readonly "25g": 12;
6
+ readonly "1g": 1;
7
+ };
8
+ readonly spelling: {
9
+ readonly "100g": "qsfp28-{n}-1";
10
+ readonly "25g": "sfp28-{n}";
11
+ readonly "1g": "ether{n}";
12
+ };
13
+ };
14
+ };
@@ -0,0 +1,4 @@
1
+ import type { ApplyOptions, Plan } from "../types";
2
+ /** A plan line the apply handles itself: upload a certificate and its key, and import them. */
3
+ export declare const INSTALL_CERTIFICATE = "#install-certificate ";
4
+ export declare function plan(desired: string, current: string, options: Pick<ApplyOptions, "secrets">): Plan;
@@ -0,0 +1,96 @@
1
+ import type { Device, Family, Interface, Port } from "../../../schema";
2
+ /** A property value as a command takes it. `undefined` and `false` leave the property out. */
3
+ export type Value = string | number | undefined | false;
4
+ /** Collects commands under their menu. */
5
+ export declare class Output {
6
+ private readonly menus;
7
+ /** Add a command to a menu. */
8
+ add(menu: string, command: string): void;
9
+ /** The whole configuration as text. */
10
+ text(): string;
11
+ }
12
+ /** Quote a value when it holds anything but plain characters. */
13
+ export declare function quote(value: string): string;
14
+ /**
15
+ * A command: a verb such as `add` or `set [ find name=x ]`, then `property=value` for every property that has a value.
16
+ *
17
+ * Properties are written in the order given.
18
+ */
19
+ export declare function command(verb: string, properties: Record<string, Value>): string;
20
+ /** `yes` or `no`. */
21
+ export declare const yesNo: (on: boolean | undefined) => string;
22
+ /** `V4` or `V6`, the suffix of a chain rendered for one family. */
23
+ export declare const familySuffix: (family: Family) => string;
24
+ /** One port or a list of ports, as `53,123`. */
25
+ export declare function portList(ports: number | readonly number[] | undefined): string | undefined;
26
+ /** Seconds as the platform writes a duration: `3600` becomes `1h`, `90` becomes `1m30s`. */
27
+ export declare function duration(seconds: number): string;
28
+ /** A rate as the switch chip takes it: `2G` becomes `2.0Gbps`. */
29
+ export declare function bitrate(rate: string): string;
30
+ /** An IPv4 prefix with a dotted mask, as the switch chip takes it: `10.0.0.0/8` becomes `10.0.0.0/255.0.0.0`. */
31
+ export declare function dottedMask(cidr: string): string;
32
+ /** A community pattern with `*` as a regular expression the platform matches on: `64500:*:*` becomes `^64500:[0-9]+:[0-9]+$`. */
33
+ export declare function communityPattern(pattern: string): string;
34
+ /** The bridge every switched port joins. One per device. */
35
+ export declare const BRIDGE = "bridge";
36
+ export type AnyPort = Port<string, string, string, string>;
37
+ export type AnyInterface = Interface<string, string>;
38
+ export declare class Context {
39
+ readonly device: Device;
40
+ readonly ports: Record<string, AnyPort>;
41
+ readonly interfaces: Record<string, AnyInterface>;
42
+ constructor(device: Device);
43
+ /** What the platform calls a port or an interface. A port is spelled by the model; an interface keeps its config name. */
44
+ name(portOrInterface: string): string;
45
+ /** The number of a VLAN, by its config name. */
46
+ vlanId(vlan: string): number;
47
+ /** Ports and LAGs that are layer 2 members of a VLAN, with their settings. A port in a LAG is represented by the LAG. */
48
+ switched(): [string, AnyPort | Extract<AnyInterface, {
49
+ type: "lag";
50
+ }>][];
51
+ /** VLAN interfaces, with their settings. */
52
+ vlanInterfaces(): [string, Extract<AnyInterface, {
53
+ type: "vlan";
54
+ }>][];
55
+ /** Every port and interface that holds addresses or other layer 3 settings. Ports are named as the platform spells them. */
56
+ routed(): {
57
+ name: string;
58
+ settings: AnyPort | AnyInterface;
59
+ }[];
60
+ }
61
+ export declare function renderInterfaces(out: Output, ctx: Context): void;
62
+ type Speed = NonNullable<AnyPort["speed"]>;
63
+ /** The platform's name for a fixed speed on a port, or undefined when it has none. */
64
+ export declare function mediumOf(platformPortName: string, speed: Speed): string | undefined;
65
+ /** The interface holding one VRRP group. */
66
+ export declare const vrrpName: (iface: string, id: number) => string;
67
+ /**
68
+ * Where LLDP runs, when it is limited to some ports. Undefined when it runs everywhere or nowhere, which the discovery settings say directly.
69
+ */
70
+ export declare function lldpMembers(ctx: Context): string[] | undefined;
71
+ /** Address lists holding the prefixes BGP originates. A session reads the one of its family. */
72
+ export declare const NETWORKS: Record<Family, string>;
73
+ export declare function renderAddressing(out: Output, ctx: Context): void;
74
+ export declare function renderFirewall(out: Output, ctx: Context): void;
75
+ /** The validator group every RPKI server joins. */
76
+ export declare const RPKI_GROUP = "rpki";
77
+ /** The chain a session without a policy uses, so that it learns or announces nothing. */
78
+ export declare const REJECT_ALL = "REJECT-ALL";
79
+ export declare function renderRouting(out: Output, ctx: Context): void;
80
+ /** Neutral service name to the platform's. `native` is the platform's own desktop client. */
81
+ export declare const SERVICES: {
82
+ readonly ftp: "ftp";
83
+ readonly ssh: "ssh";
84
+ readonly telnet: "telnet";
85
+ readonly http: "www";
86
+ readonly https: "www-ssl";
87
+ readonly api: "api";
88
+ readonly api_tls: "api-ssl";
89
+ readonly native: "winbox";
90
+ };
91
+ export declare function renderManagement(out: Output, ctx: Context): void;
92
+ /** SHA-256 of the certificate's DER encoding, lowercase hex, as the platform prints it. */
93
+ export declare function fingerprint(pem: string): string;
94
+ export declare function renderSystem(out: Output, ctx: Context): void;
95
+ export declare function render(device: Device): string;
96
+ export {};