azure-cli
Version:
Microsoft Azure Cross Platform Command Line tool
558 lines (498 loc) • 16.4 kB
JavaScript
/*** Generated by streamline 0.10.17 (callbacks) - DO NOT EDIT ***//**
* Copyright (c) Microsoft. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __ = require('underscore');
var util = require('util');
var utils = require('../../../util/utils');
var constants = require('./constants');
var moment = require('moment');
function ZoneFile(output) {
this.output = output;
this.defaultTtl = 3600;
}
__.extend(ZoneFile.prototype, {
/**
* Returns JSON object which represents standard zone file structure.
*/
parse: function (zoneName, text) {
text = this.removeComments(text);
text = this.flatten(text);
return this.parseRRs(zoneName, text);
},
/**
* Generates standard zone file content from Azure DNS zone object.
*/
generate: function (resourceGroupName, zoneName, recordSets) {
var ttl = recordSets.recordSets[0].properties.ttl;
var fileData = '';
fileData = this.generateHeader(fileData, resourceGroupName, zoneName, ttl);
fileData = this.generateRRs(fileData, recordSets);
return fileData;
},
/**
* Parse methods
*/
flatten: function (text) {
var bracketsPattern = /\([\s\S]*?\)/gim;
var match = bracketsPattern.exec(text);
while (match !== null) {
match.replacement = match[0].replace(/\s+/gm, ' ').replace('(', '').replace(')', '').trim();
var textAsCharsArray = text.split('');
textAsCharsArray.splice(match.index, match[0].length, match.replacement); // replace multi-line chars with single-line instead
text = textAsCharsArray.join('');
bracketsPattern = /\([\s\S]*?\)/gim;
match = bracketsPattern.exec(text);
}
return text;
},
removeComments: function (text) {
return text.replace(/;[\s\S]*?$/gm, '');
},
parseRRs: function (zoneName, text) {
var self = this;
var res = {
sets: []
};
zoneName = zoneName.toLowerCase();
if (!utils.stringEndsWith(zoneName, '.', true)) zoneName += '.';
var rrs = text.split('\n');
for (var i in rrs) {
var rr = rrs[i];
if (!rr || !rr.trim()) {
continue;
}
var recordSet;
var prevRRname;
if (utils.stringStartsWith(rr, '$', true)) {
self.parseDirective(rr, res);
} else {
if (res.$origin === undefined) res.$origin = zoneName;
var baseRR = this.parseRR(rr, res.$origin, zoneName, res.$ttl, prevRRname);
if (baseRR.error) {
this.output.warn(baseRR.error);
prevRRname = baseRR.name;
continue;
}
switch (baseRR.type) {
case 'SOA':
recordSet = self.parseSOA(baseRR, res.$origin);
break;
case 'NS':
recordSet = self.parseNS(baseRR, res.$origin);
break;
case 'A':
recordSet = self.parseA(baseRR);
break;
case 'AAAA':
recordSet = self.parseAAAA(baseRR);
break;
case 'CNAME':
recordSet = self.parseCNAME(baseRR, res.$origin);
break;
case 'TXT':
recordSet = self.parseTXT(baseRR);
break;
case 'MX':
recordSet = self.parseMX(baseRR, res.$origin);
break;
case '%PTR': // note: PTR is not supported yet on server end
recordSet = self.parsePTR(baseRR, res.$origin);
break;
case 'SRV':
recordSet = self.parseSRV(baseRR, res.$origin);
break;
default:
this.output.warn(util.format('Record of unsupported type: %s', rr));
continue;
}
}
if (recordSet) {
var index = utils.indexOfCaseIgnore(res.sets, {name: recordSet.name, type: recordSet.type});
if (index === -1) {
res.sets.push(recordSet); // create new RecordSet
} else {
res.sets[index].records.push(recordSet.records[0]); // Use existing Record set
if (recordSet.ttl !== res.sets[index].ttl) {
var minttl = recordSet.ttl < res.sets[index].ttl ? recordSet.ttl : res.sets[index].ttl;
this.output.warn(util.format('The TTLs %s and %s for record set "%s" are conflicts, using lower TTL of %s',
recordSet.ttl, res.sets[index].ttl, recordSet.name, minttl));
res.sets[index].ttl = minttl;
}
}
prevRRname = recordSet.name;
recordSet = undefined;
}
}
return res;
},
parse$ORIGIN: function (rr) {
var self = this;
var origin = rr.split(/\s+/g)[1];
if (!utils.stringEndsWith(origin, '.', true)) {
self.output.warn(util.format('The value "%s" of $ORIGIN directive doesn\'t end with a dot, dot added', origin));
origin += '.';
}
return origin;
},
parse$TTL: function (rr) {
var value = rr.split(/\s+/g)[1];
var ttl = this.parseTimestamp(value);
return ttl;
},
/**
* Returns seconds number from 1m, 2h, 3d, 4m TTL time format
*/
parseTimestamp: function (str) {
str = str.toLowerCase();
var seconds = 0;
var chunk = '';
var prevChar;
for (var i = 0, currChar = ''; i < str.length; i++) {
currChar = str.charAt(i);
if (!isNaN(parseInt(currChar))) {
chunk += currChar;
} else {
if (i === 0) return undefined;
if (isNaN(parseInt(prevChar))) return undefined;
switch (currChar) {
case 's':
seconds += chunk * 1;
break;
case 'm':
seconds += chunk * 60;
break;
case 'h':
seconds += chunk * 3600;
break;
case 'd':
seconds += chunk * 86400;
break;
case 'w':
seconds += chunk * 604800;
break;
default:
return undefined;
}
chunk = '';
}
prevChar = currChar;
}
if (chunk.length > 0) seconds += parseInt(chunk);
return seconds;
},
parseRR: function (rr, $origin, zoneName, $ttl, prevRRname) {
var self = this;
var res = {
data: []
};
var validTypes = ['SOA', 'NS', 'A', 'AAAA', 'CNAME', 'TXT', 'MX', 'PTR', 'SRV'];
var validClasses = ['IN', 'CS', 'CH', 'HS'];
var rrTokens = rr.trim().split(/\s+/g);
if (rrTokens.length < 2) {
res.error = util.format('Invalid record format: %s', rr);
return res;
}
var position = 0;
while (rrTokens.length > 0) {
var token = rrTokens.shift();
if (position === 0) {
res.name = token;
} else if (validClasses.indexOf(token.toUpperCase()) !== -1 && (position <= 2)) {
res.class = token.toUpperCase();
} else if (validTypes.indexOf(token.toUpperCase()) !== -1 && (position <= 3)) {
res.type = token.toUpperCase();
} else if (self.parseTimestamp(token) && (position <= 1) && (res.type === undefined)) {
res.ttl = self.parseTimestamp(token);
} else {
res.data.push(token);
}
position++;
}
if (res.data.length < 1) {
res.error = util.format('Invalid record format: %s', rr);
return res;
}
if (!res.type) {
res.error = util.format('Record of unsupported type: %s', rr);
return res;
}
if (!res.ttl) res.ttl = $ttl === undefined ? this.defaultTtl : $ttl;
if (!res.name) res.name = prevRRname;
var fqdnName = self.convertToFQDN(res.name, $origin);
if (!utils.stringEndsWith(fqdnName, zoneName, true)) {
res.error = util.format('The record set with fully-qualified name "%s" does not match the zone name "%s", skipped', fqdnName, zoneName);
return res;
}
res.name = fqdnName;
return res;
},
parseDirective: function (rr, res) {
var self = this;
var uRR = rr.toUpperCase();
if (uRR.indexOf('$ORIGIN') === 0) {
res.$origin = self.parse$ORIGIN(rr);
} else if (uRR.indexOf('$TTL') === 0) {
res.$ttl = self.parse$TTL(rr);
} else {
self.output.warn(util.format('Unrecognized directive: %s', rr));
}
},
convertToFQDN: function (value, $origin) {
if (value === '@') return $origin;
if (utils.stringEndsWith(value, '.', true)) return value;
if (!$origin) {
this.output.warn('$ORIGIN directive is not defined');
return value;
} else {
return value + '.' + $origin;
}
},
parseSOA: function (rr, $origin) {
var self = this;
var soa = {
name: rr.name,
type: rr.type,
ttl: rr.ttl,
records: [{
host: rr.data[0],
email: self.convertToFQDN(rr.data[1], $origin),
serialNumber: self.parseTimestamp(rr.data[2]),
refreshTime: self.parseTimestamp(rr.data[3]),
retryTime: self.parseTimestamp(rr.data[4]),
expireTime: self.parseTimestamp(rr.data[5]),
minimumTtl: self.parseTimestamp(rr.data[6])
}]
};
return soa;
},
parseNS: function (rr, $origin) {
var self = this;
var ns = {
name: rr.name,
type: rr.type,
ttl: rr.ttl,
records: [{
nsdname: self.convertToFQDN(rr.data[0], $origin)
}]
};
return ns;
},
parseA: function (rr) {
var a = {
name: rr.name,
type: rr.type,
ttl: rr.ttl,
records: [{
ipv4Address: rr.data[0]
}]
};
return a;
},
parseAAAA: function (rr) {
var aaaa = {
name: rr.name,
type: rr.type,
ttl: rr.ttl,
records: [{
ipv6Address: rr.data[0]
}]
};
return aaaa;
},
parseCNAME: function (rr, $origin) {
var self = this;
var cname = {
name: rr.name,
type: rr.type,
ttl: rr.ttl,
records: [{
cname: self.convertToFQDN(rr.data[0], $origin)
}]
};
return cname;
},
parseMX: function (rr, $origin) {
var self = this;
var mx = {
name: rr.name,
type: rr.type,
ttl: rr.ttl,
records: [{
preference: parseInt(rr.data[0]),
exchange: self.convertToFQDN(rr.data[1], $origin)
}]
};
return mx;
},
parseTXT: function (rr) {
var value = '';
var plainText = rr.data.join(' ');
var matches = plainText.match(/[^"]+(?=(" ")|"$)/g);
if (matches) {
value = matches.join('');
} else {
value = plainText;
}
if (value.length > 255) {
value = value.substr(0, 255);
this.output.warn(util.format('TXT record "%s" value exceeds the maximum length of 255 chars, truncated', rr.name));
}
var txt = {
name: rr.name,
type: rr.type,
ttl: rr.ttl,
records: [{
value: value.trim()
}]
};
return txt;
},
parsePTR: function (rr, $origin) {
var self = this;
var ptr = {
name: rr.name,
type: rr.type,
ttl: rr.ttl,
records: [{
ptrdname: self.convertToFQDN(rr.data[0], $origin)
}]
};
return ptr;
},
parseSRV: function (rr, $origin) {
var self = this;
var srv = {
name: rr.name,
type: rr.type,
ttl: rr.ttl,
records: [{
priority: parseInt(rr.data[0]),
weight: parseInt(rr.data[1]),
port: parseInt(rr.data[2]),
target: self.convertToFQDN(rr.data[3], $origin)
}]
};
return srv;
},
/**
* Generate methods
*/
generateHeader: function (fileData, resourceGroupName, zoneName, ttl) {
fileData += '; Exported zone file from Azure DNS\r\n';
fileData += util.format('; Resource Group Name: %s\r\n', resourceGroupName);
fileData += util.format('; Zone name: %s\n', zoneName);
fileData += util.format('; Date and time (UTC): %s\r\n\r\n', moment.utc());
fileData += util.format('$TTL %s\r\n', ttl);
fileData += util.format('$ORIGIN %s.\r\n\r\n', zoneName);
return fileData;
},
generateRRs: function (fileData, sets) {
var self = this;
var index;
var soaSet = __.find(sets.recordSets, function (rset, rindex) {
if (rset.properties.soaRecord) {
index = rindex;
return true;
}
});
if (typeof index !== 'undefined') {
fileData += self.generateFromSet(soaSet, 'soaRecord', constants.dnsZone.SOA, soaSet.properties.ttl);
sets.recordSets.splice(index, 1);
}
sets.recordSets.forEach(function (rset) {
var ttl = rset.properties.ttl;
fileData += self.generateFromSet(rset, 'aRecords', constants.dnsZone.A, ttl);
fileData += self.generateFromSet(rset, 'aaaaRecords', constants.dnsZone.AAAA, ttl);
fileData += self.generateFromSet(rset, 'txtRecords', constants.dnsZone.TXT, ttl);
fileData += self.generateFromSet(rset, 'ptrRecords', constants.dnsZone.PTR, ttl);
fileData += self.generateFromSet(rset, 'mxRecords', constants.dnsZone.MX, ttl);
fileData += self.generateFromSet(rset, 'nsRecords', constants.dnsZone.NS, ttl);
fileData += self.generateFromSet(rset, 'srvRecords', constants.dnsZone.SRV, ttl);
fileData += self.generateFromSet(rset, 'cnameRecord', constants.dnsZone.CNAME, ttl);
});
return fileData;
},
generateFromSet: function (recordSet, propertyName, type, ttl) {
var self = this;
var fileData = '';
var obj = recordSet.properties[propertyName];
if (!__.isEmpty(obj)) {
if (Array.isArray(obj)) {
for (var i = 0; i < obj.length; i++) {
var record = obj[i];
var recordName;
if (i === 0) {
recordName = recordSet.name;
} else {
recordName = utils.setIndent(recordSet.name.length);
}
fileData += self.generateRR(recordName, type, ttl, record);
}
fileData += '\r\n';
} else {
fileData += self.generateRR(recordSet.name, type, ttl, obj);
fileData += '\r\n';
}
}
return fileData;
},
generateRR: function (name, type, ttl, record) {
var self = this;
var defClass = constants.dnsZone.recordClasses[0];
var rr = util.format('%s %s %s %s ', name, ttl, defClass, type);
switch (type) {
case constants.dnsZone.SOA:
var serialNumber = record.serialNumber || moment().format('YYYYMMDDss');
if (!utils.stringEndsWith(record.host, '.', true)) record.host += '.';
if (!utils.stringEndsWith(record.email, '.', true)) record.email += '.';
rr += util.format('%s %s (\r\n\t\t\t\t%s\r\n\t\t\t\t%s\r\n\t\t\t\t%s\r\n\t\t\t\t%s\r\n\t\t\t\t%s\r\n\t\t\t\t)', record.host, record.email,
serialNumber, record.refreshTime, record.retryTime, record.expireTime, record.minimumTtl);
break;
case constants.dnsZone.A:
rr += record.ipv4Address;
break;
case constants.dnsZone.AAAA:
rr += record.ipv6Address;
break;
case constants.dnsZone.CNAME:
rr += record.cname;
if (!utils.stringEndsWith(record.cname, '.', true)) rr += '.';
break;
case constants.dnsZone.MX:
rr += util.format('%s %s', record.preference, record.exchange);
if (!utils.stringEndsWith(record.exchange, '.', true)) rr += '.';
break;
case constants.dnsZone.NS:
rr += record.nsdname;
if (!utils.stringEndsWith(record.nsdname, '.', true)) rr += '.';
break;
case constants.dnsZone.SRV:
rr += util.format('%s %s %s %s', record.priority, record.weight, record.port, record.target);
if (!utils.stringEndsWith(record.target, '.', true)) rr += '.';
break;
case constants.dnsZone.TXT:
rr += util.format('"%s"', record.value);
break;
case constants.dnsZone.PTR:
rr += record.ptrdname;
break;
default:
self.output.warn(util.format('Records of type "%s" are not supported, record with name "%s" not exported', type, name));
return '';
}
rr += '\r\n';
return rr;
}
});
module.exports = ZoneFile;