Skip to content

Commit 9d5d9ec

Browse files
authored
Merge pull request #474 from hyperonecom/develop
Release version 1.9.0
2 parents 2bf17b0 + d4e7546 commit 9d5d9ec

38 files changed

+839
-389
lines changed

.npmignore

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,5 @@
1-
docs
2-
scripts
3-
monitoring
1+
*
42

5-
# Build files
6-
dist
7-
*.tgz
8-
9-
# Common files
10-
Dockerfile
11-
*.log
12-
logs
13-
.travis.*
14-
.gitattributes
15-
.eslintrc
16-
.eslintrc.js
17-
.dockerignore
18-
.idea
19-
.DS_Store
3+
!bin/**
4+
!lib/**
5+
!scope/**

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ language: node_js
22

33
node_js: '12'
44
dist: xenial
5+
os: linux
56

67
services:
78
- docker
@@ -27,6 +28,5 @@ jobs:
2728
- ./scripts/test_version_output.sh ./h1 alpine
2829
- rm ./h1
2930
- stage: build & deploy
30-
sudo: required
3131
script:
3232
- ./scripts/travis_deploy.sh
File renamed without changes.

bin/disk/create/examples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# Create disk from .vhdx file
88

99
```bash
10-
{{command_name}} --name new-disk --size 1 --type ssd --source-file ./my-disk.vhdx
10+
{{command_name}} --name new-disk --type ssd --source-file ./my-disk.vhdx
1111
```
1212

1313
# Clone disk

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: 19 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

@@ -327,3 +336,13 @@ ava.serial('dns resolve cname at apex', async t => {
327336

328337
await tests.remove('dns zone', zone);
329338
});
339+
340+
ava.serial('dns NS record-set match nameservers', async t => {
341+
const zone = await tests.run(`dns zone create --type public --name ${tests.getName(t.title)}.com`);
342+
try {
343+
await test_record_values(t, zone, 'ns', zone.dnsName, zone.nameserver.map(x => `${x}.`));
344+
} finally {
345+
await tests.remove('dns zone', zone);
346+
347+
}
348+
});

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 || `{}`) ) }',

bin/generic/access/user/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ module.exports = (parent) => {
2727
const resource = {
2828
title: `access rights for ${parent.title}`,
2929
description: `Manage your ${parent.title} access rights`,
30-
defaultQuery: '[].{id:id,role:role}',
30+
defaultQuery: '[].{id:id,role:role,state:state}',
3131
url: args => `${parent.url(args)}/${args[parent.name]}/accessrights`,
3232
options: options,
3333
plugins: defaults.plugins,

bin/index.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,31 @@ const Cli = require('lib/cli');
88
const epilog = require('lib/epilog');
99
const config = require('lib/config');
1010

11+
const updateNotifier = require('update-notifier');
12+
1113
const Package = require('../package.json');
14+
1215
const scope = process.env.SCOPE_NAME;
1316

17+
const notifier = updateNotifier({ pkg: Package });
18+
if (notifier.update) {
19+
const chalk = require('chalk');
20+
const boxen = require('boxen');
21+
const message = [
22+
`Update available: ${chalk.dim(notifier.update.current)} ${chalk.reset(' → ')} ${chalk.green(notifier.update.latest)}`,
23+
'Read documentation how to install the latest version:',
24+
chalk.cyan('https://www.hyperone.com/tools/cli/guides/installation.html'),
25+
].join('\n');
26+
console.error(`${boxen(message, {
27+
padding: 1,
28+
margin: 1,
29+
align: 'center',
30+
borderColor: 'yellow',
31+
borderStyle: 'round',
32+
})}`);
33+
}
34+
35+
1436
const cli = Cli.createCategory(scope, {
1537
description: Package.description,
1638
version: Package.version,

bin/login/index.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ const Cli = require('lib/cli');
44

55
const logger = require('lib/logger');
66
const interactive = require('lib/interactive');
7+
const config = require('lib/config');
8+
9+
const active_user = config.get_active_user();
710

811
const options = {
912
username: {
1013
description: 'Your username',
1114
type: 'string',
12-
required: true,
15+
required: !active_user,
1316
},
1417
password: {
1518
description: 'Password',
@@ -19,11 +22,11 @@ const options = {
1922

2023
const handler = async args => {
2124
let p;
22-
25+
const username = args.username || active_user;
2326
if (args.password) {
24-
p = args.helpers.api.getApiKey(args.username, { password: args.password });
27+
p = args.helpers.api.getApiKey(username, { password: args.password });
2528
} else {
26-
p = args.helpers.api.getApiKeySSH(args.username)
29+
p = args.helpers.api.getApiKeySSH(username)
2730
.catch(err => {
2831
if (err.message.includes('host fingerprint verification failed')) {
2932
throw Cli.error.serverError(err.message);
@@ -34,7 +37,7 @@ const handler = async args => {
3437
name: 'value',
3538
validate: input => input.length === 0 ? 'Incorrect password' : true,
3639
})
37-
.then(password => args.helpers.api.getApiKey(args.username, { password: password.value }));
40+
.then(password => args.helpers.api.getApiKey(username, { password: password.value }));
3841
});
3942
}
4043

bin/organisation/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const resource = {
1212
require('bin/_plugins/api'),
1313
],
1414
title: 'Organisation',
15-
commands: ['show', 'history', 'access/user', 'list', 'rename', 'tag', 'payment'],
15+
commands: ['show', 'history', 'access/user', 'list', 'rename', 'tag', 'payment', 'delete'],
1616
};
1717

1818
const category = genericResource(resource);

0 commit comments

Comments
 (0)