@webda/aws
Version:
Webda AWS Services implementation
199 lines • 6.65 kB
JavaScript
import { Route53 } from "@aws-sdk/client-route-53";
import { JSONUtils, Service } from "@webda/core";
export class Route53Service extends Service {
/**
* Get the closest zone to the domain
*
* @param domain to get zone for
*/
static async getZoneForDomainName(domain) {
domain = this.completeDomain(domain);
let targetZone;
// Find the right zone
let r53 = new Route53({});
let res;
let params = {};
// Identify the right zone first
do {
res = await r53.listHostedZones(params);
for (let i in res.HostedZones) {
let zone = res.HostedZones[i];
if (domain.endsWith(zone.Name)) {
if (targetZone && targetZone.Name.length > zone.Name.length) {
// The previous target zone is closer to the domain
continue;
}
targetZone = zone;
}
}
params.Marker = res.NextMarker;
} while (!targetZone && res.NextMarker);
return targetZone;
}
static completeDomain(domain) {
if (!domain.endsWith(".")) {
domain = domain + ".";
}
return domain;
}
/**
* Create DNS entry
*
* @param domain to create
* @param type of DNS
* @param value the value of the record
* @param targetZone
*/
static async createDNSEntry(domain, type, value, targetZone = undefined, Comment = "@webda/aws-created") {
let r53 = new Route53({});
domain = this.completeDomain(domain);
if (!targetZone) {
targetZone = await this.getZoneForDomainName(domain);
}
if (!targetZone) {
throw Error(`Domain '${domain}' is not handled on AWS`);
}
await r53.changeResourceRecordSets({
HostedZoneId: targetZone.Id,
ChangeBatch: {
Changes: [
{
Action: "UPSERT",
ResourceRecordSet: {
Name: domain,
ResourceRecords: [
{
Value: value
}
],
TTL: 360,
Type: type
}
}
],
Comment
}
});
}
/**
* Return all entries of a zone in AWS
*
* @param domain to retrieve from
*/
static async getEntries(domain) {
let r53 = new Route53({});
const zone = await Route53Service.getZoneForDomainName(domain);
if (!zone) {
throw new Error(`Domain '${domain}' is not handled on AWS`);
}
const result = [];
let res;
do {
res = await r53.listResourceRecordSets({
HostedZoneId: zone.Id,
StartRecordIdentifier: res ? res.NextRecordIdentifier : undefined
});
result.push(...res.ResourceRecordSets);
} while (res.IsTruncated);
return result;
}
/**
* Import all records to Route53
* @param file
*/
static async import(options, Console) {
let data = JSONUtils.loadFile(options.file);
const targetZone = await this.getZoneForDomainName(data.domain);
const r53 = new Route53({});
if (!targetZone) {
throw Error(`Domain '${data.domain}' is not handled on AWS`);
}
try {
await r53.changeResourceRecordSets({
HostedZoneId: targetZone.Id,
ChangeBatch: {
Changes: data.entries
.filter(r => !(r.Type === "NS" && r.Name === targetZone.Name))
.map(r => {
if (!r.ResourceRecords.length) {
delete r.ResourceRecords;
}
return r;
})
.map(r => ({
Action: "UPSERT",
ResourceRecordSet: r
}))
}
});
if (!options.sync) {
return;
}
let continuationToken;
let toDelete = [];
do {
let records = await r53.listResourceRecordSets({
HostedZoneId: targetZone.Id,
StartRecordIdentifier: continuationToken
});
toDelete.push(...records.ResourceRecordSets.filter(r => {
return data.entries.findIndex(e => e.Name === r.Name && e.Type === r.Type) === -1;
}));
continuationToken = records.NextRecordIdentifier;
} while (continuationToken);
Console.log("INFO", `Deleting ${toDelete.length} records`);
if (options.pretend) {
toDelete.forEach(r => {
Console.log("INFO", "Deleting entry\n", JSON.stringify(r, undefined, 2));
});
}
else {
await r53.changeResourceRecordSets({
HostedZoneId: targetZone.Id,
ChangeBatch: {
Changes: toDelete.map(r => ({
Action: "DELETE",
ResourceRecordSet: r
}))
}
});
}
}
catch (err) {
Console.log("ERROR", err);
}
}
/**
* Export all records from Route53 into a file
*
* @param domain to export
* @param file to export to (can be .json or .yml)
*/
static async export(domain, file) {
JSONUtils.saveFile({
domain,
entries: await Route53Service.getEntries(domain)
}, file);
}
/**
* Manage the shell command
*
* @param Console
* @param args
*/
static async shell(Console, args) {
const command = args._.shift();
switch (command) {
case "import":
await this.import(args, Console);
break;
case "sync":
await this.import({ ...args, sync: true }, Console);
break;
case "export":
await this.export(args.domain, args.file);
break;
}
}
}
//# sourceMappingURL=route53.js.map