pmcf 1.27.2 → 1.29.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.
@@ -4,18 +4,18 @@ import { writeFile, mkdir, copyFile, glob, chmod } from "node:fs/promises";
4
4
  import { join } from "node:path";
5
5
  import { Host } from "pmcf";
6
6
  import { writeLines, sectionLines } from "../src/utils.mjs";
7
- import { prepare, } from "../src/cmd.mjs";
7
+ import { prepare } from "../src/cmd.mjs";
8
8
 
9
- const { world, args, options } = prepare();
9
+ const { root, args, options } = prepare();
10
10
 
11
11
  const hostName = args[0];
12
12
 
13
- const host = await world.load(hostName,{ type: Host });
13
+ const host = await root.load(hostName, { type: Host });
14
14
 
15
15
  await generateNetworkDefs(host, options.output);
16
16
  await generateMachineInfo(host, options.output);
17
17
  await copySshKeys(host, options.output);
18
- await generateKnownHosts(world.hosts(), join(options.output, "root", ".ssh"));
18
+ await generateKnownHosts(root.hosts(), join(options.output, "root", ".ssh"));
19
19
 
20
20
  console.log("provides", "host", ...host.provides);
21
21
  console.log("depends", `location-${host.location.name}`, ...host.depends);
@@ -153,7 +153,10 @@ async function copySshKeys(host, dir) {
153
153
  for await (const file of glob("ssh_host_*", { cwd: host.directory })) {
154
154
  const destinationFileName = join(sshDir, file);
155
155
  await copyFile(join(host.directory, file), destinationFileName);
156
- await chmod(destinationFileName, destinationFileName.endsWith('.pub') ? 0o0644 : 0o0600);
156
+ await chmod(
157
+ destinationFileName,
158
+ destinationFileName.endsWith(".pub") ? 0o0644 : 0o0600
159
+ );
157
160
  }
158
161
  }
159
162
 
package/bin/pmcf-info CHANGED
@@ -1,15 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { prepare } from "../src/cmd.mjs";
3
- const { world, args } = prepare();
3
+ const { root, args } = prepare();
4
+
5
+ await root.loadAll();
4
6
 
5
7
  const objectName = args[0];
6
8
 
7
9
  if (objectName) {
8
- const object = await world.named(objectName);
10
+ const object = await root.named(objectName);
9
11
  console.log(object.toJSON());
10
12
  } else {
11
- for await (const location of world.locations()) {
13
+ for await (const location of root.locations()) {
12
14
  console.log(location.name);
13
- console.log(" ", (await location.service({ type: "dns"}))?.toString());
15
+ console.log(" ", (await location.service({ type: "dns" }))?.toString());
14
16
  }
15
17
  }
@@ -6,9 +6,11 @@ import { Location } from "pmcf";
6
6
  import { writeLines, sectionLines } from "../src/utils.mjs";
7
7
  import { prepare } from "../src/cmd.mjs";
8
8
 
9
- const { world, args, options } = prepare();
9
+ const { root, args, options } = prepare();
10
10
 
11
- const location = await world.load(args[0], { type: Location });
11
+ await root.loadAll();
12
+
13
+ const location = await root.load(args[0], { type: Location });
12
14
 
13
15
  await generateLocationDefs(location, options.output);
14
16
 
@@ -9,9 +9,11 @@ import {
9
9
  } from "../src/utils.mjs";
10
10
  import { prepare } from "../src/cmd.mjs";
11
11
 
12
- const { world, args, options } = prepare();
12
+ const { root, args, options } = prepare();
13
13
 
14
- const owner = await world.load(args[0]);
14
+ await root.loadAll();
15
+
16
+ const owner = await root.load(args[0]);
15
17
  const updates = [
16
18
  Math.ceil(Date.now() / 1000),
17
19
  36000,
@@ -42,7 +44,7 @@ async function generateNamedDefs(owner, targetDir) {
42
44
  const createRecord = (key, type, value) => {
43
45
  return {
44
46
  key,
45
- /*type,
47
+ /*type,
46
48
  value,*/
47
49
  toString: () =>
48
50
  `${key.padEnd(maxKeyLength, " ")} ${ttl} IN ${type.padEnd(
package/bin/pmcf-network CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  import { prepare } from "../src/cmd.mjs";
4
4
 
5
- const { world, args } = prepare();
5
+ const { root, args } = prepare();
6
6
 
7
- const location = await world.load(args[0]);
7
+ const location = await root.load(args[0]);
8
8
 
9
9
  function q(str) {
10
10
  return str.match(/^\w+$/) ? str : `"${str}"`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmcf",
3
- "version": "1.27.2",
3
+ "version": "1.29.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -43,7 +43,7 @@
43
43
  "pacc": "^3.1.9"
44
44
  },
45
45
  "devDependencies": {
46
- "@types/node": "^22.10.10",
46
+ "@types/node": "^22.12.0",
47
47
  "ava": "^6.2.0",
48
48
  "c8": "^10.1.3",
49
49
  "documentation": "^14.0.3",
package/src/base.mjs CHANGED
@@ -9,6 +9,14 @@ export class Base {
9
9
  static get typeName() {
10
10
  return "base";
11
11
  }
12
+
13
+ static get pluralTypeName() {
14
+ return this.typeName + "s";
15
+ }
16
+
17
+ static get nameLookupName() {
18
+ return this.typeName + "Named";
19
+ }
12
20
 
13
21
  static get typeFileName() {
14
22
  return this.typeName + ".json";
@@ -18,16 +26,14 @@ export class Base {
18
26
  return "**/" + this.typeFileName;
19
27
  }
20
28
 
21
- static async prepareData(world, data) {
29
+ static async prepareData(root, data) {
22
30
  return this;
23
31
  }
24
32
 
25
- static baseName(name) {
26
- if (!name) {
27
- return undefined;
33
+ static normalizeName(name) {
34
+ if (name !== undefined) {
35
+ return name.replace(/\/\w+\.json$/, "");
28
36
  }
29
-
30
- return name.replace(/\/\w+\.json$/, "");
31
37
  }
32
38
 
33
39
  constructor(owner, data) {
@@ -43,6 +49,7 @@ export class Base {
43
49
 
44
50
  withOwner(owner) {
45
51
  if (this.owner !== owner) {
52
+ // @ts-ignore
46
53
  return new this.constructor(owner, this);
47
54
  }
48
55
 
@@ -50,11 +57,12 @@ export class Base {
50
57
  }
51
58
 
52
59
  get typeName() {
60
+ // @ts-ignore
53
61
  return this.constructor.typeName;
54
62
  }
55
63
 
56
- get world() {
57
- return this.owner.world;
64
+ get root() {
65
+ return this.owner.root;
58
66
  }
59
67
 
60
68
  get location() {
@@ -83,7 +91,7 @@ export class Base {
83
91
  }
84
92
 
85
93
  get fullName() {
86
- return this.owner?.fullName
94
+ return this.owner && this.name
87
95
  ? join(this.owner.fullName, this.name)
88
96
  : this.name;
89
97
  }
package/src/cluster.mjs CHANGED
@@ -4,4 +4,9 @@ export class Cluster extends Owner {
4
4
  static get typeName() {
5
5
  return "cluster";
6
6
  }
7
+
8
+ constructor(owner, data) {
9
+ super(owner, data);
10
+ owner.addObject(this);
11
+ }
7
12
  }
package/src/cmd.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  import { parseArgs } from "node:util";
2
2
  import { argv, cwd, env } from "node:process";
3
- import { World } from "./model.mjs";
3
+ import { Root } from "./model.mjs";
4
4
 
5
5
  export function prepare() {
6
6
  const { values, positionals } = parseArgs({
7
7
  args: argv.slice(2),
8
8
  options: {
9
- world: {
9
+ root: {
10
10
  type: "string",
11
11
  short: "w",
12
12
  default: env.PMCF_WORLD || cwd()
@@ -20,7 +20,7 @@ export function prepare() {
20
20
  allowPositionals: true
21
21
  });
22
22
 
23
- const world = new World(values.world);
23
+ const root = new Root(values.root);
24
24
 
25
- return { world, options: values, args: positionals };
25
+ return { root, options: values, args: positionals };
26
26
  }
package/src/dns.mjs CHANGED
@@ -13,6 +13,7 @@ export class DNSService extends Base {
13
13
  constructor(owner, data) {
14
14
  super(owner, data);
15
15
  Object.assign(this, data);
16
+ owner.addObject(this);
16
17
  }
17
18
 
18
19
  async *services() {
@@ -21,7 +22,7 @@ export class DNSService extends Base {
21
22
  yield* this.owner.services(filter);
22
23
 
23
24
  for (const s of asArray(this.forwardsTo)) {
24
- const owner = await this.owner.world.load(s);
25
+ const owner = await this.owner.root.load(s);
25
26
  yield* owner.services(filter);
26
27
  }
27
28
  }
package/src/model.mjs CHANGED
@@ -7,104 +7,92 @@ import {
7
7
  normalizeIPAddress
8
8
  } from "./utils.mjs";
9
9
  import { Base } from "./base.mjs";
10
- import { Owner } from "./owner.mjs";
10
+ import { Owner, Network, Subnet } from "./owner.mjs";
11
11
  import { Service } from "./service.mjs";
12
12
  import { Cluster } from "./cluster.mjs";
13
13
  import { DNSService } from "./dns.mjs";
14
14
 
15
- export class World extends Owner {
15
+ export class Root extends Owner {
16
16
  static get types() {
17
17
  return _typesByName;
18
18
  }
19
19
 
20
20
  static get typeName() {
21
- return "world";
21
+ return "root";
22
22
  }
23
23
 
24
- #byName = new Map();
25
-
26
24
  constructor(directory) {
27
25
  super(undefined, { name: "" });
28
26
  this.directory = directory;
29
27
  this.addObject(this);
30
28
  }
31
29
 
32
- _traverse(...args) {
33
- if (super._traverse(...args)) {
34
- for (const object of this.#byName.values()) {
35
- object._traverse(...args);
36
- }
37
-
38
- return true;
39
- }
40
-
41
- return false;
42
- }
43
-
44
30
  get fullName() {
45
31
  return "";
46
32
  }
47
33
 
48
- get world() {
34
+ get root() {
49
35
  return this;
50
36
  }
51
37
 
52
38
  async load(name, options) {
53
- if (name === "") {
54
- return this;
55
- }
56
- const baseName = Base.baseName(name);
57
-
58
- let object = this.#byName.get(baseName);
59
-
60
- if (!object) {
61
- let path = baseName.split("/");
62
- path.pop();
63
-
64
- let data;
65
- let type = options?.type;
66
- if (type) {
67
- data = JSON.parse(
68
- await readFile(
69
- join(this.directory, baseName, type.typeFileName),
70
- "utf8"
71
- )
72
- );
73
- } else {
74
- for (type of _types) {
75
- try {
76
- data = JSON.parse(
77
- await readFile(
78
- join(this.directory, baseName, type.typeFileName),
79
- "utf8"
80
- )
81
- );
82
- break;
83
- } catch {}
84
- }
85
-
86
- if (!data) {
87
- return this.load(path.join("/"), options);
88
- }
89
- }
39
+ const fullName = Base.normalizeName(name);
40
+ let object = this.named(fullName);
41
+ if (object) {
42
+ return object;
43
+ }
90
44
 
91
- const owner = await this.load(path.join("/"));
45
+ //console.log("LOAD", fullName);
92
46
 
93
- const length = owner.fullName.length;
94
- const n = baseName[length] === "/" ? length + 1 : length;
95
- data.name = baseName.substring(n);
47
+ let path = fullName.split("/");
48
+ path.pop();
96
49
 
97
- type = await type.prepareData(this, data);
50
+ let data;
51
+ let type = options?.type;
52
+ if (type) {
53
+ data = JSON.parse(
54
+ await readFile(
55
+ join(this.directory, fullName, type.typeFileName),
56
+ "utf8"
57
+ )
58
+ );
59
+ } else {
60
+ for (type of _types) {
61
+ try {
62
+ data = JSON.parse(
63
+ await readFile(
64
+ join(this.directory, fullName, type.typeFileName),
65
+ "utf8"
66
+ )
67
+ );
68
+ break;
69
+ } catch {}
70
+ }
98
71
 
99
- object = new type(owner, data);
100
- this.addObject(object);
72
+ if (!data) {
73
+ return this.load(path.join("/"), options);
74
+ }
101
75
  }
102
76
 
77
+ const owner = await this.load(path.join("/"));
78
+
79
+ const length = owner.fullName.length;
80
+ const n = fullName[length] === "/" ? length + 1 : length;
81
+ data.name = fullName.substring(n);
82
+
83
+ type = await type.prepareData(this, data);
84
+
85
+ object = new type(owner, data);
86
+
87
+ this._addObject(type.typeName, fullName, object);
88
+
89
+ //console.log("FINISH LOAD", object.fullName);
90
+
103
91
  return object;
104
92
  }
105
93
 
106
94
  async loadAll() {
107
- for (let type of Object.values(World.types)) {
95
+ for (let type of Object.values(Root.types)) {
108
96
  for await (const name of glob(type.fileNameGlob, {
109
97
  cwd: this.directory
110
98
  })) {
@@ -114,41 +102,6 @@ export class World extends Owner {
114
102
 
115
103
  this.execFinalize();
116
104
  }
117
-
118
- addObject(object) {
119
- this.#byName.set(object.fullName, object);
120
- }
121
-
122
- async named(name) {
123
- await this.loadAll();
124
- return this.#byName.get(name);
125
- }
126
-
127
- async *locations() {
128
- await this.loadAll();
129
-
130
- for (const object of this.#byName.values()) {
131
- if (object instanceof Location) {
132
- yield object;
133
- }
134
- }
135
- }
136
-
137
- async *hosts() {
138
- await this.loadAll();
139
-
140
- for (const object of this.#byName.values()) {
141
- if (object instanceof Host) {
142
- yield object;
143
- }
144
- }
145
- }
146
-
147
- async *domains() {
148
- for await (const location of this.locations()) {
149
- yield location.domain;
150
- }
151
- }
152
105
  }
153
106
 
154
107
  export class Location extends Owner {
@@ -159,82 +112,6 @@ export class Location extends Owner {
159
112
  get location() {
160
113
  return this;
161
114
  }
162
-
163
- async *hosts() {
164
- for await (const host of this.owner.hosts()) {
165
- if (host.location === this) {
166
- yield host;
167
- }
168
- }
169
- }
170
- }
171
-
172
- export class Network extends Owner {
173
- kind;
174
- scope;
175
- metric;
176
- ipv4;
177
- subnet;
178
- bridge;
179
-
180
- static get typeName() {
181
- return "network";
182
- }
183
-
184
- constructor(owner, data) {
185
- super(owner, data);
186
-
187
- let bridge;
188
- if (data.bridge) {
189
- bridge = data.bridge;
190
- delete data.bridge;
191
- }
192
-
193
- Object.assign(this, data);
194
-
195
- const subnetAddress = this.subnetAddress;
196
-
197
- if (subnetAddress) {
198
- let subnet = owner.subnet(subnetAddress);
199
- if (!subnet) {
200
- subnet = new Subnet(owner, { name: subnetAddress });
201
- }
202
-
203
- this.subnet = subnet;
204
- subnet.networks.add(this);
205
- }
206
-
207
- owner.addNetwork(this);
208
-
209
- this.bridge = owner.addBridge(this, bridge);
210
- }
211
-
212
- get netmask() {
213
- const m = this.ipv4?.match(/\/(\d+)$/);
214
- if (m) {
215
- return parseInt(m[1]);
216
- }
217
- }
218
-
219
- get subnetAddress() {
220
- if (this.ipv4) {
221
- const [addr, bits] = this.ipv4.split(/\//);
222
- const parts = addr.split(/\./);
223
- return parts.slice(0, bits / 8).join(".");
224
- }
225
- }
226
-
227
- get propertyNames() {
228
- return [
229
- ...super.propertyNames,
230
- "kind",
231
- "ipv4",
232
- "netmask",
233
- "scope",
234
- "metric",
235
- "bridge"
236
- ];
237
- }
238
115
  }
239
116
 
240
117
  export class Host extends Base {
@@ -256,14 +133,14 @@ export class Host extends Base {
256
133
  return "host";
257
134
  }
258
135
 
259
- static async prepareData(world, data) {
136
+ static async prepareData(root, data) {
260
137
  if (data.location) {
261
- data.location = await world.load(data.location, { type: Location });
138
+ data.location = await root.load(data.location, { type: Location });
262
139
  }
263
140
 
264
141
  if (data.extends) {
265
142
  data.extends = await Promise.all(
266
- asArray(data.extends).map(e => world.load(e, { type: Host }))
143
+ asArray(data.extends).map(e => root.load(e, { type: Host }))
267
144
  );
268
145
  }
269
146
 
@@ -335,7 +212,7 @@ export class Host extends Base {
335
212
  new NetworkInterface(this, iface);
336
213
  }
337
214
 
338
- owner.addHost(this);
215
+ owner.addObject(this);
339
216
  }
340
217
 
341
218
  _traverse(...args) {
@@ -452,7 +329,7 @@ export class Host extends Base {
452
329
  this.networkInterfaces[networkInterface.name] = networkInterface;
453
330
 
454
331
  if (networkInterface.network) {
455
- networkInterface.network.addHost(this);
332
+ networkInterface.network.addObject(this);
456
333
  }
457
334
  }
458
335
 
@@ -465,7 +342,9 @@ export class Host extends Base {
465
342
  }
466
343
 
467
344
  get ipAddresses() {
468
- return [...this.networkAddresses()].map(na => normalizeIPAddress(na.address));
345
+ return [...this.networkAddresses()].map(na =>
346
+ normalizeIPAddress(na.address)
347
+ );
469
348
  }
470
349
 
471
350
  get ipAddress() {
@@ -655,25 +534,5 @@ export class NetworkInterface extends Base {
655
534
  }
656
535
  }
657
536
 
658
- export class Subnet extends Base {
659
- networks = new Set();
660
-
661
- static get typeName() {
662
- return "subnet";
663
- }
664
-
665
- constructor(owner, data) {
666
- super(owner, data);
667
-
668
- Object.assign(this, data);
669
-
670
- owner.addSubnet(this);
671
- }
672
-
673
- get address() {
674
- return this.name;
675
- }
676
- }
677
-
678
537
  const _types = [Location, Network, Subnet, Host, Cluster, Service, DNSService];
679
538
  const _typesByName = Object.fromEntries(_types.map(t => [t.typeName, t]));
package/src/owner.mjs CHANGED
@@ -1,12 +1,9 @@
1
1
  import { asArray, bridgeToJSON } from "./utils.mjs";
2
-
3
2
  import { Base } from "./base.mjs";
4
3
  import { DNSService } from "./dns.mjs";
5
4
 
6
5
  export class Owner extends Base {
7
- #hosts = new Map();
8
- #networks = new Map();
9
- #subnets = new Map();
6
+ #membersByType = new Map();
10
7
  #bridges = new Set();
11
8
  #dns;
12
9
  #administratorEmail;
@@ -34,16 +31,16 @@ export class Owner extends Base {
34
31
  }
35
32
  }
36
33
  Object.assign(this, data);
34
+
35
+ owner?.addObject(this);
37
36
  }
38
37
 
39
38
  _traverse(...args) {
40
39
  if (super._traverse(...args)) {
41
- for (const network of this.#networks.values()) {
42
- network._traverse(...args);
43
- }
44
-
45
- for (const host of this.#hosts.values()) {
46
- host._traverse(...args);
40
+ for (const typeSlot of this.#membersByType.values()) {
41
+ for (const object of typeSlot.values()) {
42
+ object._traverse(...args);
43
+ }
47
44
  }
48
45
 
49
46
  return true;
@@ -56,19 +53,44 @@ export class Owner extends Base {
56
53
  return this.#dns;
57
54
  }
58
55
 
59
- async *hosts() {
60
- for (const host of this.#hosts.values()) {
61
- yield host;
56
+ named(name) {
57
+ //console.log("NAMED", this.#membersByType.keys());
58
+ for (const slot of this.#membersByType.values()) {
59
+ const object = slot.get(name);
60
+ if (object) {
61
+ return object;
62
+ }
62
63
  }
63
64
  }
64
65
 
65
- addObject(object) {
66
- this.world.addObject(object);
66
+ typeNamed(typeName, name) {
67
+ const typeSlot = this.#membersByType.get(typeName);
68
+ return typeSlot?.get(name);
67
69
  }
68
70
 
69
- addHost(host) {
70
- this.#hosts.set(host.name, host);
71
- this.addObject(host);
71
+ *typeList(typeName) {
72
+ const typeSlot = this.#membersByType.get(typeName);
73
+ if (typeSlot) {
74
+ for (const object of typeSlot.values()) {
75
+ yield object;
76
+ }
77
+ }
78
+ }
79
+
80
+ _addObject(typeName, fullName, object) {
81
+ let typeSlot = this.#membersByType.get(typeName);
82
+ if (!typeSlot) {
83
+ typeSlot = new Map();
84
+ this.#membersByType.set(typeName, typeSlot);
85
+ }
86
+ typeSlot.set(fullName, object);
87
+
88
+ //console.log(this.toString(),"ADD", typeName, fullName, object.name, this.named(fullName)?.toString());
89
+ }
90
+
91
+ addObject(object) {
92
+ this.owner?.addObject(object);
93
+ this._addObject(object.typeName, object.fullName, object);
72
94
  }
73
95
 
74
96
  async service(filter) {
@@ -90,18 +112,44 @@ export class Owner extends Base {
90
112
  }
91
113
  }
92
114
 
115
+ locationNamed(name) {
116
+ return this.typeNamed("location", name);
117
+ }
118
+
119
+ locations() {
120
+ return this.typeList("location");
121
+ }
122
+
123
+ hostNamed(name) {
124
+ return this.typeNamed("host", name);
125
+ }
126
+
127
+ hosts() {
128
+ return this.typeList("host");
129
+ }
130
+
93
131
  networkNamed(name) {
94
- return this.#networks.get(name);
132
+ return this.typeNamed("network", name);
95
133
  }
96
134
 
97
- async *networks() {
98
- for (const network of this.#networks.values()) {
99
- yield network;
100
- }
135
+ networks() {
136
+ return this.typeList("network");
137
+ }
138
+
139
+ subnetNamed(name) {
140
+ return this.typeNamed("subnet", name);
141
+ }
142
+
143
+ subnets() {
144
+ return this.typeList("subnet");
145
+ }
146
+
147
+ clusterNamed(name) {
148
+ return this.typeNamed("cluster", name);
101
149
  }
102
150
 
103
- addNetwork(network) {
104
- this.#networks.set(network.fullName, network);
151
+ clusters() {
152
+ return this.typeList("cluster");
105
153
  }
106
154
 
107
155
  addBridge(network, destinationNetworks) {
@@ -167,33 +215,115 @@ export class Owner extends Base {
167
215
  }
168
216
  }
169
217
 
170
- addSubnet(subnet) {
171
- this.#subnets.set(subnet.name, subnet);
218
+ get administratorEmail() {
219
+ return this.#administratorEmail || "admin@" + this.domain;
172
220
  }
173
221
 
174
- subnet(name) {
175
- return this.#subnets.get(name);
222
+ *domains() {
223
+ for (const location of this.locations()) {
224
+ yield location.domain;
225
+ }
176
226
  }
177
227
 
178
- subnets() {
179
- return this.#subnets.values();
228
+ get propertyNames() {
229
+ return [...super.propertyNames, "domain", "administratorEmail", "dns"];
180
230
  }
181
231
 
182
- get administratorEmail() {
183
- return this.#administratorEmail || "admin@" + this.domain;
232
+ toJSON() {
233
+ const json = super.toJSON();
234
+
235
+ for (const [typeName, slot] of this.#membersByType) {
236
+ json[typeName] = [...slot.keys()].sort();
237
+ }
238
+
239
+ return json;
240
+ }
241
+ }
242
+
243
+ export class Network extends Owner {
244
+ kind;
245
+ scope;
246
+ metric;
247
+ ipv4;
248
+ subnet;
249
+ bridge;
250
+
251
+ static get typeName() {
252
+ return "network";
253
+ }
254
+
255
+ constructor(owner, data) {
256
+ super(owner, data);
257
+
258
+ let bridge;
259
+ if (data.bridge) {
260
+ bridge = data.bridge;
261
+ delete data.bridge;
262
+ }
263
+
264
+ Object.assign(this, data);
265
+
266
+ const subnetAddress = this.subnetAddress;
267
+
268
+ if (subnetAddress) {
269
+ let subnet = owner.subnetNamed(subnetAddress);
270
+ if (!subnet) {
271
+ subnet = new Subnet(owner, { name: subnetAddress });
272
+ }
273
+
274
+ this.subnet = subnet;
275
+ subnet.networks.add(this);
276
+ }
277
+
278
+ owner.addObject(this);
279
+
280
+ this.bridge = owner.addBridge(this, bridge);
281
+ }
282
+
283
+ get netmask() {
284
+ const m = this.ipv4?.match(/\/(\d+)$/);
285
+ if (m) {
286
+ return parseInt(m[1]);
287
+ }
288
+ }
289
+
290
+ get subnetAddress() {
291
+ if (this.ipv4) {
292
+ const [addr, bits] = this.ipv4.split(/\//);
293
+ const parts = addr.split(/\./);
294
+ return parts.slice(0, bits / 8).join(".");
295
+ }
184
296
  }
185
297
 
186
298
  get propertyNames() {
187
- return [...super.propertyNames, "domain", "administratorEmail", "dns"];
299
+ return [
300
+ ...super.propertyNames,
301
+ "kind",
302
+ "ipv4",
303
+ "netmask",
304
+ "scope",
305
+ "metric",
306
+ "bridge"
307
+ ];
188
308
  }
309
+ }
189
310
 
190
- toJSON() {
191
- return {
192
- ...super.toJSON(),
193
- networks: [...this.#networks.keys()].sort(),
194
- subnets: [...this.#subnets.keys()].sort(),
195
- bridges: [...this.#bridges].map(b => bridgeToJSON(b)),
196
- hosts: [...this.#hosts.keys()].sort()
197
- };
311
+ export class Subnet extends Base {
312
+ networks = new Set();
313
+
314
+ static get typeName() {
315
+ return "subnet";
316
+ }
317
+
318
+ constructor(owner, data) {
319
+ super(owner, data);
320
+
321
+ Object.assign(this, data);
322
+
323
+ owner.addObject(this);
324
+ }
325
+
326
+ get address() {
327
+ return this.name;
198
328
  }
199
329
  }
package/src/service.mjs CHANGED
@@ -71,6 +71,7 @@ export class Service extends Base {
71
71
  if (this.#ipAddresses) {
72
72
  data.ipAddresses = this.#ipAddresses;
73
73
  }
74
+ // @ts-ignore
74
75
  return new this.constructor(owner, data);
75
76
  }
76
77
 
package/types/base.d.mts CHANGED
@@ -1,17 +1,19 @@
1
1
  export function extractFrom(object: any, propertyNames: any): {};
2
2
  export class Base {
3
3
  static get typeName(): string;
4
+ static get pluralTypeName(): string;
5
+ static get nameLookupName(): string;
4
6
  static get typeFileName(): string;
5
7
  static get fileNameGlob(): string;
6
- static prepareData(world: any, data: any): Promise<typeof Base>;
7
- static baseName(name: any): any;
8
+ static prepareData(root: any, data: any): Promise<typeof Base>;
9
+ static normalizeName(name: any): any;
8
10
  constructor(owner: any, data: any);
9
11
  owner: any;
10
12
  name: any;
11
13
  description: any;
12
14
  withOwner(owner: any): any;
13
15
  get typeName(): any;
14
- get world(): any;
16
+ get root(): any;
15
17
  get location(): any;
16
18
  get host(): any;
17
19
  get network(): any;
package/types/cmd.d.mts CHANGED
@@ -1,9 +1,9 @@
1
1
  export function prepare(): {
2
- world: World;
2
+ root: Root;
3
3
  options: {
4
- world: string;
4
+ root: string;
5
5
  output: string;
6
6
  };
7
7
  args: string[];
8
8
  };
9
- import { World } from "./model.mjs";
9
+ import { Root } from "./model.mjs";
package/types/model.d.mts CHANGED
@@ -1,33 +1,18 @@
1
- export class World extends Owner {
1
+ export class Root extends Owner {
2
2
  static get types(): {
3
- [k: string]: typeof DNSService | typeof Cluster | typeof Service | typeof Host | typeof Network | typeof Subnet;
3
+ [k: string]: typeof DNSService | typeof Subnet | typeof Cluster | typeof Service | typeof Host;
4
4
  };
5
5
  constructor(directory: any);
6
6
  get fullName(): string;
7
- get world(): this;
7
+ get root(): this;
8
8
  load(name: any, options: any): any;
9
9
  loadAll(): Promise<void>;
10
- named(name: any): Promise<any>;
11
- locations(): AsyncGenerator<Location, void, unknown>;
12
- hosts(): AsyncGenerator<Host, void, unknown>;
13
- domains(): AsyncGenerator<any, void, unknown>;
14
- #private;
15
10
  }
16
11
  export class Location extends Owner {
17
12
  get location(): this;
18
13
  }
19
- export class Network extends Owner {
20
- kind: any;
21
- scope: any;
22
- metric: any;
23
- ipv4: any;
24
- subnet: any;
25
- bridge: any;
26
- get netmask(): number;
27
- get subnetAddress(): any;
28
- }
29
14
  export class Host extends Base {
30
- static prepareData(world: any, data: any): Promise<typeof Host>;
15
+ static prepareData(root: any, data: any): Promise<typeof Host>;
31
16
  networkInterfaces: {};
32
17
  postinstall: any[];
33
18
  _traverse(...args: any[]): boolean;
@@ -81,12 +66,9 @@ export class NetworkInterface extends Base {
81
66
  get kind(): any;
82
67
  #private;
83
68
  }
84
- export class Subnet extends Base {
85
- networks: Set<any>;
86
- get address(): any;
87
- }
88
69
  import { Owner } from "./owner.mjs";
89
70
  import { DNSService } from "./dns.mjs";
71
+ import { Subnet } from "./owner.mjs";
90
72
  import { Cluster } from "./cluster.mjs";
91
73
  import { Service } from "./service.mjs";
92
74
  import { Base } from "./base.mjs";
package/types/owner.d.mts CHANGED
@@ -5,27 +5,42 @@ export class Owner extends Base {
5
5
  };
6
6
  _traverse(...args: any[]): boolean;
7
7
  get dns(): DNSService;
8
- hosts(): AsyncGenerator<any, void, unknown>;
8
+ named(name: any): any;
9
+ typeNamed(typeName: any, name: any): any;
10
+ typeList(typeName: any): Generator<any, void, unknown>;
11
+ _addObject(typeName: any, fullName: any, object: any): void;
9
12
  addObject(object: any): void;
10
- addHost(host: any): void;
11
13
  service(filter: any): Promise<any>;
12
14
  services(filter: any): AsyncGenerator<any, void, unknown>;
15
+ locationNamed(name: any): any;
16
+ locations(): Generator<any, void, unknown>;
17
+ hostNamed(name: any): any;
18
+ hosts(): Generator<any, void, unknown>;
13
19
  networkNamed(name: any): any;
14
- networks(): AsyncGenerator<any, void, unknown>;
15
- addNetwork(network: any): void;
20
+ networks(): Generator<any, void, unknown>;
21
+ subnetNamed(name: any): any;
22
+ subnets(): Generator<any, void, unknown>;
23
+ clusterNamed(name: any): any;
24
+ clusters(): Generator<any, void, unknown>;
16
25
  addBridge(network: any, destinationNetworks: any): any;
17
26
  _resolveBridges(): void;
18
27
  networkAddresses(): AsyncGenerator<any, void, unknown>;
19
- addSubnet(subnet: any): void;
20
- subnet(name: any): any;
21
- subnets(): MapIterator<any>;
22
- toJSON(): {
23
- networks: any[];
24
- subnets: any[];
25
- bridges: any[][];
26
- hosts: any[];
27
- };
28
+ domains(): Generator<any, void, unknown>;
28
29
  #private;
29
30
  }
31
+ export class Network extends Owner {
32
+ kind: any;
33
+ scope: any;
34
+ metric: any;
35
+ ipv4: any;
36
+ subnet: any;
37
+ bridge: any;
38
+ get netmask(): number;
39
+ get subnetAddress(): any;
40
+ }
41
+ export class Subnet extends Base {
42
+ networks: Set<any>;
43
+ get address(): any;
44
+ }
30
45
  import { Base } from "./base.mjs";
31
46
  import { DNSService } from "./dns.mjs";