diff --git a/src/aws.js b/src/aws.js index cb37ec673..213937344 100644 --- a/src/aws.js +++ b/src/aws.js @@ -6,10 +6,6 @@ exports = module.exports = { getSignedUploadUrl: getSignedUploadUrl, getSignedDownloadUrl: getSignedDownloadUrl, - addSubdomain: addSubdomain, - delSubdomain: delSubdomain, - getChangeStatus: getChangeStatus, - copyObject: copyObject }; @@ -17,28 +13,9 @@ var assert = require('assert'), AWS = require('aws-sdk'), config = require('./config.js'), debug = require('debug')('box:aws'), - settings = require('./settings.js'), SubdomainError = require('./subdomainerror.js'), superagent = require('superagent'); -function getDnsCredentials(callback) { - assert.strictEqual(typeof callback, 'function'); - - settings.getDnsConfig(function (error, dnsConfig) { - if (error) return callback(new SubdomainError(SubdomainError.INTERNAL_ERROR, error)); - - var credentials = { - accessKeyId: dnsConfig.accessKeyId, - secretAccessKey: dnsConfig.secretAccessKey, - region: dnsConfig.region - }; - - if (dnsConfig.endpoint) credentials.endpoint = new AWS.Endpoint(dnsConfig.endpoint); - - callback(null, credentials); - }); -} - function getBackupCredentials(callback) { assert.strictEqual(typeof callback, 'function'); @@ -122,165 +99,6 @@ function getSignedDownloadUrl(filename, callback) { }); } -function getZoneByName(zoneName, callback) { - assert.strictEqual(typeof zoneName, 'string'); - assert.strictEqual(typeof callback, 'function'); - - debug('getZoneByName: %s', zoneName); - - getDnsCredentials(function (error, credentials) { - if (error) return callback(error); - - var route53 = new AWS.Route53(credentials); - route53.listHostedZones({}, function (error, result) { - if (error) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, new Error(error))); - - var zone = result.HostedZones.filter(function (zone) { - return zone.Name.slice(0, -1) === zoneName; // aws zone name contains a '.' at the end - })[0]; - - if (!zone) return callback(new SubdomainError(SubdomainError.NOT_FOUND, 'no such zone')); - - debug('getZoneByName: found zone', zone); - - callback(null, zone); - }); - }); -} - -function addSubdomain(zoneName, subdomain, type, value, callback) { - assert.strictEqual(typeof zoneName, 'string'); - assert.strictEqual(typeof subdomain, 'string'); - assert.strictEqual(typeof type, 'string'); - assert.strictEqual(typeof value, 'string'); - assert.strictEqual(typeof callback, 'function'); - - debug('addSubdomain: ' + subdomain + ' for domain ' + zoneName + ' with value ' + value); - - getZoneByName(zoneName, function (error, zone) { - if (error) return callback(error); - - var fqdn = config.appFqdn(subdomain); - var params = { - ChangeBatch: { - Changes: [{ - Action: 'UPSERT', - ResourceRecordSet: { - Type: type, - Name: fqdn, - ResourceRecords: [{ - Value: value - }], - Weight: 0, - SetIdentifier: fqdn, - TTL: 1 - } - }] - }, - HostedZoneId: zone.Id - }; - - getDnsCredentials(function (error, credentials) { - if (error) return callback(error); - - var route53 = new AWS.Route53(credentials); - route53.changeResourceRecordSets(params, function(error, result) { - if (error && error.code === 'PriorRequestNotComplete') { - return callback(new SubdomainError(SubdomainError.STILL_BUSY, error.message)); - } else if (error) { - return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, error.message)); - } - - debug('addSubdomain: success. changeInfoId:%j', result); - - callback(null, result.ChangeInfo.Id); - }); - }); - }); -} - -function delSubdomain(zoneName, subdomain, type, value, callback) { - assert.strictEqual(typeof zoneName, 'string'); - assert.strictEqual(typeof subdomain, 'string'); - assert.strictEqual(typeof type, 'string'); - assert.strictEqual(typeof value, 'string'); - assert.strictEqual(typeof callback, 'function'); - - debug('delSubdomain: %s for domain %s.', subdomain, zoneName); - - getZoneByName(zoneName, function (error, zone) { - if (error) return callback(error); - - var fqdn = config.appFqdn(subdomain); - var resourceRecordSet = { - Name: fqdn, - Type: type, - ResourceRecords: [{ - Value: value - }], - Weight: 0, - SetIdentifier: fqdn, - TTL: 1 - }; - - var params = { - ChangeBatch: { - Changes: [{ - Action: 'DELETE', - ResourceRecordSet: resourceRecordSet - }] - }, - HostedZoneId: zone.Id - }; - - getDnsCredentials(function (error, credentials) { - if (error) return callback(error); - - var route53 = new AWS.Route53(credentials); - route53.changeResourceRecordSets(params, function(error, result) { - if (error && error.message && error.message.indexOf('it was not found') !== -1) { - debug('delSubdomain: resource record set not found.', error); - return callback(new SubdomainError(SubdomainError.NOT_FOUND, new Error(error))); - } else if (error && error.code === 'NoSuchHostedZone') { - debug('delSubdomain: hosted zone not found.', error); - return callback(new SubdomainError(SubdomainError.NOT_FOUND, new Error(error))); - } else if (error && error.code === 'PriorRequestNotComplete') { - debug('delSubdomain: resource is still busy', error); - return callback(new SubdomainError(SubdomainError.STILL_BUSY, new Error(error))); - } else if (error && error.code === 'InvalidChangeBatch') { - debug('delSubdomain: invalid change batch. No such record to be deleted.'); - return callback(new SubdomainError(SubdomainError.NOT_FOUND, new Error(error))); - } else if (error) { - debug('delSubdomain: error', error); - return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, new Error(error))); - } - - debug('delSubdomain: success'); - - callback(null); - }); - }); - }); -} - -function getChangeStatus(changeId, callback) { - assert.strictEqual(typeof changeId, 'string'); - assert.strictEqual(typeof callback, 'function'); - - if (changeId === '') return callback(null, 'INSYNC'); - - getDnsCredentials(function (error, credentials) { - if (error) return callback(error); - - var route53 = new AWS.Route53(credentials); - route53.getChange({ Id: changeId }, function (error, result) { - if (error) return callback(error); - - callback(null, result.ChangeInfo.Status); - }); - }); -} - function copyObject(from, to, callback) { assert.strictEqual(typeof from, 'string'); assert.strictEqual(typeof to, 'string'); diff --git a/src/caas.js b/src/dns/caas.js similarity index 95% rename from src/caas.js rename to src/dns/caas.js index a1a0290b1..c3eae563c 100644 --- a/src/caas.js +++ b/src/dns/caas.js @@ -9,9 +9,9 @@ exports = module.exports = { }; var assert = require('assert'), - config = require('./config.js'), - debug = require('debug')('box:caas'), - SubdomainError = require('./subdomainerror.js'), + config = require('../config.js'), + debug = require('debug')('box:dns/caas'), + SubdomainError = require('../subdomainerror.js'), superagent = require('superagent'), util = require('util'); diff --git a/src/dns/route53.js b/src/dns/route53.js new file mode 100644 index 000000000..f79622ff7 --- /dev/null +++ b/src/dns/route53.js @@ -0,0 +1,193 @@ +/* jslint node:true */ + +'use strict'; + +exports = module.exports = { + addSubdomain: addSubdomain, + delSubdomain: delSubdomain, + getChangeStatus: getChangeStatus +}; + +var assert = require('assert'), + AWS = require('aws-sdk'), + config = require('../config.js'), + debug = require('debug')('box:dns/route53'), + settings = require('../settings.js'), + SubdomainError = require('../subdomainerror.js'); + +function getDnsCredentials(callback) { + assert.strictEqual(typeof callback, 'function'); + + settings.getDnsConfig(function (error, dnsConfig) { + if (error) return callback(new SubdomainError(SubdomainError.INTERNAL_ERROR, error)); + + var credentials = { + accessKeyId: dnsConfig.accessKeyId, + secretAccessKey: dnsConfig.secretAccessKey, + region: dnsConfig.region + }; + + if (dnsConfig.endpoint) credentials.endpoint = new AWS.Endpoint(dnsConfig.endpoint); + + callback(null, credentials); + }); +} + +function getZoneByName(zoneName, callback) { + assert.strictEqual(typeof zoneName, 'string'); + assert.strictEqual(typeof callback, 'function'); + + debug('getZoneByName: %s', zoneName); + + getDnsCredentials(function (error, credentials) { + if (error) return callback(error); + + var route53 = new AWS.Route53(credentials); + route53.listHostedZones({}, function (error, result) { + if (error) return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, new Error(error))); + + var zone = result.HostedZones.filter(function (zone) { + return zone.Name.slice(0, -1) === zoneName; // aws zone name contains a '.' at the end + })[0]; + + if (!zone) return callback(new SubdomainError(SubdomainError.NOT_FOUND, 'no such zone')); + + debug('getZoneByName: found zone', zone); + + callback(null, zone); + }); + }); +} + +function addSubdomain(zoneName, subdomain, type, value, callback) { + assert.strictEqual(typeof zoneName, 'string'); + assert.strictEqual(typeof subdomain, 'string'); + assert.strictEqual(typeof type, 'string'); + assert.strictEqual(typeof value, 'string'); + assert.strictEqual(typeof callback, 'function'); + + debug('addSubdomain: ' + subdomain + ' for domain ' + zoneName + ' with value ' + value); + + getZoneByName(zoneName, function (error, zone) { + if (error) return callback(error); + + var fqdn = config.appFqdn(subdomain); + var params = { + ChangeBatch: { + Changes: [{ + Action: 'UPSERT', + ResourceRecordSet: { + Type: type, + Name: fqdn, + ResourceRecords: [{ + Value: value + }], + Weight: 0, + SetIdentifier: fqdn, + TTL: 1 + } + }] + }, + HostedZoneId: zone.Id + }; + + getDnsCredentials(function (error, credentials) { + if (error) return callback(error); + + var route53 = new AWS.Route53(credentials); + route53.changeResourceRecordSets(params, function(error, result) { + if (error && error.code === 'PriorRequestNotComplete') { + return callback(new SubdomainError(SubdomainError.STILL_BUSY, error.message)); + } else if (error) { + return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, error.message)); + } + + debug('addSubdomain: success. changeInfoId:%j', result); + + callback(null, result.ChangeInfo.Id); + }); + }); + }); +} + +function delSubdomain(zoneName, subdomain, type, value, callback) { + assert.strictEqual(typeof zoneName, 'string'); + assert.strictEqual(typeof subdomain, 'string'); + assert.strictEqual(typeof type, 'string'); + assert.strictEqual(typeof value, 'string'); + assert.strictEqual(typeof callback, 'function'); + + debug('delSubdomain: %s for domain %s.', subdomain, zoneName); + + getZoneByName(zoneName, function (error, zone) { + if (error) return callback(error); + + var fqdn = config.appFqdn(subdomain); + var resourceRecordSet = { + Name: fqdn, + Type: type, + ResourceRecords: [{ + Value: value + }], + Weight: 0, + SetIdentifier: fqdn, + TTL: 1 + }; + + var params = { + ChangeBatch: { + Changes: [{ + Action: 'DELETE', + ResourceRecordSet: resourceRecordSet + }] + }, + HostedZoneId: zone.Id + }; + + getDnsCredentials(function (error, credentials) { + if (error) return callback(error); + + var route53 = new AWS.Route53(credentials); + route53.changeResourceRecordSets(params, function(error, result) { + if (error && error.message && error.message.indexOf('it was not found') !== -1) { + debug('delSubdomain: resource record set not found.', error); + return callback(new SubdomainError(SubdomainError.NOT_FOUND, new Error(error))); + } else if (error && error.code === 'NoSuchHostedZone') { + debug('delSubdomain: hosted zone not found.', error); + return callback(new SubdomainError(SubdomainError.NOT_FOUND, new Error(error))); + } else if (error && error.code === 'PriorRequestNotComplete') { + debug('delSubdomain: resource is still busy', error); + return callback(new SubdomainError(SubdomainError.STILL_BUSY, new Error(error))); + } else if (error && error.code === 'InvalidChangeBatch') { + debug('delSubdomain: invalid change batch. No such record to be deleted.'); + return callback(new SubdomainError(SubdomainError.NOT_FOUND, new Error(error))); + } else if (error) { + debug('delSubdomain: error', error); + return callback(new SubdomainError(SubdomainError.EXTERNAL_ERROR, new Error(error))); + } + + debug('delSubdomain: success'); + + callback(null); + }); + }); + }); +} + +function getChangeStatus(changeId, callback) { + assert.strictEqual(typeof changeId, 'string'); + assert.strictEqual(typeof callback, 'function'); + + if (changeId === '') return callback(null, 'INSYNC'); + + getDnsCredentials(function (error, credentials) { + if (error) return callback(error); + + var route53 = new AWS.Route53(credentials); + route53.getChange({ Id: changeId }, function (error, result) { + if (error) return callback(error); + + callback(null, result.ChangeInfo.Status); + }); + }); +} diff --git a/src/subdomains.js b/src/subdomains.js index 770f4fdbe..7e3b88187 100644 --- a/src/subdomains.js +++ b/src/subdomains.js @@ -4,8 +4,8 @@ var assert = require('assert'), async = require('async'), - aws = require('./aws.js'), - caas = require('./caas.js'), + route53 = require('./dns/route53.js'), + caas = require('./dns/caas.js'), config = require('./config.js'), debug = require('debug')('box:subdomains'), util = require('util'), @@ -18,10 +18,9 @@ module.exports = exports = { status: status }; -// choose which subdomain backend we use -// for test purpose we use aws +// choose which subdomain backend we use for test purpose we use route53 function api() { - return config.isCustomDomain() || config.TEST ? aws : caas; + return config.isCustomDomain() || config.TEST ? route53 : caas; } function add(record, callback) {