pmcf 4.30.8 → 5.0.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 (41) hide show
  1. package/bin/pmcf-diagram +1 -1
  2. package/package.json +7 -7
  3. package/src/base.mjs +81 -182
  4. package/src/cluster.mjs +22 -40
  5. package/src/{network-support.mjs → common-attributes.mjs} +66 -5
  6. package/src/extra-source-service.mjs +8 -7
  7. package/src/host.mjs +50 -53
  8. package/src/initialization-context.mjs +110 -216
  9. package/src/module.mjs +1 -2
  10. package/src/network-address.mjs +2 -2
  11. package/src/network-interfaces/ethernet.mjs +7 -15
  12. package/src/network-interfaces/loopback.mjs +1 -8
  13. package/src/network-interfaces/network-interface.mjs +28 -40
  14. package/src/network-interfaces/skeleton.mjs +14 -9
  15. package/src/network-interfaces/tun.mjs +0 -5
  16. package/src/network-interfaces/wireguard.mjs +0 -4
  17. package/src/network-interfaces/wlan.mjs +7 -18
  18. package/src/network.mjs +39 -43
  19. package/src/owner.mjs +120 -236
  20. package/src/root.mjs +13 -7
  21. package/src/service-owner.mjs +7 -26
  22. package/src/service.mjs +32 -19
  23. package/src/services/alpm.mjs +10 -6
  24. package/src/services/bind.mjs +73 -79
  25. package/src/services/chrony.mjs +3 -4
  26. package/src/services/headscale.mjs +0 -1
  27. package/src/services/influxdb.mjs +2 -2
  28. package/src/services/kea.mjs +16 -6
  29. package/src/services/mosquitto.mjs +4 -1
  30. package/src/services/openldap.mjs +4 -5
  31. package/src/services/postfix.mjs +0 -2
  32. package/src/services/systemd-journal-remote.mjs +12 -2
  33. package/src/services/systemd-journal-upload.mjs +8 -2
  34. package/src/services/systemd-journald.mjs +23 -1
  35. package/src/services/systemd-resolved.mjs +41 -19
  36. package/src/services/systemd-timesyncd.mjs +32 -10
  37. package/src/services/tailscale.mjs +0 -1
  38. package/src/subnet.mjs +38 -34
  39. package/src/type.mjs +86 -1
  40. package/src/utils.mjs +2 -2
  41. package/src/location.mjs +0 -48
package/src/host.mjs CHANGED
@@ -1,17 +1,23 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { FileContentProvider } from "npm-pkgbuild";
4
+ import { AggregatedMap } from "aggregated-map";
4
5
  import {
5
- default_attribute_writable,
6
+ default_collection_attribute_writable,
6
7
  string_attribute,
7
8
  string_attribute_writable,
8
9
  string_set_attribute_writable,
9
10
  number_attribute_writable,
10
- boolean_attribute_false
11
+ boolean_attribute_false,
12
+ priority_attribute
11
13
  } from "pacc";
12
- import { NetworkInterface, addresses, addType } from "pmcf";
14
+ import { addresses, addType, assign } from "pmcf";
15
+ import {
16
+ networkAddressAttributes,
17
+ networkInterfaces_attribute,
18
+ hosts_attribute
19
+ } from "./common-attributes.mjs";
13
20
  import { ServiceOwner } from "./service-owner.mjs";
14
- import { networkAddressAttributes } from "./network-support.mjs";
15
21
  import { addHook } from "./hooks.mjs";
16
22
  import {
17
23
  domainFromDominName,
@@ -28,29 +34,29 @@ export class Host extends ServiceOwner {
28
34
  static priority = 1.9;
29
35
  static attributes = {
30
36
  ...networkAddressAttributes,
31
- networkInterfaces: {
32
- ...default_attribute_writable,
33
- type: "network_interface",
34
- collection: true
35
- },
36
- aliases: string_set_attribute_writable,
37
+ networkInterfaces: networkInterfaces_attribute,
38
+ aliases: { ...string_set_attribute_writable, name: "aliases" },
37
39
  os: {
38
40
  ...string_attribute_writable,
39
- values: ["osx", "windows", "linux"]
41
+ name: "os",
42
+ values: new Set(["osx", "windows", "linux"])
40
43
  },
41
- "machine-id": string_attribute_writable,
42
- distribution: string_attribute_writable,
44
+ "machine-id": { ...string_attribute_writable, name: "machine-id" },
45
+ distribution: { ...string_attribute_writable, name: "distribution" },
43
46
  deployment: {
44
47
  ...string_attribute_writable,
45
- values: ["production", "development"]
48
+ name: "deployment",
49
+ values: new Set(["production", "development"])
46
50
  },
47
- weight: number_attribute_writable,
48
- serial: string_attribute_writable,
49
- vendor: string_attribute_writable,
50
- keymap: string_attribute_writable,
51
+ priority: priority_attribute,
52
+ weight: { ...number_attribute_writable, name: "weight" },
53
+ serial: { ...string_attribute_writable, name: "serial" },
54
+ vendor: { ...string_attribute_writable, name: "vendor" },
55
+ keymap: { ...string_attribute_writable, name: "keymap" },
51
56
  chassis: {
52
57
  ...string_attribute_writable,
53
- values: [
58
+ name: "chassis",
59
+ values: new Set([
54
60
  "phone",
55
61
  "tablet",
56
62
  "router",
@@ -64,23 +70,22 @@ export class Host extends ServiceOwner {
64
70
  "battery",
65
71
  "virtual",
66
72
  "dehumidifier"
67
- ]
73
+ ])
68
74
  },
69
75
  architecture: {
70
76
  ...string_attribute_writable,
71
- values: ["x86", "x86_64", "aarch64", "armv7", "riscv"]
77
+ name: "architecture",
78
+ values: new Set(["x86", "x86_64", "aarch64", "armv7", "riscv"])
72
79
  },
73
- replaces: string_set_attribute_writable,
74
- depends: string_set_attribute_writable,
75
- provides: string_set_attribute_writable,
80
+ replaces: { ...string_set_attribute_writable, name: "replaces" },
81
+ depends: { ...string_set_attribute_writable, name: "depends" },
82
+ provides: { ...string_set_attribute_writable, name: "provides" },
76
83
  extends: {
77
- ...default_attribute_writable,
78
- type: Host,
79
- collection: true,
80
- owner: false
84
+ ...default_collection_attribute_writable,
85
+ name: "extends",
86
+ type: Host
81
87
  },
82
- model: string_attribute,
83
- isModel: boolean_attribute_false
88
+ model: { ...string_attribute, name: "model" }
84
89
  };
85
90
 
86
91
  static {
@@ -106,11 +111,12 @@ export class Host extends ServiceOwner {
106
111
  super.materializeExtends();
107
112
 
108
113
  for (const host of this.walkDirections(["extends"])) {
109
- for (const [name, ni] of host.networkInterfaces) {
114
+ for (const ni of host.networkInterfaces.values()) {
110
115
  const present = this._networkInterfaces.get(ni.name);
111
116
 
112
117
  if (present) {
113
118
  present.extends.add(ni);
119
+ present.materializeExtends();
114
120
  } else {
115
121
  this.networkInterfaces = ni.forOwner(this);
116
122
  }
@@ -292,6 +298,9 @@ export class Host extends ServiceOwner {
292
298
  );
293
299
  }
294
300
 
301
+ /**
302
+ * @return {Set<string>}
303
+ */
295
304
  get domainNames() {
296
305
  return new Set(
297
306
  [
@@ -335,16 +344,6 @@ export class Host extends ServiceOwner {
335
344
  return [this];
336
345
  }
337
346
 
338
- typeNamed(typeName, name) {
339
- if (typeName === NetworkInterface.name) {
340
- const ni = this._networkInterfaces.get(name);
341
- if (ni) {
342
- return ni;
343
- }
344
- }
345
- return super.typeNamed(typeName, name);
346
- }
347
-
348
347
  get network() {
349
348
  for (const ni of this.networkInterfaces.values()) {
350
349
  if (ni._kind !== "loopback" && ni._network) {
@@ -371,7 +370,8 @@ export class Host extends ServiceOwner {
371
370
  this._networkInterfaces.set(networkInterface.name, networkInterface);
372
371
 
373
372
  if (!this.isTemplate && networkInterface.network) {
374
- networkInterface.network.hosts = this;
373
+ assign(hosts_attribute, networkInterface.network, this);
374
+ //networkInterface.network.hosts = this;
375
375
  }
376
376
  }
377
377
 
@@ -390,13 +390,9 @@ export class Host extends ServiceOwner {
390
390
  }
391
391
 
392
392
  get subnets() {
393
- let all = new Set();
394
-
395
- for (const networkInterface of this.networkInterfaces.values()) {
396
- all = all.union(networkInterface.subnets);
397
- }
398
-
399
- return all;
393
+ return new AggregatedMap(
394
+ [...this.networkInterfaces.values()].map(ni => ni.subnets)
395
+ );
400
396
  }
401
397
 
402
398
  async publicKey(type = "ed25519") {
@@ -421,7 +417,7 @@ export class Host extends ServiceOwner {
421
417
  Object.assign(packageData.properties, {
422
418
  description: `${this.typeName} definitions for ${this.fullName}`,
423
419
  dependencies: [
424
- `${this.location.typeName}-${this.location.name}`,
420
+ `${this.owner.typeName}-${this.owner.name}`,
425
421
  ...this.depends
426
422
  ],
427
423
  provides: [...this.provides],
@@ -440,9 +436,10 @@ export class Host extends ServiceOwner {
440
436
  await ni.systemdDefinitions(dir, packageData);
441
437
  }
442
438
 
443
- await generateKnownHosts(this.owner.hosts, join(dir, "root", ".ssh"));
444
-
445
- //console.log([...this.walkDirections(["extends"])].map(e => e.fullName));
439
+ await generateKnownHosts(
440
+ this.owner.hosts.values(),
441
+ join(dir, "root", ".ssh")
442
+ );
446
443
 
447
444
  for (const [name, service] of this.services) {
448
445
  if (service.systemdConfigs) {
@@ -1,9 +1,12 @@
1
1
  import { readFile, glob } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
- import { toInternal, attributeIterator, types, resolveTypeLinks } from "pacc";
4
- import { asArray } from "./utils.mjs";
5
- import { Base } from "./base.mjs";
6
- import { root } from "./root.mjs";
3
+ import {
4
+ extendingAttributeIterator,
5
+ types,
6
+ resolveTypeLinks,
7
+ create
8
+ } from "pacc";
9
+ import { Base, root, assign } from "pmcf";
7
10
 
8
11
  /**
9
12
  * Keeps track of all in flight object creations and loose ends during config initialization.
@@ -18,278 +21,174 @@ export class InitializationContext {
18
21
  }
19
22
 
20
23
  resolveOutstanding() {
21
- nextOutstanding: for (let { object, attribute, name, value } of this
24
+ nextOutstanding: for (let { object, attribute, value } of this
22
25
  .outstandingResolves) {
23
26
  value = object.expand(value);
24
27
 
25
28
  for (const type of attribute.type.members || [attribute.type]) {
26
29
  for (const node of object.walkDirections(["this", "owner"])) {
27
- const resolved = node.typeNamed(type.name, value);
30
+ const resolved = this.named(value, node);
28
31
  if (resolved) {
29
- this.assign(object, name, attribute, resolved);
32
+ assign(attribute, object, resolved);
30
33
  continue nextOutstanding;
31
34
  }
32
35
  }
33
36
  }
34
37
 
35
38
  this.error(
36
- `Unable to resolve "${value}" (${attribute.type.name}) for attribute ${name}`,
37
- object.root.named(value)?.toString()
39
+ `Unknown ${attribute.name}(${attribute.type.name}): "${value}"`
38
40
  );
39
41
  }
40
42
  }
41
43
 
42
- resolveLater(object, attribute, name, value) {
43
- this.outstandingResolves.push({ object, attribute, name, value });
44
+ resolveLater(object, attribute, value) {
45
+ this.outstandingResolves.push({ object, attribute, value });
44
46
  }
45
47
 
46
48
  error(...args) {
47
49
  console.error(...args);
48
50
  }
49
51
 
50
- assign(object, name, attribute, value) {
51
- value = toInternal(value, attribute);
52
- value ??= attribute.default;
53
-
52
+ instantiateAndAssign(object, attribute, value) {
53
+ if (attribute.type.primitive) {
54
+ return assign(attribute, object, value);
55
+ }
54
56
  if (value !== undefined) {
55
- if (attribute.values) {
56
- if (attribute.values.indexOf(value) < 0) {
57
- this.error(name, "unknown value", value, attribute.values);
58
- }
59
- }
60
-
61
- if (attribute.collection) {
62
- const current = object[name];
63
-
64
- switch (typeof current) {
65
- case "undefined":
66
- if (attribute.constructor === value.constructor) {
67
- object[name] = value;
68
- } else {
69
- object[name] = asArray(value);
70
- }
71
- break;
72
- case "object":
73
- if (Array.isArray(current)) {
74
- if (Array.isArray(value)) {
75
- current.push(...value);
76
- } else {
77
- current.push(value);
78
- }
79
- } else {
80
- if (current instanceof Set) {
81
- if (value instanceof Set) {
82
- object[name] = current.union(value);
83
- } else {
84
- /*if(name === 'extends') {
85
- console.log("EXT",object.fullName,value.fullName)
86
- }*/
87
- object[name].add(value);
88
- }
89
- } else if (current instanceof Map) {
90
- const keyName = attribute.type.key;
91
- if (keyName) {
92
- current.set(value[keyName], value);
93
- } else {
94
- // TODO
95
- object[name] = value;
96
- }
97
- } else {
98
- const keyName = attribute.type.key;
99
- object[name][value[keyName]] = value;
100
- }
101
- }
102
- break;
103
- case "function":
104
- if (value instanceof Base) {
105
- object.addObject(value);
106
- } else {
107
- this.error("Unknown collection type", name, current);
108
- }
109
- break;
57
+ if (typeof value === "object") {
58
+ if (attribute.type && value instanceof attribute.type) {
59
+ return assign(attribute, object, value);
110
60
  }
111
61
  } else {
112
- object[name] = value;
62
+ const present = this.named(value, object);
63
+
64
+ if (
65
+ present &&
66
+ (present.typeName === attribute.type.name ||
67
+ attribute.type.members?.has(present.typeName))
68
+ ) {
69
+ return assign(attribute, object, present);
70
+ } else {
71
+ if (attribute.type.constructWithIdentifierOnly) {
72
+ return assign(
73
+ attribute,
74
+ object,
75
+ create(attribute.type, object, value)
76
+ );
77
+ } else {
78
+ return this.resolveLater(object, attribute, value);
79
+ }
80
+ }
113
81
  }
82
+ return assign(
83
+ attribute,
84
+ object,
85
+ this.read(create(attribute.type, object, value), value)
86
+ );
114
87
  }
115
88
  }
116
89
 
117
- instantiateAndAssign(object, name, attribute, value) {
118
- if (attribute.type.primitive) {
119
- this.assign(object, name, attribute, value);
120
- return;
90
+ read(object, data, type = object.constructor) {
91
+ if (data?.properties) {
92
+ Object.assign(object.properties, data.properties);
121
93
  }
122
94
 
123
- switch (typeof value) {
124
- case "undefined":
125
- return;
126
-
127
- case "function":
128
- this.error("Invalid value", name, value);
129
- break;
130
-
131
- case "boolean":
132
- case "bigint":
133
- case "number":
134
- case "string":
135
- {
136
- let o;
95
+ for (const [path, attribute] of extendingAttributeIterator(
96
+ type,
97
+ attribute => attribute.writable
98
+ )) {
99
+ const name = path.join(".");
100
+ const value = object.expand(data[name]);
137
101
 
138
- for (const type of attribute.type.members || [attribute.type]) {
139
- o = object.typeNamed(type.name, value);
140
- if (o) {
141
- break;
102
+ if (attribute.collection) {
103
+ if (typeof value === "object") {
104
+ if (Array.isArray(value)) {
105
+ for (const v of value) {
106
+ this.instantiateAndAssign(object, attribute, v);
142
107
  }
143
- }
144
-
145
- if (o) {
146
- this.assign(object, name, attribute, o);
147
108
  } else {
148
- if (attribute.type.constructWithIdentifierOnly) {
149
- o = new attribute.type(
150
- object.ownerFor(attribute, value),
151
- value
152
- );
153
- this.read(o, value);
154
- object.addObject(o);
109
+ if (value instanceof Base) {
110
+ assign(attribute, object, value);
155
111
  } else {
156
- this.resolveLater(object, attribute, name, value);
112
+ for (const [objectName, objectData] of Object.entries(value)) {
113
+ if (typeof objectData === "object") {
114
+ objectData[attribute.type.key] = objectName;
115
+ }
116
+ this.instantiateAndAssign(object, attribute, objectData);
117
+ }
157
118
  }
158
119
  }
120
+ continue;
159
121
  }
160
-
161
- break;
162
-
163
- case "object":
164
- if (attribute.type && value instanceof attribute.type) {
165
- this.assign(object, name, attribute, value);
166
- } else {
167
- this.assign(
168
- object,
169
- name,
170
- attribute,
171
- this.typeFactory(
172
- attribute.type,
173
- object.ownerFor(attribute, value),
174
- value
175
- )
176
- );
177
- }
178
- break;
122
+ }
123
+ this.instantiateAndAssign(object, attribute, value);
179
124
  }
180
- }
181
125
 
182
- typeFactory(type, owner, data) {
183
- const factory = type.factoryFor?.(owner, data) || type;
184
- const object = new factory(owner);
126
+ if (data.extends) {
127
+ object.materializeExtends();
128
+ }
185
129
 
186
- this.read(object, data);
187
- owner.addObject(object);
188
130
  return object;
189
131
  }
190
132
 
191
- read(object, data, type = object.constructor) {
192
- if (data?.properties) {
193
- Object.assign(object.properties, data.properties);
194
- }
195
-
196
- this._read(object, data, type);
133
+ async load(fileName, type) {
134
+ const name = fileName.replace(/\/?([^\/]+\.json)?$/, "");
197
135
 
198
- if (data.extends) {
199
- object.materializeExtends();
136
+ const object = this.root.named(name);
137
+ if (object) {
138
+ return object;
200
139
  }
201
- }
202
140
 
203
- _read(object, data, type) {
204
- if (type.extends) {
205
- this._read(object, data, type.extends);
206
- }
141
+ if (type === undefined) {
142
+ const tn = fileName.substring(name.length, fileName.length - 5);
207
143
 
208
- for (const [path, attribute] of attributeIterator(type.attributes)) {
209
- if (attribute.writable) {
210
- const name = path.join(".");
211
- const value = object.expand(data[name]);
144
+ type = types[tn];
212
145
 
213
- if (attribute.collection) {
214
- if (typeof value === "object") {
215
- if (Array.isArray(value)) {
216
- for (const v of value) {
217
- this.instantiateAndAssign(object, name, attribute, v);
218
- }
219
- } else {
220
- if (value instanceof Base) {
221
- this.assign(object, name, attribute, value);
222
- } else {
223
- for (const [objectName, objectData] of Object.entries(value)) {
224
- if (typeof objectData === "object") {
225
- objectData[attribute.type.key] = objectName;
226
- }
227
- this.instantiateAndAssign(
228
- object,
229
- name,
230
- attribute,
231
- objectData
232
- );
233
- }
234
- }
235
- }
236
- continue;
237
- }
146
+ if (!type) {
147
+ for (const type of Object.values(types).filter(type => type.fileName)) {
148
+ try {
149
+ return await this.load(fileName, type);
150
+ } catch {}
238
151
  }
239
- this.instantiateAndAssign(object, name, attribute, value);
152
+
153
+ this.error(`No type for "${fileName}"`);
240
154
  }
241
155
  }
242
- }
243
156
 
244
- async loadType(name, type) {
245
157
  const data = JSON.parse(
246
- await readFile(
247
- join(this.directory, name, type.fileName),
248
- "utf8"
249
- )
158
+ await readFile(join(this.directory, name, type.fileName), "utf8")
250
159
  );
251
160
 
252
161
  let owner;
253
162
  if (name[0] === "/") {
254
163
  const parentName = name.replace(/\/[^\/]+$/, "");
164
+
255
165
  owner = await this.load(parentName);
166
+
167
+ if (!owner) {
168
+ this.error(`No Parent for "${name}" "${parentName}"`);
169
+ return;
170
+ }
171
+
256
172
  data.name = name.substring(owner.fullName.length + 1);
257
173
  } else {
258
174
  owner = this.root;
259
175
  data.name = name;
260
176
  }
261
177
 
262
- const object = this.typeFactory(type, owner, data);
263
-
264
- //console.log("LOAD", [name, type.name, owner.fullName, data.name, object.fullName]);
265
-
266
- this.root.addTypeObject(type.name, name, object);
267
-
268
- return object;
269
- }
270
-
271
- async load(name, options) {
272
- name = name.replace(/\/?([^\/]+\.json)?$/, "");
273
-
274
- const object = this.root.named(name);
275
- if (object) {
276
- return object;
277
- }
178
+ for (const [path, attribute] of extendingAttributeIterator(
179
+ owner.constructor,
180
+ attribute => attribute.type === type && attribute.collection
181
+ )) {
182
+ const object = create(type, owner, data);
278
183
 
279
- if (options?.type) {
280
- return this.loadType(name, options.type);
281
- } else {
282
- for (const type of Object.values(types).filter(
283
- type => type?.fileName
284
- )) {
285
- try {
286
- return await this.loadType(name, type);
287
- } catch {}
184
+ // set backointer early so that parent properties can be found during load
185
+ if (attribute.backpointer) {
186
+ assign(attribute.backpointer, object, owner);
288
187
  }
188
+ return assign(attribute, owner, this.read(object, data));
289
189
  }
290
190
 
291
- const parentName = name.replace(/\/[^\/]$/, "");
292
- return name === parentName ? this.root : this.load(parentName, options);
191
+ this.error(`No attribute to assign ${type.name} to ${owner.fullName}`);
293
192
  }
294
193
 
295
194
  async loadAll() {
@@ -300,14 +199,7 @@ export class InitializationContext {
300
199
  for await (const name of glob("**/" + type.fileName, {
301
200
  cwd: this.directory
302
201
  })) {
303
- if (type === this.root.constructor) {
304
- const data = JSON.parse(
305
- await readFile(join(this.directory, name), "utf8")
306
- );
307
- this.root.properties = data.properties;
308
- } else {
309
- await this.load("/" + name, { type });
310
- }
202
+ await this.load("/" + name, type);
311
203
  }
312
204
  }
313
205
  }
@@ -315,7 +207,9 @@ export class InitializationContext {
315
207
  this.resolveOutstanding();
316
208
  }
317
209
 
318
- named(name) {
319
- return this.root.named(name);
210
+ named(name, base) {
211
+ return name[0] === "/"
212
+ ? this.root.named(name.substring(1))
213
+ : base.named(name);
320
214
  }
321
215
  }
package/src/module.mjs CHANGED
@@ -2,13 +2,12 @@ export * from "./type.mjs";
2
2
  export * from "./base.mjs";
3
3
  export * from "./cluster.mjs";
4
4
  export * from "./owner.mjs";
5
- export * from "./location.mjs";
6
5
  export * from "./root.mjs";
7
6
  export * from "./subnet.mjs";
8
7
  export * from "./service-owner.mjs";
9
8
  export * from "./network.mjs";
10
9
  export * from "./network-address.mjs";
11
- export * from "./network-support.mjs";
10
+ export * from "./common-attributes.mjs";
12
11
  export * from "./host.mjs";
13
12
  export * from "./network-interfaces/network-interface.mjs";
14
13
  export * from "./network-interfaces/loopback.mjs";
@@ -56,8 +56,8 @@ export function addresses(sources, options) {
56
56
  if (typeof s === "string") {
57
57
  return s;
58
58
  }
59
- if (options?.aggregate && s instanceof Owner && s.subnets) {
60
- return [...s.subnets];
59
+ if (options?.aggregate && s instanceof Owner && s.subnets.size > 0) {
60
+ return [...s.subnets.keys()];
61
61
  }
62
62
 
63
63
  return s.networkAddresses
@@ -1,28 +1,20 @@
1
- import { default_attribute_writable, addType } from "pacc";
2
1
  import { NetworkInterface } from "./network-interface.mjs";
2
+ import { addType } from "../type.mjs";
3
+ import { networkInterfaces_attribute } from "../common-attributes.mjs";
3
4
 
4
5
  export class ethernet extends NetworkInterface {
5
- static specializationOf = NetworkInterface;
6
6
  static attributes = {
7
7
  arpbridge: {
8
- ...default_attribute_writable,
9
- type: "network_interface",
10
- collection: true,
11
- owner: false
8
+ ...networkInterfaces_attribute,
9
+ name: "arpbridge"
12
10
  }
13
11
  };
14
12
 
13
+ static commonNamePattern = /^(eth|end|en)\d+$/;
14
+
15
15
  static {
16
16
  addType(this);
17
17
  }
18
18
 
19
- static isCommonName(name) {
20
- return name.match(/^eth\d+$/);
21
- }
22
-
23
- arpbridge;
24
-
25
- get kind() {
26
- return this.constructor.name;
27
- }
19
+ arpbridge = new Map();
28
20
  }