diff --git a/lib/nameserver/store/file.js b/lib/nameserver/store/file.js
index 7088a0a..587a125 100644
--- a/lib/nameserver/store/file.js
+++ b/lib/nameserver/store/file.js
@@ -29,11 +29,7 @@ class NameserverRepoFile extends NameserverBase {
if ([null, undefined].includes(r[f])) r[f] = ''
}
- // Ensure the nested export object is always present and well-formed.
- // Unlike MySQL (which joins nt_nameserver_export_type), TOML stores the
- // type name inline, so no translation is needed.
if (!r.export || typeof r.export !== 'object') r.export = {}
- if ([null, undefined].includes(r.export.type)) r.export.type = ''
if ([null, undefined].includes(r.export.interval)) r.export.interval = 0
if ([null, undefined].includes(r.export.status)) r.export.status = ''
r.export.serials = Boolean(r.export.serials)
diff --git a/lib/nameserver/store/mysql.js b/lib/nameserver/store/mysql.js
index 911cba5..8ef94ad 100644
--- a/lib/nameserver/store/mysql.js
+++ b/lib/nameserver/store/mysql.js
@@ -21,16 +21,7 @@ class Nameserver extends NameserverBase {
if (g.length === 1) return g[0].id
}
- if (args.export.type) {
- args = JSON.parse(JSON.stringify(args))
- const rows = await Mysql.execute(
- ...Mysql.select('SELECT id FROM nt_nameserver_export_type', {
- name: args.export.type,
- }),
- )
- args.export_type_id = rows[0].id
- delete args.export.type
- }
+ args = await resolveType(args)
return await Mysql.execute(...Mysql.insert(`nt_nameserver`, mapToDbColumn(objectToDb(args), nsDbMap)))
}
@@ -58,13 +49,12 @@ class Nameserver extends NameserverBase {
, ns.export_interval
, ns.export_serials
, ns.export_status
- , ns.engine
, ns.listen
, ns.publisher
, ns.transport
, ns.dnssec
, ns.deleted
- , t.name AS export_type
+ , t.name AS type
FROM nt_nameserver ns
JOIN nt_nameserver_export_type t ON ns.export_type_id=t.id`,
mapToDbColumn(args, nsDbMap),
@@ -81,6 +71,7 @@ class Nameserver extends NameserverBase {
async put(args) {
if (!args.id) return false
+ args = await resolveType(args)
const id = args.id
delete args.id
// Mysql.debug(1)
@@ -111,6 +102,24 @@ class Nameserver extends NameserverBase {
export default Nameserver
+/**
+ * `type` is stored as the export_type_id foreign key, so a write has to resolve
+ * the name to its id.
+ */
+async function resolveType(argsIn) {
+ if (argsIn.type === undefined) return argsIn
+
+ const args = JSON.parse(JSON.stringify(argsIn))
+ const rows = await Mysql.execute(
+ ...Mysql.select('SELECT id FROM nt_nameserver_export_type', { name: args.type }),
+ )
+ if (!rows.length) throw new Error(`unknown nameserver type: ${args.type}`)
+
+ args.export_type_id = rows[0].id
+ delete args.type
+ return args
+}
+
function dbToObject(rows) {
for (const row of rows) {
for (const f of ['description', 'address6', 'remote_login', 'datadir', 'logdir', 'export_status']) {
@@ -128,9 +137,8 @@ function dbToObject(rows) {
}
if (row[f] === null || row[f] === undefined) delete row[f]
}
- if ([null, undefined, ''].includes(row.engine)) delete row.engine
for (const f of ['export']) {
- for (const p of ['type', 'interval', 'serials', 'status']) {
+ for (const p of ['interval', 'serials', 'status']) {
if (![null, undefined].includes(row[`${f}_${p}`])) {
if (row[f] === undefined) row[f] = {}
row[f][p] = row[`${f}_${p}`]
diff --git a/lib/nameserver/test/nameserver.json b/lib/nameserver/test/nameserver.json
index 934ecbd..a97719a 100644
--- a/lib/nameserver/test/nameserver.json
+++ b/lib/nameserver/test/nameserver.json
@@ -8,11 +8,11 @@
"remote_login": "nsd",
"logdir": "/foo",
"datadir": "/bar",
+ "type": "nsd",
"export": {
"interval": 0,
"serials": true,
- "status": "last run:03-05 15:25
last cp :09-20 12:59",
- "type": "nsd"
+ "status": "last run:03-05 15:25
last cp :09-20 12:59"
},
"ttl": 3600
}
diff --git a/lib/nameserver/test/runtime.js b/lib/nameserver/test/runtime.js
index c2aef40..69b31d6 100644
--- a/lib/nameserver/test/runtime.js
+++ b/lib/nameserver/test/runtime.js
@@ -1,7 +1,8 @@
// The nameserver record carries the v3 runtime configuration the supervisor
-// consumes (engine, listen, publisher, transport, dnssec). It has to survive a
+// consumes (type, listen, publisher, transport, dnssec). It has to survive a
// round-trip through whichever store is active: nested structures in the file
-// stores, JSON columns under MySQL.
+// stores, JSON columns under MySQL, and `type` through the export_type_id
+// foreign key.
import assert from 'node:assert/strict'
import { describe, it, after, before } from 'node:test'
@@ -13,8 +14,8 @@ const runtimeCase = {
name: 'rt.ns.example.com.',
address: '1.2.3.9',
ttl: 3600,
- export: { type: 'nsd', interval: 0, serials: true, status: '' },
- engine: 'native',
+ export: { interval: 0, serials: true, status: '' },
+ type: 'native',
listen: [
{ address: '127.0.0.1', port: 5353, proto: 'udp' },
{ address: '127.0.0.1', port: 5353, proto: 'tcp' },
@@ -41,10 +42,10 @@ describe('nameserver runtime config', () => {
assert.deepEqual(ns.listen, runtimeCase.listen)
})
- it('round-trips engine, publisher, transport and dnssec', async () => {
+ it('round-trips type, publisher, transport and dnssec', async () => {
const [ns] = await Nameserver.get({ id: runtimeCase.id })
- assert.equal(ns.engine, 'native')
+ assert.equal(ns.type, 'native')
assert.deepEqual(ns.publisher, runtimeCase.publisher)
assert.deepEqual(ns.transport, runtimeCase.transport)
assert.deepEqual(ns.dnssec, runtimeCase.dnssec)
@@ -52,22 +53,34 @@ describe('nameserver runtime config', () => {
it('updates runtime config in place', async () => {
const listen = [{ address: '127.0.0.1', port: 5354, proto: 'udp' }]
- assert.ok(await Nameserver.put({ id: runtimeCase.id, listen, engine: 'bind' }))
+ assert.ok(await Nameserver.put({ id: runtimeCase.id, listen, type: 'bind' }))
const [ns] = await Nameserver.get({ id: runtimeCase.id })
assert.deepEqual(ns.listen, listen)
- assert.equal(ns.engine, 'bind')
+ assert.equal(ns.type, 'bind')
+ })
+
+ // dynect and bind-nsupdate are 2.x export types with no v3 nameserver. An
+ // adopted record must still round-trip; refusing it would strand the record.
+ it('stores a type nothing here can build', async () => {
+ const legacy = { ...runtimeCase, id: 4244, name: 'dyn.ns.example.com.', type: 'dynect' }
+ await Nameserver.destroy({ id: legacy.id })
+ await Nameserver.create(legacy)
+
+ const [ns] = await Nameserver.get({ id: legacy.id })
+ assert.equal(ns.type, 'dynect')
+ await Nameserver.destroy({ id: legacy.id })
})
it('omits runtime keys entirely for a legacy record that has none', async () => {
const legacy = { ...runtimeCase, id: 4243, name: 'legacy.ns.example.com.' }
- for (const k of ['engine', 'listen', 'publisher', 'transport', 'dnssec']) delete legacy[k]
+ for (const k of ['listen', 'publisher', 'transport', 'dnssec']) delete legacy[k]
await Nameserver.destroy({ id: legacy.id })
await Nameserver.create(legacy)
const [ns] = await Nameserver.get({ id: legacy.id })
- for (const k of ['engine', 'listen', 'publisher', 'transport', 'dnssec']) {
+ for (const k of ['listen', 'publisher', 'transport', 'dnssec']) {
assert.equal(ns[k], undefined, `${k} should be absent, not null`)
}
await Nameserver.destroy({ id: legacy.id })
diff --git a/routes/test/nameserver.json b/routes/test/nameserver.json
index a5366be..ce2ff0b 100644
--- a/routes/test/nameserver.json
+++ b/routes/test/nameserver.json
@@ -8,11 +8,11 @@
"remote_login": "nsd",
"logdir": "/foo",
"datadir": "/bar",
+ "type": "nsd",
"export": {
"interval": 0,
"serials": true,
- "status": "last run:03-05 15:25
last cp :09-20 12:59",
- "type": "nsd"
+ "status": "last run:03-05 15:25
last cp :09-20 12:59"
},
"ttl": 3600,
"deleted": false
diff --git a/sql/01_nt_group.sql b/sql/01_nt_group.sql
index 4595ada..c7ed848 100644
--- a/sql/01_nt_group.sql
+++ b/sql/01_nt_group.sql
@@ -42,6 +42,10 @@ CREATE TABLE IF NOT EXISTS nt_group_subgroups(
INSERT IGNORE INTO `nt_group` (`nt_group_id`, `parent_group_id`, `name`)
VALUES
(1,0,'NicTool');
-INSERT IGNORE INTO nt_group_log(nt_group_id, nt_user_id, action, timestamp, modified_group_id, parent_group_id)
-VALUES
- (1, 1, 'added', UNIX_TIMESTAMP(), 1, 0);
+
+# The log row carries no explicit id and nt_group_log has no unique key over
+# these columns, guard on the row already being there.
+INSERT INTO nt_group_log(nt_group_id, nt_user_id, action, timestamp, modified_group_id, parent_group_id)
+SELECT 1, 1, 'added', UNIX_TIMESTAMP(), 1, 0
+FROM DUAL WHERE NOT EXISTS
+ (SELECT 1 FROM nt_group_log WHERE modified_group_id=1 AND action='added');
diff --git a/sql/04_nt_nameserver.sql b/sql/04_nt_nameserver.sql
index 3e5a73a..d02916d 100644
--- a/sql/04_nt_nameserver.sql
+++ b/sql/04_nt_nameserver.sql
@@ -18,8 +18,8 @@ CREATE TABLE IF NOT EXISTS nt_nameserver(
export_serials tinyint(1) UNSIGNED NOT NULL DEFAULT '1',
export_status varchar(255) NULL DEFAULT NULL,
-- v3 runtime configuration, driving the nameserver supervisor. Stored as
- -- JSON because the shape is engine-specific and evolves with the engines.
- engine VARCHAR(32) NULL DEFAULT NULL,
+ -- JSON because the shape varies by nameserver type and evolves with it.
+ -- Which software this is stays in export_type_id: one fact, one column.
listen JSON NULL DEFAULT NULL,
publisher JSON NULL DEFAULT NULL,
transport JSON NULL DEFAULT NULL,
@@ -72,21 +72,37 @@ VALUES (1,'djbdns','djbdns (tinydns & axfrdns)','cr.yp.to/djbdns.html'),
(5,'bind-nsupdate','BIND (nsupdate protocol)',''),
(6,'nsd','Name Server Daemon (NSD)','www.nlnetlabs.nl/projects/nsd/'),
(7,'dynect','DynECT Standard DNS','dyn.com/managed-dns/'),
- (8,'knot','Knot DNS','www.knot-dns.cz');
+ (8,'knot','Knot DNS','www.knot-dns.cz'),
+ (9,'coredns','CoreDNS','coredns.io'),
+ (10,'native','NicTool (in-process)','nictool.com');
-INSERT IGNORE INTO nt_nameserver(nt_group_id, name, ttl, description, address,
- export_type_id, logdir, datadir, export_interval) values (1,'ns1.example.com.',86400,'ns east',
- '198.93.97.188','1','/etc/tinydns-ns1/log/main/',
- '/etc/tinydns-ns1/root/',120);
-INSERT IGNORE INTO nt_nameserver(nt_group_id, name, ttl, description, address,
- export_type_id, logdir, datadir, export_interval) values (1,'ns2.example.com.',86400,'ns west',
- '216.133.235.6','1','/etc/tinydns-ns2/log/main/','/etc/tinydns-ns2/root/',120);
-INSERT IGNORE INTO nt_nameserver(nt_group_id, name, ttl, description, address,
- export_type_id, logdir, datadir, export_interval) values (1,'ns3.example.com.',86400,'ns test',
- '127.0.0.1','2','/var/log', '/etc/namedb/master/',120);
-INSERT IGNORE INTO nt_nameserver_log(nt_group_id,nt_user_id, action, timestamp, nt_nameserver_id) VALUES (1,1,'added',UNIX_TIMESTAMP(), 1);
-INSERT IGNORE INTO nt_nameserver_log(nt_group_id,nt_user_id, action, timestamp, nt_nameserver_id) VALUES (1,1,'added',UNIX_TIMESTAMP(), 2);
-INSERT IGNORE INTO nt_nameserver_log(nt_group_id,nt_user_id, action, timestamp, nt_nameserver_id) VALUES (1,1,'added',UNIX_TIMESTAMP(), 3);
+INSERT INTO nt_nameserver(nt_group_id, name, ttl, description, address,
+ export_type_id, logdir, datadir, export_interval)
+SELECT 1,'ns1.example.com.',86400,'ns east','198.93.97.188','1',
+ '/etc/tinydns-ns1/log/main/','/etc/tinydns-ns1/root/',120
+FROM DUAL WHERE NOT EXISTS
+ (SELECT 1 FROM nt_nameserver WHERE name='ns1.example.com.');
+
+INSERT INTO nt_nameserver(nt_group_id, name, ttl, description, address,
+ export_type_id, logdir, datadir, export_interval)
+SELECT 1,'ns2.example.com.',86400,'ns west','216.133.235.6','1',
+ '/etc/tinydns-ns2/log/main/','/etc/tinydns-ns2/root/',120
+FROM DUAL WHERE NOT EXISTS
+ (SELECT 1 FROM nt_nameserver WHERE name='ns2.example.com.');
+
+INSERT INTO nt_nameserver(nt_group_id, name, ttl, description, address,
+ export_type_id, logdir, datadir, export_interval)
+SELECT 1,'ns3.example.com.',86400,'ns test','127.0.0.1','2',
+ '/var/log','/etc/namedb/master/',120
+FROM DUAL WHERE NOT EXISTS
+ (SELECT 1 FROM nt_nameserver WHERE name='ns3.example.com.');
+
+INSERT INTO nt_nameserver_log(nt_group_id, nt_user_id, action, timestamp, nt_nameserver_id)
+SELECT 1,1,'added',UNIX_TIMESTAMP(),ns.nt_nameserver_id
+FROM nt_nameserver ns
+WHERE ns.name IN ('ns1.example.com.','ns2.example.com.','ns3.example.com.')
+ AND NOT EXISTS (SELECT 1 FROM nt_nameserver_log l
+ WHERE l.nt_nameserver_id = ns.nt_nameserver_id AND l.action = 'added');
CREATE TABLE IF NOT EXISTS nt_nameserver_export_log(
nt_nameserver_export_log_id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
diff --git a/sql/06_resource_records.sql b/sql/06_resource_records.sql
index 60b93cc..fdbde03 100644
--- a/sql/06_resource_records.sql
+++ b/sql/06_resource_records.sql
@@ -17,7 +17,7 @@ VALUES
(2,'NS','Name Server',1,1,0),
(5,'CNAME','Canonical Name',1,1,0),
(6,'SOA','Start Of Authority',0,0,0),
- (12,'PTR','Pointer',1,0,0),
+ (12,'PTR','Pointer',1,1,0),
(13,'HINFO','Host Info',0,0,1),
(15,'MX','Mail Exchanger',0,1,0),
(16,'TXT','Text',1,1,0),
diff --git a/sql/upgrade/03_nameserver_runtime_columns.sql b/sql/upgrade/03_nameserver_runtime_columns.sql
index 7946060..0e806d9 100644
--- a/sql/upgrade/03_nameserver_runtime_columns.sql
+++ b/sql/upgrade/03_nameserver_runtime_columns.sql
@@ -7,7 +7,6 @@
# errors with ER_DUP_FIELDNAME. That is safe to ignore.
ALTER TABLE nt_nameserver
- ADD COLUMN engine VARCHAR(32) NULL DEFAULT NULL,
ADD COLUMN listen JSON NULL DEFAULT NULL,
ADD COLUMN publisher JSON NULL DEFAULT NULL,
ADD COLUMN transport JSON NULL DEFAULT NULL,