Skip to content

Commit d4e7546

Browse files
authored
Merge pull request #466 from ad-m/dns-import
Add DNS import via nameserver & improve DNS importing
2 parents de8b593 + 06abdeb commit d4e7546

7 files changed

Lines changed: 118 additions & 63 deletions

File tree

bin/dns/recordTypes.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ module.exports = {
2222
to_bind: content => ({
2323
alias: content,
2424
}),
25-
to_content: record => record.alias,
25+
to_content: (record, name) => formatRecordName(record.alias, name),
2626
},
2727
txt: {
2828
value: 'some-text-value',
@@ -46,14 +46,14 @@ module.exports = {
4646
preference: content.split(' ')[0],
4747
host: content.split(' ')[1],
4848
}),
49-
to_content: record => `${record.preference} ${record.host}`,
49+
to_content: (record, name) => `${record.preference} ${formatRecordName(record.host, name)}`,
5050
},
5151
ns: {
5252
value: 'ns1.example.com',
5353
to_bind: content => ({
5454
host: content,
5555
}),
56-
to_content: (record, zone) => formatRecordName(record.host, zone.dnsName),
56+
to_content: (record, name) => formatRecordName(record.host, name),
5757
},
5858
srv: {
5959
value: '10 5 11 s1.example.com.',
@@ -63,7 +63,7 @@ module.exports = {
6363
port: content.split(' ')[2],
6464
target: content.split(' ')[3],
6565
}),
66-
to_content: record => `${record.priority} ${record.weight} ${record.port} ${record.target}`,
66+
to_content: (record, name) => `${record.priority} ${record.weight} ${record.port} ${formatRecordName(record.target, name)}`,
6767
},
6868
soa: {
6969
value: 'pns.hyperone.com. hostmaster.hyperone.com. 2018093002 15 180 1209600 1800',

bin/dns/tests.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,15 @@ ava.serial('dns punycode encoded', async t => {
301301
await tests.remove('dns zone', zone);
302302
});
303303

304+
ava.serial('dns create with probing', async t => {
305+
const value = '3.3.3.3';
306+
const name = `${tests.getName(t.title)}.${value}.xip.io.`;
307+
const zone = await tests.run(`dns zone create --type public --name ${name} --dns-probing`);
308+
t.true(zone.name === name);
309+
await test_record_values(t, zone, 'a', zone.dnsName, [value]);
310+
await tests.remove('dns zone', zone);
311+
});
312+
304313
ava.serial('dns resolve cname at apex', async t => {
305314
const ip = '2.2.2.2';
306315

bin/dns/zone/create/examples.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# Create zone
2+
13
```bash
2-
{{command_name}} --name my-domain.tld
4+
{{command_name}} --name my-domain.tld --type public
5+
```
6+
7+
# Create zone and probe current DNS nameserver to guess DNS records
8+
9+
```bash
10+
{{command_name}} --zone 'my-domain.tld' --type public --dns-probing
311
```

bin/dns/zone/import/examples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
```bash
22
{{command_name}} --zone 'my-domain.tld' --zone-file my-zone-export.txt
3-
```
3+
```

bin/dns/zone/import/index.js

Lines changed: 76 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -33,70 +33,92 @@ const supported_label = supported_types
3333
.map(x => x.toUpperCase())
3434
.join(', ');
3535

36+
const equalRrset = (a, b) => a.record.length == b.record.length &&
37+
a.record.every(x => b.record.some(y => y.content == x.content)) &&
38+
b.record.every(x => a.record.some(y => y.content == x.content));
39+
40+
const zonefile2rrset = (fname, dnsName) => {
41+
const local_zone = zonefile.parse(fs.readFileSync(fname, 'utf-8'));
42+
const rrset = [];
43+
for (const rrtype of supported_types) {
44+
const rrset_type = local_zone[rrtype.toLowerCase()] || [];
45+
rrset.push(...[...new Set(rrset_type.map(x => x.name))].map(name => {
46+
const records = rrset_type.filter(x => formatRecordName(x.name, dnsName) === formatRecordName(name, dnsName));
47+
const ttl = records.length >= 0 ? records[0].ttl || local_zone.$ttl : local_zone.$ttl;
48+
49+
return {
50+
name: formatRecordName(name, dnsName),
51+
type: rrtype.toUpperCase(),
52+
ttl,
53+
record: rrset_type.filter(x => x.name === name).map(x => ({
54+
content: recordTypes[rrtype].to_content(x, dnsName),
55+
enable: true,
56+
})),
57+
};
58+
}));
59+
}
60+
return rrset;
61+
};
62+
3663
module.exports = (resource) => Cli.createCommand('import', {
3764
description: `Import ${supported_label} records of ${resource.title} from BIND-compatible format`,
3865
plugins: resource.plugins,
3966
options: Object.assign({}, options, resource.options),
4067
dirname: __dirname,
41-
handler: (args) => args.helpers.api
42-
.get(`${args.$node.parent.config.url(args)}/${args.zone}`)
43-
.then(async remote_zone => {
44-
const local_zone = zonefile.parse(fs.readFileSync(args['zone-file'], 'utf-8'));
45-
46-
for (const type of supported_types) {
47-
const remote_rrset_names = new Set(remote_zone.recordset
48-
.filter(rrset => rrset.type === type.toUpperCase())
49-
.map(record => record.name));
68+
handler: async (args) => {
69+
Cli.mutually_exclusive_validate(args, 'zone-file', 'nameserver');
5070

51-
const local_rrset_type = local_zone[type.toLowerCase()] || [];
52-
const local_rrset_names = new Set(local_rrset_type.map(x => formatRecordName(x.name, remote_zone.dnsName)));
53-
const need_to_remove = set_difference(remote_rrset_names, local_rrset_names);
54-
// Delete
55-
if (args.delete) {
56-
for (const rrset_name of need_to_remove) {
57-
const rrset = remote_zone.recordset.find(x => x.type === type.toUpperCase() && formatRecordName(x.name, remote_zone.dnsName) === rrset_name);
58-
const url = `${resource.url(args)}/${args.zone}/recordset/${rrset.id}`;
59-
await args.helpers.api.delete(url);
60-
console.error(`Delete ${type.toUpperCase()} ${rrset_name}`);
61-
}
62-
}
71+
const remote_zone = await args.helpers.api.get(`${resource.url(args)}/${args.zone}`);
72+
const local_zone = zonefile2rrset(args['zone-file'], remote_zone.dnsName);
6373

64-
// Upsert
65-
const need_to_upsert = set_difference(local_rrset_names, need_to_remove);
66-
for (const rrset_name of need_to_upsert) {
67-
const records = local_rrset_type
68-
.filter(rrset => formatRecordName(rrset.name, remote_zone.dnsName) === rrset_name)
69-
.map(record => recordTypes[type].to_content(record, remote_zone))
70-
.map(content => ({
71-
content: content,
72-
disabled: false,
73-
}));
74+
for (const type of supported_types) {
75+
const remote_rrset_names = new Set(remote_zone.recordset
76+
.filter(rrset => rrset.type === type.toUpperCase())
77+
.map(rrset => rrset.name)
78+
);
79+
const local_rrset_names = new Set(local_zone
80+
.filter(rrset => rrset.type === type.toUpperCase())
81+
.map(rrset => rrset.name)
82+
);
83+
const need_to_remove = set_difference(remote_rrset_names, local_rrset_names);
7484

75-
const ttl = local_rrset_type.find(rrset => formatRecordName(rrset.name, remote_zone.dnsName) === rrset_name).ttl | local_zone.$ttl;
85+
// Delete
86+
if (args.delete) {
87+
for (const rrset_name of need_to_remove) {
88+
const rrset = remote_zone.recordset.find(x => x.type === type.toUpperCase() && x.name === rrset_name);
89+
const url = `${resource.url(args)}/${args.zone}/recordset/${rrset.id}`;
90+
await args.helpers.api.delete(url);
91+
console.error(`Delete ${type.toUpperCase()} ${rrset_name}`);
92+
}
93+
}
7694

77-
const data = {
78-
name: rrset_name,
79-
ttl: ttl,
80-
type: type.toUpperCase(),
81-
record: records,
82-
};
95+
// Upsert
96+
const need_to_upsert = set_difference(local_rrset_names, need_to_remove);
97+
for (const rrset_name of need_to_upsert) {
98+
const rrset = local_zone
99+
.find(rrset =>
100+
rrset.type === type.toUpperCase() && rrset.name == rrset_name
101+
);
83102

84-
const remote_rrset = remote_zone.recordset.find(rrset => rrset.type === type.toUpperCase() && rrset.name === rrset_name);
85-
if (remote_rrset) {
86-
// Update
87-
const url = `${resource.url(args)}/${args.zone}/recordset/${remote_rrset.id}`;
88-
await args.helpers.api.patch(url, data);
89-
console.error(`Update ${type.toUpperCase()} ${rrset_name}`);
90-
} else {
91-
// Add
92-
const url = `${resource.url(args)}/${args.zone}/recordset`;
93-
await args.helpers.api.post(url, data);
94-
console.error(`Add ${type.toUpperCase()} ${rrset_name}`);
95-
}
103+
const remote_rrset = remote_zone.recordset.find(rrset => rrset.type === type.toUpperCase() && rrset.name === rrset_name);
104+
if (remote_rrset && !equalRrset(rrset, remote_rrset)) {
105+
// Update
106+
const url = `${resource.url(args)}/${args.zone}/recordset/${remote_rrset.id}`;
107+
await args.helpers.api.patch(url, rrset);
108+
console.error(`Update ${type.toUpperCase()} ${rrset_name}`);
109+
} else if (!remote_rrset) {
110+
// Add
111+
const url = `${resource.url(args)}/${args.zone}/recordset`;
112+
await args.helpers.api.post(url, rrset);
113+
console.error(`Add ${type.toUpperCase()} ${rrset_name}`);
114+
} else {
115+
console.error(`Skip update ${type.toUpperCase()} ${rrset_name}`);
96116
}
97117
}
98-
return args.helpers.api
99-
.get(`${resource.url(args)}/${args.zone}`)
100-
.then(result => args.helpers.sendOutput(args, result));
101-
}),
118+
}
119+
120+
return args.helpers.api
121+
.get(`${resource.url(args)}/${args.zone}`)
122+
.then(result => args.helpers.sendOutput(args, result));
123+
},
102124
});

bin/dns/zone/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ const schema = {
2929
virtual: true,
3030
onCreate: true,
3131
},
32+
'dns-probing': {
33+
type: 'boolean',
34+
onCreate: true,
35+
destBody: 'source.dnsProbing',
36+
description: 'Probe current DNS nameserver to guess DNS records',
37+
},
3238
};
39+
3340
const resource = {
3441
name: 'zone',
3542
defaultQuery: '[].{id:id, name:name, type:flavour, dnsName:dnsName, state:state, tags:join(\',\',keys(tag || `{}`) ) }',

docs/dns.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,19 @@ Create DNS Zone
129129

130130
### Syntax
131131

132-
```h1 dns zone create | --name NAME --type TYPE [--tag TAG [--tag TAG ...]] [--dns-name DNS-NAME]```
133-
### Example
132+
```h1 dns zone create | --name NAME --type TYPE [--tag TAG [--tag TAG ...]] [--dns-name DNS-NAME] [--dns-probing]```
133+
### Examples
134+
135+
#### Create zone
134136

135137
```bash
136-
h1 dns zone create --name my-domain.tld
138+
h1 dns zone create --name my-domain.tld --type public
139+
```
140+
141+
#### Create zone and probe current DNS nameserver to guess DNS records
142+
143+
```bash
144+
h1 dns zone create --zone 'my-domain.tld' --type public --dns-probing
137145
```
138146

139147
### Required arguments
@@ -149,6 +157,7 @@ h1 dns zone create --name my-domain.tld
149157
| ---- | ------- | ----------- |
150158
| ```--tag TAG [--tag TAG ...]``` | | Key=value of tag. The parameter may occur repeatedly |
151159
| ```--dns-name DNS-NAME``` | | DNS zone name (zone name by default) |
160+
| ```--dns-probing``` | | Probe current DNS nameserver to guess DNS records |
152161

153162
## h1 dns zone rename
154163

0 commit comments

Comments
 (0)