cloudns-cloudformation-sync 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.prettierrc ADDED
@@ -0,0 +1,5 @@
1
+ trailingComma: es5
2
+ tabWidth: 2
3
+ semi: false
4
+ singleQuote: true
5
+ printWidth: 160
package/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # ClouDNS CloudFormation Sync
2
- Copyright (C) Clouden Oy 2020, author Kenneth Falck <kennu@clouden.net>.
2
+
3
+ Copyright (C) Clouden Oy 2020-2024, author Kenneth Falck <kennu@clouden.net>.
3
4
 
4
5
  Released under the MIT license.
5
6
 
@@ -29,7 +30,7 @@ Other resource types are also allowed (A, AAAA, ALIAS, etc).
29
30
 
30
31
  Use the cloudns-cloudformation-sync command to synchronize ClouDNS records.
31
32
 
32
- AWS_PROFILE=xxx cloudns-cloudformation-sync <cloudns-username> <cloudns-password-parameter-name> [ttl]
33
+ AWS_PROFILE=xxx cloudns-cloudformation-sync <cloudns-username> <cloudns-password-parameter-name> [ttl [stackname...]]
33
34
 
34
35
  Options:
35
36
 
@@ -37,5 +38,6 @@ Options:
37
38
  <cloudns-username> - ClouDNS API sub-auth-user
38
39
  <cloudns-password-parameter-name> - SSM Parameter with the encrypted ClouDNS API password
39
40
  [ttl] - Optional TTL for generated records (defaults to 300)
41
+ [stackName...] - Optional CloudFormation stack name(s) to limit the exports to scan (defaults to all stacks)
40
42
 
41
43
  You can create your ClouDNS API credentials in the ClouDNS management console.
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.main = void 0;
4
4
  /**
5
5
  * Read AWS CloudFormation Exports and autogenerate ClouDNS records based on their names and values.
6
- * Kenneth Falck <kennu@clouden.net> (C) Clouden Oy 2023
6
+ * Kenneth Falck <kennu@clouden.net> (C) Clouden Oy 2020-2024
7
7
  *
8
8
  * This tool can be used to autogenerate ClouDNS records for CloudFormation resources like
9
9
  * CloudFront distributions and API Gateway domains.
@@ -19,12 +19,13 @@ exports.main = void 0;
19
19
  *
20
20
  * Other resource types are also allowed (A, AAAA, ALIAS, etc).
21
21
  *
22
- * Command line usage: AWS_PROFILE=xxx ts-node cloudns-cloudformation-sync.ts <cloudns-username> <cloudns-password-parameter-name> [ttl]
22
+ * Command line usage: AWS_PROFILE=xxx ts-node cloudns-cloudformation-sync.ts <cloudns-username> <cloudns-password-parameter-name> [ttl [stackName...]]
23
23
  *
24
24
  * AWS_PROFILE=xxx - Specify your AWS profile in ~/.aws/credentials as an environment variable
25
25
  * <cloudns-username> - ClouDNS API sub-auth-user
26
26
  * <cloudns-password-parameter-name> - SSM Parameter with the encrypted ClouDNS API password
27
27
  * [ttl] - Optional TTL for generated records (defaults to 300)
28
+ * [stackName...] - Optional CloudFormation stack name(s) to limit the exports to scan (defaults to all stacks)
28
29
  */
29
30
  const client_ssm_1 = require("@aws-sdk/client-ssm");
30
31
  const client_cloudformation_1 = require("@aws-sdk/client-cloudformation");
@@ -33,17 +34,20 @@ const querystring = require("querystring");
33
34
  // Load ~/.aws/config
34
35
  process.env.AWS_SDK_LOAD_CONFIG = '1';
35
36
  async function cloudnsRestCall(cloudnsUsername, cloudnsPassword, method, relativeUrl, queryOptions) {
36
- let fullUrl = 'https://api.cloudns.net' + relativeUrl + '?' + querystring.stringify(Object.assign({
37
- 'sub-auth-user': cloudnsUsername,
38
- 'auth-password': cloudnsPassword,
39
- }, queryOptions || {}));
37
+ let fullUrl = 'https://api.cloudns.net' +
38
+ relativeUrl +
39
+ '?' +
40
+ querystring.stringify(Object.assign({
41
+ 'sub-auth-user': cloudnsUsername,
42
+ 'auth-password': cloudnsPassword,
43
+ }, queryOptions || {}));
40
44
  // console.log('Note: Calling', fullUrl)
41
45
  const response = await (0, node_fetch_1.default)(fullUrl, {
42
46
  method: method,
43
47
  headers: {
44
48
  'Content-Type': 'application/json',
45
- 'Accept': 'application/json',
46
- }
49
+ Accept: 'application/json',
50
+ },
47
51
  });
48
52
  if (!response.ok) {
49
53
  const errorText = await response.text();
@@ -61,18 +65,20 @@ async function autoDetectCloudnsHostAndZone(cloudnsUsername, cloudnsPassword, na
61
65
  const hostName2 = nameParts.slice(0, nameParts.length - 3).join('.');
62
66
  const zoneName2 = nameParts.slice(nameParts.length - 3).join('.');
63
67
  // Check which zone exists
64
- const zoneResponse1 = zoneCache[zoneName1] || await cloudnsRestCall(cloudnsUsername, cloudnsPassword, 'GET', '/dns/get-zone-info.json', {
65
- 'domain-name': zoneName1,
66
- });
68
+ const zoneResponse1 = zoneCache[zoneName1] ||
69
+ (await cloudnsRestCall(cloudnsUsername, cloudnsPassword, 'GET', '/dns/get-zone-info.json', {
70
+ 'domain-name': zoneName1,
71
+ }));
67
72
  zoneCache[zoneName1] = zoneResponse1;
68
- const zoneResponse2 = zoneCache[zoneName2] || await cloudnsRestCall(cloudnsUsername, cloudnsPassword, 'GET', '/dns/get-zone-info.json', {
69
- 'domain-name': zoneName2,
70
- });
73
+ const zoneResponse2 = zoneCache[zoneName2] ||
74
+ (await cloudnsRestCall(cloudnsUsername, cloudnsPassword, 'GET', '/dns/get-zone-info.json', {
75
+ 'domain-name': zoneName2,
76
+ }));
71
77
  zoneCache[zoneName2] = zoneResponse2;
72
78
  // console.log('Note: Response for host', hostName1, 'in zone', zoneName1, ':', zoneResponse1)
73
79
  // console.log('Note: Response for host', hostName2, 'in zone', zoneName2, ':', zoneResponse2)
74
- const zoneName = (zoneResponse1.status === '1' ? zoneName1 : zoneResponse2.status === '1' ? zoneName2 : '');
75
- const hostName = (zoneResponse1.status === '1' ? hostName1 : zoneResponse2.status === '1' ? hostName2 : '');
80
+ const zoneName = zoneResponse1.status === '1' ? zoneName1 : zoneResponse2.status === '1' ? zoneName2 : '';
81
+ const hostName = zoneResponse1.status === '1' ? hostName1 : zoneResponse2.status === '1' ? hostName2 : '';
76
82
  if (!zoneName) {
77
83
  // Neither zone exists
78
84
  throw new Error('Zone Not Found: ' + name);
@@ -87,8 +93,8 @@ async function createOrUpdateCloudnsResource(cloudnsUsername, cloudnsPassword, n
87
93
  // Does the record exist?
88
94
  const recordsResponse = await cloudnsRestCall(cloudnsUsername, cloudnsPassword, 'GET', '/dns/records.json', {
89
95
  'domain-name': zoneName,
90
- 'host': hostName,
91
- 'type': type,
96
+ host: hostName,
97
+ type: type,
92
98
  });
93
99
  const existingRecord = Object.values(recordsResponse)[0];
94
100
  if ((existingRecord === null || existingRecord === void 0 ? void 0 : existingRecord.host) === hostName && (existingRecord === null || existingRecord === void 0 ? void 0 : existingRecord.type) === type && (existingRecord === null || existingRecord === void 0 ? void 0 : existingRecord.ttl) === ttlValue && (existingRecord === null || existingRecord === void 0 ? void 0 : existingRecord.record) === value) {
@@ -101,10 +107,10 @@ async function createOrUpdateCloudnsResource(cloudnsUsername, cloudnsPassword, n
101
107
  const result = await cloudnsRestCall(cloudnsUsername, cloudnsPassword, 'POST', '/dns/mod-record.json', {
102
108
  'domain-name': zoneName,
103
109
  'record-id': existingRecord === null || existingRecord === void 0 ? void 0 : existingRecord.id,
104
- 'host': hostName,
110
+ host: hostName,
105
111
  'record-type': type,
106
- 'record': value,
107
- 'ttl': ttlValue,
112
+ record: value,
113
+ ttl: ttlValue,
108
114
  });
109
115
  if (result.status === 'Failed') {
110
116
  throw new Error('Modify record failed: ' + (result.statusMessage || result.statusDescription));
@@ -115,10 +121,10 @@ async function createOrUpdateCloudnsResource(cloudnsUsername, cloudnsPassword, n
115
121
  console.log('CREATE', name, type, ttlValue, value, 'ZONE', zoneName, 'HOST', hostName);
116
122
  const result = await cloudnsRestCall(cloudnsUsername, cloudnsPassword, 'POST', '/dns/add-record.json', {
117
123
  'domain-name': zoneName,
118
- 'host': hostName,
124
+ host: hostName,
119
125
  'record-type': type,
120
- 'record': value,
121
- 'ttl': ttlValue,
126
+ record: value,
127
+ ttl: ttlValue,
122
128
  });
123
129
  if (result.status === 'Failed') {
124
130
  throw new Error('Add record failed: ' + (result.statusMessage || result.statusDescription));
@@ -126,17 +132,18 @@ async function createOrUpdateCloudnsResource(cloudnsUsername, cloudnsPassword, n
126
132
  }
127
133
  }
128
134
  async function main() {
129
- var _a, _b;
130
- console.log('ClouDNS CloudFormation Sync by Kenneth Falck <kennu@clouden.net> (C) Clouden Oy 2023');
135
+ var _a, _b, _c;
136
+ console.log('ClouDNS CloudFormation Sync by Kenneth Falck <kennu@clouden.net> (C) Clouden Oy 2020-2024');
131
137
  const cloudnsUsername = process.argv[2];
132
138
  const cloudnsPasswordParameter = process.argv[3];
133
139
  const ttlValue = process.argv[4] || '300';
140
+ const stackNames = process.argv.slice(5);
134
141
  if (!cloudnsUsername) {
135
- console.error('Usage: cloudns-cloudformation-sync <cloudns-username> <cloudns-password-parameter-name> [ttl]');
142
+ console.error('Usage: cloudns-cloudformation-sync <cloudns-username> <cloudns-password-parameter-name> [ttl [stackName...]]');
136
143
  process.exit(1);
137
144
  }
138
145
  if (!cloudnsPasswordParameter) {
139
- console.error('Usage: cloudns-cloudformation-sync <cloudns-username> <cloudns-password-parameter-name> [ttl]');
146
+ console.error('Usage: cloudns-cloudformation-sync <cloudns-username> <cloudns-password-parameter-name> [ttl [stackName...]]');
140
147
  process.exit(1);
141
148
  }
142
149
  const ssm = new client_ssm_1.SSMClient({});
@@ -153,7 +160,15 @@ async function main() {
153
160
  NextToken: nextToken,
154
161
  }));
155
162
  for (const exportObj of response.Exports || []) {
156
- if ((_b = exportObj.Name) === null || _b === void 0 ? void 0 : _b.match(/^ClouDNS:/)) {
163
+ if (stackNames.length && !stackNames.includes(exportObj.ExportingStackId || '')) {
164
+ // Check if the name part of the ID matches arn:aws:cloudformation:eu-west-1:<xxx>:stack/<name>/<xxx>
165
+ const m = (_b = exportObj.ExportingStackId) === null || _b === void 0 ? void 0 : _b.match(/^arn:[^:]+:cloudformation:[^:]+:[^:]+:stack\/([^\/]+)\//);
166
+ if (!m || !stackNames.includes(m[1])) {
167
+ // Stack ID name part didn't match given stackName, so skip it
168
+ continue;
169
+ }
170
+ }
171
+ if ((_c = exportObj.Name) === null || _c === void 0 ? void 0 : _c.match(/^ClouDNS:/)) {
157
172
  const nameParts = exportObj.Name.split(':');
158
173
  const resourceType = nameParts[1];
159
174
  const resourceName = nameParts.slice(2).join('.');
@@ -165,4 +180,4 @@ async function main() {
165
180
  } while (nextToken);
166
181
  }
167
182
  exports.main = main;
168
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xvdWRucy1jbG91ZGZvcm1hdGlvbi1zeW5jLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2Nsb3VkbnMtY2xvdWRmb3JtYXRpb24tc3luYy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBd0JHO0FBQ0gsb0RBQW9FO0FBQ3BFLDBFQUE0RztBQUM1RywyQ0FBOEI7QUFDOUIsMkNBQTBDO0FBRTFDLHFCQUFxQjtBQUNyQixPQUFPLENBQUMsR0FBRyxDQUFDLG1CQUFtQixHQUFHLEdBQUcsQ0FBQTtBQUVyQyxLQUFLLFVBQVUsZUFBZSxDQUFDLGVBQXVCLEVBQUUsZUFBdUIsRUFBRSxNQUFjLEVBQUUsV0FBbUIsRUFBRSxZQUFpQjtJQUNySSxJQUFJLE9BQU8sR0FBRyx5QkFBeUIsR0FBRyxXQUFXLEdBQUcsR0FBRyxHQUFHLFdBQVcsQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQztRQUNoRyxlQUFlLEVBQUUsZUFBZTtRQUNoQyxlQUFlLEVBQUUsZUFBZTtLQUNqQyxFQUFFLFlBQVksSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFBO0lBRXZCLHdDQUF3QztJQUV4QyxNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUEsb0JBQUssRUFBQyxPQUFPLEVBQUU7UUFDcEMsTUFBTSxFQUFFLE1BQU07UUFDZCxPQUFPLEVBQUU7WUFDUCxjQUFjLEVBQUUsa0JBQWtCO1lBQ2xDLFFBQVEsRUFBRSxrQkFBa0I7U0FDN0I7S0FDRixDQUFDLENBQUE7SUFDRixJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsRUFBRTtRQUNoQixNQUFNLFNBQVMsR0FBRyxNQUFNLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtRQUN2QyxPQUFPLENBQUMsS0FBSyxDQUFDLFlBQVksRUFBRSxRQUFRLENBQUMsTUFBTSxFQUFFLFFBQVEsQ0FBQyxVQUFVLEVBQUUsU0FBUyxDQUFDLENBQUE7UUFDNUUsTUFBTSxJ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TSxTQUFTLEdBQUcsU0FBUyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUE7Z0JBQzNDLE1BQU0sWUFBWSxHQUFHLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtnQkFDakMsTUFBTSxZQUFZLEdBQUcsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUE7Z0JBQ2pELE1BQU0sYUFBYSxHQUFHLFNBQVMsQ0FBQyxLQUFNLENBQUE7Z0JBQ3RDLE1BQU0sNkJBQTZCLENBQUMsZUFBZSxFQUFFLGVBQWUsRUFBRSxZQUFZLEVBQUUsWUFBWSxFQUFFLGFBQWEsRUFBRSxRQUFRLEVBQUUsU0FBUyxDQUFDLENBQUE7YUFDdEk7U0FDRjtRQUNELFNBQVMsR0FBRyxRQUFRLENBQUMsU0FBUyxDQUFBO0tBQy9CLFFBQVEsU0FBUyxFQUFDO0FBQ3JCLENBQUM7QUF4Q0Qsb0JBd0NDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBSZWFkIEFXUyBDbG91ZEZvcm1hdGlvbiBFeHBvcnRzIGFuZCBhdXRvZ2VuZXJhdGUgQ2xvdUROUyByZWNvcmRzIGJhc2VkIG9uIHRoZWlyIG5hbWVzIGFuZCB2YWx1ZXMuXG4gKiBLZW5uZXRoIEZhbGNrIDxrZW5udUBjbG91ZGVuLm5ldD4gKEMpIENsb3VkZW4gT3kgMjAyM1xuICpcbiAqIFRoaXMgdG9vbCBjYW4gYmUgdXNlZCB0byBhdXRvZ2VuZXJhdGUgQ2xvdUROUyByZWNvcmRzIGZvciBDbG91ZEZvcm1hdGlvbiByZXNvdXJjZXMgbGlrZVxuICogQ2xvdWRGcm9udCBkaXN0cmlidXRpb25zIGFuZCBBUEkgR2F0ZXdheSBkb21haW5zLlxuICpcbiAqIENsb3VkRm9ybWF0aW9uIGV4cG9ydCBuYW1lIG11c3Qgc3BlY2lmeSB0aGUgcmVzb3VyY2UgdHlwZSBhbmQgcmVjb3JkIGhvc3RuYW1lIGFzIGZvbGxvd3M6XG4gKiBDbG91RE5TOkNOQU1FOm15aG9zdDpleGFtcGxlOm9yZ1xuICpcbiAqIENsb3VkRm9ybWF0aW9uIGV4cG9ydCB2YWx1ZSBtdXN0IHNwZWNpZnkgdGhlIHJlY29yZCB2YWx1ZSBhcy1pcyAoZm9yIGluc3RhbmNlLCBhIGRpc3RyaWJ1dGlvbiBkb21haW4gbmFtZSk6XG4gKiB4eHh4eHh4eHh4eHh4eC5jbG91ZGZyb250Lm5ldFxuICpcbiAqIFRoZSBhYm92ZSBleGFtcGxlIHdpbGwgZ2VuZXJhdGUgdGhlIGZvbGxvd2luZyByZWNvcmQgaW4gdGhlIENsb3VETlMgem9uZSBleGFtcGxlLm9yZzpcbiAqIG15aG9zdC5leGFtcGxlLm9yZyBDTkFNRSB4eHh4eHh4eHh4eHh4eC5jbG91ZGZyb250Lm5ldFxuICpcbiAqIE90aGVyIHJlc291cmNlIHR5cGVzIGFyZSBhbHNvIGFsbG93ZWQgKEEsIEFBQUEsIEFMSUFTLCBldGMpLlxuICpcbiAqIENvbW1hbmQgbGluZSB1c2FnZTogQVdTX1BST0ZJTEU9eHh4IHRzLW5vZGUgY2xvdWRucy1jbG91ZGZvcm1hdGlvbi1zeW5jLnRzIDxjbG91ZG5zLXVzZXJuYW1lPiA8Y2xvdWRucy1wYXNzd29yZC1wYXJhbWV0ZXItbmFtZT4gW3R0bF1cbiAqXG4gKiBBV1NfUFJPRklMRT14eHggLSBTcGVjaWZ5IHlvdXIgQVdTIHByb2ZpbGUgaW4gfi8uYXdzL2NyZWRlbnRpYWxzIGFzIGFuIGVudmlyb25tZW50IHZhcmlhYmxlXG4gKiA8Y2xvdWRucy11c2VybmFtZT4gLSBDbG91RE5TIEFQSSBzdWItYXV0aC11c2VyXG4gKiA8Y2xvdWRucy1wYXNzd29yZC1wYXJhbWV0ZXItbmFtZT4gLSBTU00gUGFyYW1ldGVyIHdpdGggdGhlIGVuY3J5cHRlZCBDbG91RE5TIEFQSSBwYXNzd29yZFxuICogW3R0bF0gLSBPcHRpb25hbCBUVEwgZm9yIGdlbmVyYXRlZCByZWNvcmRzIChkZWZhdWx0cyB0byAzMDApXG4gKi9cbmltcG9ydCB7IFNTTUNsaWVudCwgR2V0UGFyYW1ldGVyQ29tbWFuZCB9IGZyb20gJ0Bhd3Mtc2RrL2NsaWVudC1zc20nXG5pbXBvcnQgeyBDbG91ZEZvcm1hdGlvbkNsaWVudCwgTGlzdEV4cG9ydHNDb21tYW5kLCBMaXN0RXhwb3J0c091dHB1dCB9IGZyb20gJ0Bhd3Mtc2RrL2NsaWVudC1jbG91ZGZvcm1hdGlvbidcbmltcG9ydCBmZXRjaCBmcm9tICdub2RlLWZldGNoJ1xuaW1wb3J0ICogYXMgcXVlcnlzdHJpbmcgZnJvbSAncXVlcnlzdHJpbmcnXG5cbi8vIExvYWQgfi8uYXdzL2NvbmZpZ1xucHJvY2Vzcy5lbnYuQVdTX1NES19MT0FEX0NPTkZJRyA9ICcxJ1xuXG5hc3luYyBmdW5jdGlvbiBjbG91ZG5zUmVzdENhbGwoY2xvdWRuc1VzZXJuYW1lOiBzdHJpbmcsIGNsb3VkbnNQYXNzd29yZDogc3RyaW5nLCBtZXRob2Q6IHN0cmluZywgcmVsYXRpdmVVcmw6IHN0cmluZywgcXVlcnlPcHRpb25zOiBhbnkpIHtcbiAgbGV0IGZ1bGxVcmwgPSAnaHR0cHM6Ly9hcGkuY2xvdWRucy5uZXQnICsgcmVsYXRpdmVVcmwgKyAnPycgKyBxdWVyeXN0cmluZy5zdHJpbmdpZnkoT2JqZWN0LmFzc2lnbih7XG4gICAgJ3N1Yi1hdXRoLXVzZXInOiBjbG91ZG5zVXNlcm5hbWUsXG4gICAgJ2F1dGgtcGFzc3dvcmQnOiBjbG91ZG5zUGFzc3dvcmQsXG4gIH0sIHF1ZXJ5T3B0aW9ucyB8fCB7fSkpXG5cbiAgLy8gY29uc29sZS5sb2coJ05vdGU6IENhbGxpbmcnLCBmdWxsVXJsKVxuXG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2goZnVsbFVybCwge1xuICAgIG1ldGhvZDogbWV0aG9kLFxuICAgIGhlYWRlcnM6IHtcbiAgICAgICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicsXG4gICAgICAnQWNjZXB0JzogJ2FwcGxpY2F0aW9uL2pzb24nLFxuICAgIH1cbiAgfSlcbiAgaWYgKCFyZXNwb25zZS5vaykge1xuICAgIGNvbnN0IGVycm9yVGV4dCA9IGF3YWl0IHJlc3BvbnNlLnRleHQoKVxuICAgIGNvbnNvbGUuZXJyb3IoJ0hUVFAgRXJyb3InLCByZXNwb25zZS5zdGF0dXMsIHJlc3BvbnNlLnN0YXR1c1RleHQsIGVycm9yVGV4dClcbiAgICB0aHJvdyBuZXcgRXJyb3IoZXJyb3JUZXh0KVxuICB9XG4gIHJldHVybiByZXNwb25zZS5qc29uKClcbn1cblxuYXN5bmMgZnVuY3Rpb24gYXV0b0RldGVjdENsb3VkbnNIb3N0QW5kWm9uZShjbG91ZG5zVXNlcm5hbWU6IHN0cmluZywgY2xvdWRuc1Bhc3N3b3JkOiBzdHJpbmcsIG5hbWU6IHN0cmluZywgem9uZUNhY2hlOiBhbnkpIHtcbiAgY29uc3QgbmFtZVBhcnRzID0gbmFtZS5zcGxpdCgnLicpXG5cbiAgLy8gWm9uZSBhbmQgaG9zdCBuYW1lIGZvciB4eHgudGxkXG4gIGNvbnN0IGhvc3ROYW1lMSA9IG5hbWVQYXJ0cy5zbGljZSgwLCBuYW1lUGFydHMubGVuZ3RoLTIpLmpvaW4oJy4nKVxuICBjb25zdCB6b25lTmFtZTEgPSBuYW1lUGFydHMuc2xpY2UobmFtZVBhcnRzLmxlbmd0aC0yKS5qb2luKCcuJylcblxuICAvLyBab25lIGFuZCBob3N0IG5hbWUgZm9yIHh4eC5zdWJ0bGQudGxkXG4gIGNvbnN0IGhvc3ROYW1lMiA9IG5hbWVQYXJ0cy5zbGljZSgwLCBuYW1lUGFydHMubGVuZ3RoLTMpLmpvaW4oJy4nKVxuICBjb25zdCB6b25lTmFtZTIgPSBuYW1lUGFydHMuc2xpY2UobmFtZVBhcnRzLmxlbmd0aC0zKS5qb2luKCcuJylcblxuICAvLyBDaGVjayB3aGljaCB6b25lIGV4aXN0c1xuICBjb25zdCB6b25lUmVzcG9uc2UxID0gem9uZUNhY2hlW3pvbmVOYW1lMV0gfHwgYXdhaXQgY2xvdWRuc1Jlc3RDYWxsKGNsb3VkbnNVc2VybmFtZSwgY2xvdWRuc1Bhc3N3b3JkLCAnR0VUJywgJy9kbnMvZ2V0LXpvbmUtaW5mby5qc29uJywge1xuICAgICdkb21haW4tbmFtZSc6IHpvbmVOYW1lMSxcbiAgfSlcbiAgem9uZUNhY2hlW3pvbmVOYW1lMV0gPSB6b25lUmVzcG9uc2UxXG4gIGNvbnN0IHpvbmVSZXNwb25zZTIgPSB6b25lQ2FjaGVbem9uZU5hbWUyXSB8fCBhd2FpdCBjbG91ZG5zUmVzdENhbGwoY2xvdWRuc1VzZXJuYW1lLCBjbG91ZG5zUGFzc3dvcmQsICdHRVQnLCAnL2Rucy9nZXQtem9uZS1pbmZvLmpzb24nLCB7XG4gICAgJ2RvbWFpbi1uYW1lJzogem9uZU5hbWUyLFxuICB9KVxuICB6b25lQ2FjaGVbem9uZU5hbWUyXSA9IHpvbmVSZXNwb25zZTJcblxuICAvLyBjb25zb2xlLmxvZygnTm90ZTogUmVzcG9uc2UgZm9yIGhvc3QnLCBob3N0TmFtZTEsICdpbiB6b25lJywgem9uZU5hbWUxLCAnOicsIHpvbmVSZXNwb25zZTEpXG4gIC8vIGNvbnNvbGUubG9nKCdOb3RlOiBSZXNwb25zZSBmb3IgaG9zdCcsIGhvc3ROYW1lMiwgJ2luIHpvbmUnLCB6b25lTmFtZTIsICc6Jywgem9uZVJlc3BvbnNlMilcblxuICBjb25zdCB6b25lTmFtZSA9ICh6b25lUmVzcG9uc2UxLnN0YXR1cyA9PT0gJzEnID8gem9uZU5hbWUxIDogem9uZVJlc3BvbnNlMi5zdGF0dXMgPT09ICcxJyA/IHpvbmVOYW1lMiA6ICcnKVxuICBjb25zdCBob3N0TmFtZSA9ICh6b25lUmVzcG9uc2UxLnN0YXR1cyA9PT0gJzEnID8gaG9zdE5hbWUxIDogem9uZVJlc3BvbnNlMi5zdGF0dXMgPT09ICcxJyA/IGhvc3ROYW1lMiA6ICcnKVxuICBpZiAoIXpvbmVOYW1lKSB7XG4gICAgLy8gTmVpdGhlciB6b25lIGV4aXN0c1xuICAgIHRocm93IG5ldyBFcnJvcignWm9uZSBOb3QgRm91bmQ6ICcgKyBuYW1lKVxuICB9XG4gIHJldHVybiB7XG4gICAgaG9zdE5hbWU6IGhvc3ROYW1lLFxuICAgIHpvbmVOYW1lOiB6b25lTmFtZSxcbiAgfVxufVxuXG5hc3luYyBmdW5jdGlvbiBjcmVhdGVPclVwZGF0ZUNsb3VkbnNSZXNvdXJjZShjbG91ZG5zVXNlcm5hbWU6IHN0cmluZywgY2xvdWRuc1Bhc3N3b3JkOiBzdHJpbmcsIG5hbWU6IHN0cmluZywgdHlwZTogc3RyaW5nLCB2YWx1ZTogc3RyaW5nLCB0dGxWYWx1ZTogc3RyaW5nLCB6b25lQ2FjaGU6IGFueSkge1xuICBjb25zdCB7IHpvbmVOYW1lLCBob3N0TmFtZSB9ID0gYXdhaXQgYXV0b0RldGVjdENsb3VkbnNIb3N0QW5kWm9uZShjbG91ZG5zVXNlcm5hbWUsIGNsb3VkbnNQYXNzd29yZCwgbmFtZSwgem9uZUNhY2hlKVxuICAvLyBEb2VzIHRoZSByZWNvcmQgZXhpc3Q/XG4gIGNvbnN0IHJlY29yZHNSZXNwb25zZSA9IGF3YWl0IGNsb3VkbnNSZXN0Q2FsbChjbG91ZG5zVXNlcm5hbWUsIGNsb3VkbnNQYXNzd29yZCwgJ0dFVCcsICcvZG5zL3JlY29yZHMuanNvbicsIHtcbiAgICAnZG9tYWluLW5hbWUnOiB6b25lTmFtZSxcbiAgICAnaG9zdCc6IGhvc3ROYW1lLFxuICAgICd0eXBlJzogdHlwZSxcbiAgfSlcbiAgY29uc3QgZXhpc3RpbmdSZWNvcmQ6IGFueSA9IE9iamVjdC52YWx1ZXMocmVjb3Jkc1Jlc3BvbnNlKVswXVxuICBpZiAoZXhpc3RpbmdSZWNvcmQ/Lmhvc3QgPT09IGhvc3ROYW1lICYmIGV4aXN0aW5nUmVjb3JkPy50eXBlID09PSB0eXBlICYmIGV4aXN0aW5nUmVjb3JkPy50dGwgPT09IHR0bFZhbHVlICYmIGV4aXN0aW5nUmVjb3JkPy5yZWNvcmQgPT09IHZhbHVlKSB7XG4gICAgLy8gUmVjb3JkIGV4aXN0cyBhbHJlYWR5IC0gbm8gY2hhbmdlXG4gICAgY29uc29sZS5sb2coJ09LJywgbmFtZSwgdHlwZSwgdHRsVmFsdWUsIHZhbHVlLCAnWk9ORScsIHpvbmVOYW1lLCAnSE9TVCcsIGhvc3ROYW1lKVxuICB9IGVsc2UgaWYgKGV4aXN0aW5nUmVjb3JkPy5pZCkge1xuICAgIC8vIFVwZGF0ZSByZWNvcmRcbiAgICBjb25zb2xlLmxvZygnVVBEQVRFJywgbmFtZSwgdHlwZSwgdHRsVmFsdWUsIHZhbHVlLCAnWk9ORScsIHpvbmVOYW1lLCAnSE9TVCcsIGhvc3ROYW1lKVxuICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGNsb3VkbnNSZXN0Q2FsbChjbG91ZG5zVXNlcm5hbWUsIGNsb3VkbnNQYXNzd29yZCwgJ1BPU1QnLCAnL2Rucy9tb2QtcmVjb3JkLmpzb24nLCB7XG4gICAgICAnZG9tYWluLW5hbWUnOiB6b25lTmFtZSxcbiAgICAgICdyZWNvcmQtaWQnOiBleGlzdGluZ1JlY29yZD8uaWQsXG4gICAgICAnaG9zdCc6IGhvc3ROYW1lLFxuICAgICAgJ3JlY29yZC10eXBlJzogdHlwZSxcbiAgICAgICdyZWNvcmQnOiB2YWx1ZSxcbiAgICAgICd0dGwnOiB0dGxWYWx1ZSxcbiAgICB9KVxuICAgIGlmIChyZXN1bHQuc3RhdHVzID09PSAnRmFpbGVkJykge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCAnTW9kaWZ5IHJlY29yZCBmYWlsZWQ6ICcgKyAocmVzdWx0LnN0YXR1c01lc3NhZ2UgfHwgcmVzdWx0LnN0YXR1c0Rlc2NyaXB0aW9uKSlcbiAgICB9XG4gIH0gZWxzZSB7XG4gICAgLy8gQ3JlYXRlIHJlY29yZFxuICAgIGNvbnNvbGUubG9nKCdDUkVBVEUnLCBuYW1lLCB0eXBlLCB0dGxWYWx1ZSwgdmFsdWUsICdaT05FJywgem9uZU5hbWUsICdIT1NUJywgaG9zdE5hbWUpXG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgY2xvdWRuc1Jlc3RDYWxsKGNsb3VkbnNVc2VybmFtZSwgY2xvdWRuc1Bhc3N3b3JkLCAnUE9TVCcsICcvZG5zL2FkZC1yZWNvcmQuanNvbicsIHtcbiAgICAgICdkb21haW4tbmFtZSc6IHpvbmVOYW1lLFxuICAgICAgJ2hvc3QnOiBob3N0TmFtZSxcbiAgICAgICdyZWNvcmQtdHlwZSc6IHR5cGUsXG4gICAgICAncmVjb3JkJzogdmFsdWUsXG4gICAgICAndHRsJzogdHRsVmFsdWUsXG4gICAgfSlcbiAgICBpZiAocmVzdWx0LnN0YXR1cyA9PT0gJ0ZhaWxlZCcpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvciggJ0FkZCByZWNvcmQgZmFpbGVkOiAnICsgKHJlc3VsdC5zdGF0dXNNZXNzYWdlIHx8IHJlc3VsdC5zdGF0dXNEZXNjcmlwdGlvbikpXG4gICAgfVxuICB9XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBtYWluKCkge1xuICBjb25zb2xlLmxvZygnQ2xvdUROUyBDbG91ZEZvcm1hdGlvbiBTeW5jIGJ5IEtlbm5ldGggRmFsY2sgPGtlbm51QGNsb3VkZW4ubmV0PiAoQykgQ2xvdWRlbiBPeSAyMDIzJylcbiAgY29uc3QgY2xvdWRuc1VzZXJuYW1lID0gcHJvY2Vzcy5hcmd2WzJdXG4gIGNvbnN0IGNsb3VkbnNQYXNzd29yZFBhcmFtZXRlciA9IHByb2Nlc3MuYXJndlszXVxuICBjb25zdCB0dGxWYWx1ZSA9IHByb2Nlc3MuYXJndls0XSB8fCAnMzAwJ1xuICBpZiAoIWNsb3VkbnNVc2VybmFtZSkge1xuICAgIGNvbnNvbGUuZXJyb3IoJ1VzYWdlOiBjbG91ZG5zLWNsb3VkZm9ybWF0aW9uLXN5bmMgPGNsb3VkbnMtdXNlcm5hbWU+IDxjbG91ZG5zLXBhc3N3b3JkLXBhcmFtZXRlci1uYW1lPiBbdHRsXScpXG4gICAgcHJvY2Vzcy5leGl0KDEpXG4gIH1cbiAgaWYgKCFjbG91ZG5zUGFzc3dvcmRQYXJhbWV0ZXIpIHtcbiAgICBjb25zb2xlLmVycm9yKCdVc2FnZTogY2xvdWRucy1jbG91ZGZvcm1hdGlvbi1zeW5jIDxjbG91ZG5zLXVzZXJuYW1lPiA8Y2xvdWRucy1wYXNzd29yZC1wYXJhbWV0ZXItbmFtZT4gW3R0bF0nKVxuICAgIHByb2Nlc3MuZXhpdCgxKVxuICB9XG5cbiAgY29uc3Qgc3NtID0gbmV3IFNTTUNsaWVudCh7fSlcbiAgY29uc3Qgem9uZUNhY2hlID0ge31cblxuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHNzbS5zZW5kKG5ldyBHZXRQYXJhbWV0ZXJDb21tYW5kKHtcbiAgICBOYW1lOiBjbG91ZG5zUGFzc3dvcmRQYXJhbWV0ZXIsXG4gICAgV2l0aERlY3J5cHRpb246IHRydWUsXG4gIH0pKVxuICBjb25zdCBjbG91ZG5zUGFzc3dvcmQgPSByZXNwb25zZS5QYXJhbWV0ZXI/LlZhbHVlIHx8ICcnXG5cbiAgY29uc3QgY2xvdWRGb3JtYXRpb24gPSBuZXcgQ2xvdWRGb3JtYXRpb25DbGllbnQoe30pXG4gIGxldCBuZXh0VG9rZW5cbiAgZG8ge1xuICAgIGNvbnN0IHJlc3BvbnNlOiBMaXN0RXhwb3J0c091dHB1dCA9IGF3YWl0IGNsb3VkRm9ybWF0aW9uLnNlbmQobmV3IExpc3RFeHBvcnRzQ29tbWFuZCh7XG4gICAgICBOZXh0VG9rZW46IG5leHRUb2tlbixcbiAgICB9KSlcbiAgICBmb3IgKGNvbnN0IGV4cG9ydE9iaiBvZiByZXNwb25zZS5FeHBvcnRzIHx8IFtdKSB7XG4gICAgICBpZiAoZXhwb3J0T2JqLk5hbWU/Lm1hdGNoKC9eQ2xvdUROUzovKSkge1xuICAgICAgICBjb25zdCBuYW1lUGFydHMgPSBleHBvcnRPYmouTmFtZS5zcGxpdCgnOicpXG4gICAgICAgIGNvbnN0IHJlc291cmNlVHlwZSA9IG5hbWVQYXJ0c1sxXVxuICAgICAgICBjb25zdCByZXNvdXJjZU5hbWUgPSBuYW1lUGFydHMuc2xpY2UoMikuam9pbignLicpXG4gICAgICAgIGNvbnN0IHJlc291cmNlVmFsdWUgPSBleHBvcnRPYmouVmFsdWUhXG4gICAgICAgIGF3YWl0IGNyZWF0ZU9yVXBkYXRlQ2xvdWRuc1Jlc291cmNlKGNsb3VkbnNVc2VybmFtZSwgY2xvdWRuc1Bhc3N3b3JkLCByZXNvdXJjZU5hbWUsIHJlc291cmNlVHlwZSwgcmVzb3VyY2VWYWx1ZSwgdHRsVmFsdWUsIHpvbmVDYWNoZSlcbiAgICAgIH1cbiAgICB9XG4gICAgbmV4dFRva2VuID0gcmVzcG9uc2UuTmV4dFRva2VuXG4gIH0gd2hpbGUgKG5leHRUb2tlbilcbn1cblxuIl19
183
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xvdWRucy1jbG91ZGZvcm1hdGlvbi1zeW5jLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2Nsb3VkbnMtY2xvdWRmb3JtYXRpb24tc3luYy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXlCRztBQUNILG9EQUFvRTtBQUNwRSwwRUFBNEc7QUFDNUcsMkNBQThCO0FBQzlCLDJDQUEwQztBQUUxQyxxQkFBcUI7QUFDckIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxtQkFBbUIsR0FBRyxHQUFHLENBQUE7QUFFckMsS0FBSyxVQUFVLGVBQWUsQ0FBQyxlQUF1QixFQUFFLGVBQXVCLEVBQUUsTUFBYyxFQUFFLFdBQW1CLEVBQUUsWUFBaUI7SUFDckksSUFBSSxPQUFPLEdBQ1QseUJBQXlCO1FBQ3pCLFdBQVc7UUFDWCxHQUFHO1FBQ0gsV0FBVyxDQUFDLFNBQVMsQ0FDbkIsTUFBTSxDQUFDLE1BQU0sQ0FDWDtZQUNFLGVBQWUsRUFBRSxlQUFlO1lBQ2hDLGVBQWUsRUFBRSxlQUFlO1NBQ2pDLEVBQ0QsWUFBWSxJQUFJLEVBQUUsQ0FDbkIsQ0FDRixDQUFBO0lBRUgsd0NBQXdDO0lBRXhDLE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBQSxvQkFBSyxFQUFDLE9BQU8sRUFBRTtRQUNwQyxNQUFNLEVBQUUsTUFBTTtRQUNkLE9BQU8sRUFBRTtZQUNQLGNBQWMsRUFBRSxrQkFBa0I7WUFDbEMsTUFBTSxFQUFFLGtCQUFrQjtTQUMzQjtLQUNGLENBQUMsQ0FBQTtJQUNGLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxFQUFFLENBQUM7UUFDakIsTUFBTSxTQUFTLEdBQUcsTUFBTSxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUE7UUFDdkMsT0FBTyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQUUsUUFBUSxDQUFDLE1BQU0sRUFBRSxRQUFRLENBQUMsVUFBVSxFQUFFLFNBQVMsQ0FBQyxDQUFBO1FBQzVF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QVMsRUFBQztBQUNyQixDQUFDO0FBckRELG9CQXFEQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogUmVhZCBBV1MgQ2xvdWRGb3JtYXRpb24gRXhwb3J0cyBhbmQgYXV0b2dlbmVyYXRlIENsb3VETlMgcmVjb3JkcyBiYXNlZCBvbiB0aGVpciBuYW1lcyBhbmQgdmFsdWVzLlxuICogS2VubmV0aCBGYWxjayA8a2VubnVAY2xvdWRlbi5uZXQ+IChDKSBDbG91ZGVuIE95IDIwMjAtMjAyNFxuICpcbiAqIFRoaXMgdG9vbCBjYW4gYmUgdXNlZCB0byBhdXRvZ2VuZXJhdGUgQ2xvdUROUyByZWNvcmRzIGZvciBDbG91ZEZvcm1hdGlvbiByZXNvdXJjZXMgbGlrZVxuICogQ2xvdWRGcm9udCBkaXN0cmlidXRpb25zIGFuZCBBUEkgR2F0ZXdheSBkb21haW5zLlxuICpcbiAqIENsb3VkRm9ybWF0aW9uIGV4cG9ydCBuYW1lIG11c3Qgc3BlY2lmeSB0aGUgcmVzb3VyY2UgdHlwZSBhbmQgcmVjb3JkIGhvc3RuYW1lIGFzIGZvbGxvd3M6XG4gKiBDbG91RE5TOkNOQU1FOm15aG9zdDpleGFtcGxlOm9yZ1xuICpcbiAqIENsb3VkRm9ybWF0aW9uIGV4cG9ydCB2YWx1ZSBtdXN0IHNwZWNpZnkgdGhlIHJlY29yZCB2YWx1ZSBhcy1pcyAoZm9yIGluc3RhbmNlLCBhIGRpc3RyaWJ1dGlvbiBkb21haW4gbmFtZSk6XG4gKiB4eHh4eHh4eHh4eHh4eC5jbG91ZGZyb250Lm5ldFxuICpcbiAqIFRoZSBhYm92ZSBleGFtcGxlIHdpbGwgZ2VuZXJhdGUgdGhlIGZvbGxvd2luZyByZWNvcmQgaW4gdGhlIENsb3VETlMgem9uZSBleGFtcGxlLm9yZzpcbiAqIG15aG9zdC5leGFtcGxlLm9yZyBDTkFNRSB4eHh4eHh4eHh4eHh4eC5jbG91ZGZyb250Lm5ldFxuICpcbiAqIE90aGVyIHJlc291cmNlIHR5cGVzIGFyZSBhbHNvIGFsbG93ZWQgKEEsIEFBQUEsIEFMSUFTLCBldGMpLlxuICpcbiAqIENvbW1hbmQgbGluZSB1c2FnZTogQVdTX1BST0ZJTEU9eHh4IHRzLW5vZGUgY2xvdWRucy1jbG91ZGZvcm1hdGlvbi1zeW5jLnRzIDxjbG91ZG5zLXVzZXJuYW1lPiA8Y2xvdWRucy1wYXNzd29yZC1wYXJhbWV0ZXItbmFtZT4gW3R0bCBbc3RhY2tOYW1lLi4uXV1cbiAqXG4gKiBBV1NfUFJPRklMRT14eHggLSBTcGVjaWZ5IHlvdXIgQVdTIHByb2ZpbGUgaW4gfi8uYXdzL2NyZWRlbnRpYWxzIGFzIGFuIGVudmlyb25tZW50IHZhcmlhYmxlXG4gKiA8Y2xvdWRucy11c2VybmFtZT4gLSBDbG91RE5TIEFQSSBzdWItYXV0aC11c2VyXG4gKiA8Y2xvdWRucy1wYXNzd29yZC1wYXJhbWV0ZXItbmFtZT4gLSBTU00gUGFyYW1ldGVyIHdpdGggdGhlIGVuY3J5cHRlZCBDbG91RE5TIEFQSSBwYXNzd29yZFxuICogW3R0bF0gLSBPcHRpb25hbCBUVEwgZm9yIGdlbmVyYXRlZCByZWNvcmRzIChkZWZhdWx0cyB0byAzMDApXG4gKiBbc3RhY2tOYW1lLi4uXSAtIE9wdGlvbmFsIENsb3VkRm9ybWF0aW9uIHN0YWNrIG5hbWUocykgdG8gbGltaXQgdGhlIGV4cG9ydHMgdG8gc2NhbiAoZGVmYXVsdHMgdG8gYWxsIHN0YWNrcylcbiAqL1xuaW1wb3J0IHsgU1NNQ2xpZW50LCBHZXRQYXJhbWV0ZXJDb21tYW5kIH0gZnJvbSAnQGF3cy1zZGsvY2xpZW50LXNzbSdcbmltcG9ydCB7IENsb3VkRm9ybWF0aW9uQ2xpZW50LCBMaXN0RXhwb3J0c0NvbW1hbmQsIExpc3RFeHBvcnRzT3V0cHV0IH0gZnJvbSAnQGF3cy1zZGsvY2xpZW50LWNsb3VkZm9ybWF0aW9uJ1xuaW1wb3J0IGZldGNoIGZyb20gJ25vZGUtZmV0Y2gnXG5pbXBvcnQgKiBhcyBxdWVyeXN0cmluZyBmcm9tICdxdWVyeXN0cmluZydcblxuLy8gTG9hZCB+Ly5hd3MvY29uZmlnXG5wcm9jZXNzLmVudi5BV1NfU0RLX0xPQURfQ09ORklHID0gJzEnXG5cbmFzeW5jIGZ1bmN0aW9uIGNsb3VkbnNSZXN0Q2FsbChjbG91ZG5zVXNlcm5hbWU6IHN0cmluZywgY2xvdWRuc1Bhc3N3b3JkOiBzdHJpbmcsIG1ldGhvZDogc3RyaW5nLCByZWxhdGl2ZVVybDogc3RyaW5nLCBxdWVyeU9wdGlvbnM6IGFueSkge1xuICBsZXQgZnVsbFVybCA9XG4gICAgJ2h0dHBzOi8vYXBpLmNsb3VkbnMubmV0JyArXG4gICAgcmVsYXRpdmVVcmwgK1xuICAgICc/JyArXG4gICAgcXVlcnlzdHJpbmcuc3RyaW5naWZ5KFxuICAgICAgT2JqZWN0LmFzc2lnbihcbiAgICAgICAge1xuICAgICAgICAgICdzdWItYXV0aC11c2VyJzogY2xvdWRuc1VzZXJuYW1lLFxuICAgICAgICAgICdhdXRoLXBhc3N3b3JkJzogY2xvdWRuc1Bhc3N3b3JkLFxuICAgICAgICB9LFxuICAgICAgICBxdWVyeU9wdGlvbnMgfHwge31cbiAgICAgIClcbiAgICApXG5cbiAgLy8gY29uc29sZS5sb2coJ05vdGU6IENhbGxpbmcnLCBmdWxsVXJsKVxuXG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2goZnVsbFVybCwge1xuICAgIG1ldGhvZDogbWV0aG9kLFxuICAgIGhlYWRlcnM6IHtcbiAgICAgICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicsXG4gICAgICBBY2NlcHQ6ICdhcHBsaWNhdGlvbi9qc29uJyxcbiAgICB9LFxuICB9KVxuICBpZiAoIXJlc3BvbnNlLm9rKSB7XG4gICAgY29uc3QgZXJyb3JUZXh0ID0gYXdhaXQgcmVzcG9uc2UudGV4dCgpXG4gICAgY29uc29sZS5lcnJvcignSFRUUCBFcnJvcicsIHJlc3BvbnNlLnN0YXR1cywgcmVzcG9uc2Uuc3RhdHVzVGV4dCwgZXJyb3JUZXh0KVxuICAgIHRocm93IG5ldyBFcnJvcihlcnJvclRleHQpXG4gIH1cbiAgcmV0dXJuIHJlc3BvbnNlLmpzb24oKVxufVxuXG5hc3luYyBmdW5jdGlvbiBhdXRvRGV0ZWN0Q2xvdWRuc0hvc3RBbmRab25lKGNsb3VkbnNVc2VybmFtZTogc3RyaW5nLCBjbG91ZG5zUGFzc3dvcmQ6IHN0cmluZywgbmFtZTogc3RyaW5nLCB6b25lQ2FjaGU6IGFueSkge1xuICBjb25zdCBuYW1lUGFydHMgPSBuYW1lLnNwbGl0KCcuJylcblxuICAvLyBab25lIGFuZCBob3N0IG5hbWUgZm9yIHh4eC50bGRcbiAgY29uc3QgaG9zdE5hbWUxID0gbmFtZVBhcnRzLnNsaWNlKDAsIG5hbWVQYXJ0cy5sZW5ndGggLSAyKS5qb2luKCcuJylcbiAgY29uc3Qgem9uZU5hbWUxID0gbmFtZVBhcnRzLnNsaWNlKG5hbWVQYXJ0cy5sZW5ndGggLSAyKS5qb2luKCcuJylcblxuICAvLyBab25lIGFuZCBob3N0IG5hbWUgZm9yIHh4eC5zdWJ0bGQudGxkXG4gIGNvbnN0IGhvc3ROYW1lMiA9IG5hbWVQYXJ0cy5zbGljZSgwLCBuYW1lUGFydHMubGVuZ3RoIC0gMykuam9pbignLicpXG4gIGNvbnN0IHpvbmVOYW1lMiA9IG5hbWVQYXJ0cy5zbGljZShuYW1lUGFydHMubGVuZ3RoIC0gMykuam9pbignLicpXG5cbiAgLy8gQ2hlY2sgd2hpY2ggem9uZSBleGlzdHNcbiAgY29uc3Qgem9uZVJlc3BvbnNlMSA9XG4gICAgem9uZUNhY2hlW3pvbmVOYW1lMV0gfHxcbiAgICAoYXdhaXQgY2xvdWRuc1Jlc3RDYWxsKGNsb3VkbnNVc2VybmFtZSwgY2xvdWRuc1Bhc3N3b3JkLCAnR0VUJywgJy9kbnMvZ2V0LXpvbmUtaW5mby5qc29uJywge1xuICAgICAgJ2RvbWFpbi1uYW1lJzogem9uZU5hbWUxLFxuICAgIH0pKVxuICB6b25lQ2FjaGVbem9uZU5hbWUxXSA9IHpvbmVSZXNwb25zZTFcbiAgY29uc3Qgem9uZVJlc3BvbnNlMiA9XG4gICAgem9uZUNhY2hlW3pvbmVOYW1lMl0gfHxcbiAgICAoYXdhaXQgY2xvdWRuc1Jlc3RDYWxsKGNsb3VkbnNVc2VybmFtZSwgY2xvdWRuc1Bhc3N3b3JkLCAnR0VUJywgJy9kbnMvZ2V0LXpvbmUtaW5mby5qc29uJywge1xuICAgICAgJ2RvbWFpbi1uYW1lJzogem9uZU5hbWUyLFxuICAgIH0pKVxuICB6b25lQ2FjaGVbem9uZU5hbWUyXSA9IHpvbmVSZXNwb25zZTJcblxuICAvLyBjb25zb2xlLmxvZygnTm90ZTogUmVzcG9uc2UgZm9yIGhvc3QnLCBob3N0TmFtZTEsICdpbiB6b25lJywgem9uZU5hbWUxLCAnOicsIHpvbmVSZXNwb25zZTEpXG4gIC8vIGNvbnNvbGUubG9nKCdOb3RlOiBSZXNwb25zZSBmb3IgaG9zdCcsIGhvc3ROYW1lMiwgJ2luIHpvbmUnLCB6b25lTmFtZTIsICc6Jywgem9uZVJlc3BvbnNlMilcblxuICBjb25zdCB6b25lTmFtZSA9IHpvbmVSZXNwb25zZTEuc3RhdHVzID09PSAnMScgPyB6b25lTmFtZTEgOiB6b25lUmVzcG9uc2UyLnN0YXR1cyA9PT0gJzEnID8gem9uZU5hbWUyIDogJydcbiAgY29uc3QgaG9zdE5hbWUgPSB6b25lUmVzcG9uc2UxLnN0YXR1cyA9PT0gJzEnID8gaG9zdE5hbWUxIDogem9uZVJlc3BvbnNlMi5zdGF0dXMgPT09ICcxJyA/IGhvc3ROYW1lMiA6ICcnXG4gIGlmICghem9uZU5hbWUpIHtcbiAgICAvLyBOZWl0aGVyIHpvbmUgZXhpc3RzXG4gICAgdGhyb3cgbmV3IEVycm9yKCdab25lIE5vdCBGb3VuZDogJyArIG5hbWUpXG4gIH1cbiAgcmV0dXJuIHtcbiAgICBob3N0TmFtZTogaG9zdE5hbWUsXG4gICAgem9uZU5hbWU6IHpvbmVOYW1lLFxuICB9XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGNyZWF0ZU9yVXBkYXRlQ2xvdWRuc1Jlc291cmNlKFxuICBjbG91ZG5zVXNlcm5hbWU6IHN0cmluZyxcbiAgY2xvdWRuc1Bhc3N3b3JkOiBzdHJpbmcsXG4gIG5hbWU6IHN0cmluZyxcbiAgdHlwZTogc3RyaW5nLFxuICB2YWx1ZTogc3RyaW5nLFxuICB0dGxWYWx1ZTogc3RyaW5nLFxuICB6b25lQ2FjaGU6IGFueVxuKSB7XG4gIGNvbnN0IHsgem9uZU5hbWUsIGhvc3ROYW1lIH0gPSBhd2FpdCBhdXRvRGV0ZWN0Q2xvdWRuc0hvc3RBbmRab25lKGNsb3VkbnNVc2VybmFtZSwgY2xvdWRuc1Bhc3N3b3JkLCBuYW1lLCB6b25lQ2FjaGUpXG4gIC8vIERvZXMgdGhlIHJlY29yZCBleGlzdD9cbiAgY29uc3QgcmVjb3Jkc1Jlc3BvbnNlID0gYXdhaXQgY2xvdWRuc1Jlc3RDYWxsKGNsb3VkbnNVc2VybmFtZSwgY2xvdWRuc1Bhc3N3b3JkLCAnR0VUJywgJy9kbnMvcmVjb3Jkcy5qc29uJywge1xuICAgICdkb21haW4tbmFtZSc6IHpvbmVOYW1lLFxuICAgIGhvc3Q6IGhvc3ROYW1lLFxuICAgIHR5cGU6IHR5cGUsXG4gIH0pXG4gIGNvbnN0IGV4aXN0aW5nUmVjb3JkOiBhbnkgPSBPYmplY3QudmFsdWVzKHJlY29yZHNSZXNwb25zZSlbMF1cbiAgaWYgKGV4aXN0aW5nUmVjb3JkPy5ob3N0ID09PSBob3N0TmFtZSAmJiBleGlzdGluZ1JlY29yZD8udHlwZSA9PT0gdHlwZSAmJiBleGlzdGluZ1JlY29yZD8udHRsID09PSB0dGxWYWx1ZSAmJiBleGlzdGluZ1JlY29yZD8ucmVjb3JkID09PSB2YWx1ZSkge1xuICAgIC8vIFJlY29yZCBleGlzdHMgYWxyZWFkeSAtIG5vIGNoYW5nZVxuICAgIGNvbnNvbGUubG9nKCdPSycsIG5hbWUsIHR5cGUsIHR0bFZhbHVlLCB2YWx1ZSwgJ1pPTkUnLCB6b25lTmFtZSwgJ0hPU1QnLCBob3N0TmFtZSlcbiAgfSBlbHNlIGlmIChleGlzdGluZ1JlY29yZD8uaWQpIHtcbiAgICAvLyBVcGRhdGUgcmVjb3JkXG4gICAgY29uc29sZS5sb2coJ1VQREFURScsIG5hbWUsIHR5cGUsIHR0bFZhbHVlLCB2YWx1ZSwgJ1pPTkUnLCB6b25lTmFtZSwgJ0hPU1QnLCBob3N0TmFtZSlcbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBjbG91ZG5zUmVzdENhbGwoY2xvdWRuc1VzZXJuYW1lLCBjbG91ZG5zUGFzc3dvcmQsICdQT1NUJywgJy9kbnMvbW9kLXJlY29yZC5qc29uJywge1xuICAgICAgJ2RvbWFpbi1uYW1lJzogem9uZU5hbWUsXG4gICAgICAncmVjb3JkLWlkJzogZXhpc3RpbmdSZWNvcmQ/LmlkLFxuICAgICAgaG9zdDogaG9zdE5hbWUsXG4gICAgICAncmVjb3JkLXR5cGUnOiB0eXBlLFxuICAgICAgcmVjb3JkOiB2YWx1ZSxcbiAgICAgIHR0bDogdHRsVmFsdWUsXG4gICAgfSlcbiAgICBpZiAocmVzdWx0LnN0YXR1cyA9PT0gJ0ZhaWxlZCcpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignTW9kaWZ5IHJlY29yZCBmYWlsZWQ6ICcgKyAocmVzdWx0LnN0YXR1c01lc3NhZ2UgfHwgcmVzdWx0LnN0YXR1c0Rlc2NyaXB0aW9uKSlcbiAgICB9XG4gIH0gZWxzZSB7XG4gICAgLy8gQ3JlYXRlIHJlY29yZFxuICAgIGNvbnNvbGUubG9nKCdDUkVBVEUnLCBuYW1lLCB0eXBlLCB0dGxWYWx1ZSwgdmFsdWUsICdaT05FJywgem9uZU5hbWUsICdIT1NUJywgaG9zdE5hbWUpXG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgY2xvdWRuc1Jlc3RDYWxsKGNsb3VkbnNVc2VybmFtZSwgY2xvdWRuc1Bhc3N3b3JkLCAnUE9TVCcsICcvZG5zL2FkZC1yZWNvcmQuanNvbicsIHtcbiAgICAgICdkb21haW4tbmFtZSc6IHpvbmVOYW1lLFxuICAgICAgaG9zdDogaG9zdE5hbWUsXG4gICAgICAncmVjb3JkLXR5cGUnOiB0eXBlLFxuICAgICAgcmVjb3JkOiB2YWx1ZSxcbiAgICAgIHR0bDogdHRsVmFsdWUsXG4gICAgfSlcbiAgICBpZiAocmVzdWx0LnN0YXR1cyA9PT0gJ0ZhaWxlZCcpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignQWRkIHJlY29yZCBmYWlsZWQ6ICcgKyAocmVzdWx0LnN0YXR1c01lc3NhZ2UgfHwgcmVzdWx0LnN0YXR1c0Rlc2NyaXB0aW9uKSlcbiAgICB9XG4gIH1cbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIG1haW4oKSB7XG4gIGNvbnNvbGUubG9nKCdDbG91RE5TIENsb3VkRm9ybWF0aW9uIFN5bmMgYnkgS2VubmV0aCBGYWxjayA8a2VubnVAY2xvdWRlbi5uZXQ+IChDKSBDbG91ZGVuIE95IDIwMjAtMjAyNCcpXG4gIGNvbnN0IGNsb3VkbnNVc2VybmFtZSA9IHByb2Nlc3MuYXJndlsyXVxuICBjb25zdCBjbG91ZG5zUGFzc3dvcmRQYXJhbWV0ZXIgPSBwcm9jZXNzLmFyZ3ZbM11cbiAgY29uc3QgdHRsVmFsdWUgPSBwcm9jZXNzLmFyZ3ZbNF0gfHwgJzMwMCdcbiAgY29uc3Qgc3RhY2tOYW1lcyA9IHByb2Nlc3MuYXJndi5zbGljZSg1KVxuICBpZiAoIWNsb3VkbnNVc2VybmFtZSkge1xuICAgIGNvbnNvbGUuZXJyb3IoJ1VzYWdlOiBjbG91ZG5zLWNsb3VkZm9ybWF0aW9uLXN5bmMgPGNsb3VkbnMtdXNlcm5hbWU+IDxjbG91ZG5zLXBhc3N3b3JkLXBhcmFtZXRlci1uYW1lPiBbdHRsIFtzdGFja05hbWUuLi5dXScpXG4gICAgcHJvY2Vzcy5leGl0KDEpXG4gIH1cbiAgaWYgKCFjbG91ZG5zUGFzc3dvcmRQYXJhbWV0ZXIpIHtcbiAgICBjb25zb2xlLmVycm9yKCdVc2FnZTogY2xvdWRucy1jbG91ZGZvcm1hdGlvbi1zeW5jIDxjbG91ZG5zLXVzZXJuYW1lPiA8Y2xvdWRucy1wYXNzd29yZC1wYXJhbWV0ZXItbmFtZT4gW3R0bCBbc3RhY2tOYW1lLi4uXV0nKVxuICAgIHByb2Nlc3MuZXhpdCgxKVxuICB9XG5cbiAgY29uc3Qgc3NtID0gbmV3IFNTTUNsaWVudCh7fSlcbiAgY29uc3Qgem9uZUNhY2hlID0ge31cblxuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHNzbS5zZW5kKFxuICAgIG5ldyBHZXRQYXJhbWV0ZXJDb21tYW5kKHtcbiAgICAgIE5hbWU6IGNsb3VkbnNQYXNzd29yZFBhcmFtZXRlcixcbiAgICAgIFdpdGhEZWNyeXB0aW9uOiB0cnVlLFxuICAgIH0pXG4gIClcbiAgY29uc3QgY2xvdWRuc1Bhc3N3b3JkID0gcmVzcG9uc2UuUGFyYW1ldGVyPy5WYWx1ZSB8fCAnJ1xuXG4gIGNvbnN0IGNsb3VkRm9ybWF0aW9uID0gbmV3IENsb3VkRm9ybWF0aW9uQ2xpZW50KHt9KVxuICBsZXQgbmV4dFRva2VuXG4gIGRvIHtcbiAgICBjb25zdCByZXNwb25zZTogTGlzdEV4cG9ydHNPdXRwdXQgPSBhd2FpdCBjbG91ZEZvcm1hdGlvbi5zZW5kKFxuICAgICAgbmV3IExpc3RFeHBvcnRzQ29tbWFuZCh7XG4gICAgICAgIE5leHRUb2tlbjogbmV4dFRva2VuLFxuICAgICAgfSlcbiAgICApXG4gICAgZm9yIChjb25zdCBleHBvcnRPYmogb2YgcmVzcG9uc2UuRXhwb3J0cyB8fCBbXSkge1xuICAgICAgaWYgKHN0YWNrTmFtZXMubGVuZ3RoICYmICFzdGFja05hbWVzLmluY2x1ZGVzKGV4cG9ydE9iai5FeHBvcnRpbmdTdGFja0lkIHx8ICcnKSkge1xuICAgICAgICAvLyBDaGVjayBpZiB0aGUgbmFtZSBwYXJ0IG9mIHRoZSBJRCBtYXRjaGVzIGFybjphd3M6Y2xvdWRmb3JtYXRpb246ZXUtd2VzdC0xOjx4eHg+OnN0YWNrLzxuYW1lPi88eHh4PlxuICAgICAgICBjb25zdCBtID0gZXhwb3J0T2JqLkV4cG9ydGluZ1N0YWNrSWQ/Lm1hdGNoKC9eYXJuOlteOl0rOmNsb3VkZm9ybWF0aW9uOlteOl0rOlteOl0rOnN0YWNrXFwvKFteXFwvXSspXFwvLylcbiAgICAgICAgaWYgKCFtIHx8ICFzdGFja05hbWVzLmluY2x1ZGVzKG1bMV0pKSB7XG4gICAgICAgICAgLy8gU3RhY2sgSUQgbmFtZSBwYXJ0IGRpZG4ndCBtYXRjaCBnaXZlbiBzdGFja05hbWUsIHNvIHNraXAgaXRcbiAgICAgICAgICBjb250aW51ZVxuICAgICAgICB9XG4gICAgICB9XG4gICAgICBpZiAoZXhwb3J0T2JqLk5hbWU/Lm1hdGNoKC9eQ2xvdUROUzovKSkge1xuICAgICAgICBjb25zdCBuYW1lUGFydHMgPSBleHBvcnRPYmouTmFtZS5zcGxpdCgnOicpXG4gICAgICAgIGNvbnN0IHJlc291cmNlVHlwZSA9IG5hbWVQYXJ0c1sxXVxuICAgICAgICBjb25zdCByZXNvdXJjZU5hbWUgPSBuYW1lUGFydHMuc2xpY2UoMikuam9pbignLicpXG4gICAgICAgIGNvbnN0IHJlc291cmNlVmFsdWUgPSBleHBvcnRPYmouVmFsdWUhXG4gICAgICAgIGF3YWl0IGNyZWF0ZU9yVXBkYXRlQ2xvdWRuc1Jlc291cmNlKGNsb3VkbnNVc2VybmFtZSwgY2xvdWRuc1Bhc3N3b3JkLCByZXNvdXJjZU5hbWUsIHJlc291cmNlVHlwZSwgcmVzb3VyY2VWYWx1ZSwgdHRsVmFsdWUsIHpvbmVDYWNoZSlcbiAgICAgIH1cbiAgICB9XG4gICAgbmV4dFRva2VuID0gcmVzcG9uc2UuTmV4dFRva2VuXG4gIH0gd2hpbGUgKG5leHRUb2tlbilcbn1cbiJdfQ==
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cloudns-cloudformation-sync",
3
- "version": "1.2.1",
4
- "description": "Copyright (C) Clouden Oy 2023",
3
+ "version": "1.4.0",
4
+ "description": "Copyright (C) Clouden Oy 2020-2024",
5
5
  "main": "lib/cloudns-cloudformation-sync.js",
6
6
  "bin": {
7
7
  "cloudns-cloudformation-sync": "bin/cloudns-cloudformation-sync"
@@ -28,13 +28,13 @@
28
28
  },
29
29
  "homepage": "https://github.com/cloudeninc/cloudns-cloudformation-sync#readme",
30
30
  "dependencies": {
31
- "@aws-sdk/client-cloudformation": "^3.245.0",
32
- "@aws-sdk/client-ssm": "^3.245.0",
31
+ "@aws-sdk/client-cloudformation": "^3.525.0",
32
+ "@aws-sdk/client-ssm": "^3.525.0",
33
33
  "node-fetch": "^2.6.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@types/node": "^18.11.18",
36
+ "@types/node": "^20.11.24",
37
37
  "@types/node-fetch": "^2.5.7",
38
- "typescript": "^4.9.4"
38
+ "typescript": "^5.3.3"
39
39
  }
40
40
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Read AWS CloudFormation Exports and autogenerate ClouDNS records based on their names and values.
3
- * Kenneth Falck <kennu@clouden.net> (C) Clouden Oy 2023
3
+ * Kenneth Falck <kennu@clouden.net> (C) Clouden Oy 2020-2024
4
4
  *
5
5
  * This tool can be used to autogenerate ClouDNS records for CloudFormation resources like
6
6
  * CloudFront distributions and API Gateway domains.
@@ -16,12 +16,13 @@
16
16
  *
17
17
  * Other resource types are also allowed (A, AAAA, ALIAS, etc).
18
18
  *
19
- * Command line usage: AWS_PROFILE=xxx ts-node cloudns-cloudformation-sync.ts <cloudns-username> <cloudns-password-parameter-name> [ttl]
19
+ * Command line usage: AWS_PROFILE=xxx ts-node cloudns-cloudformation-sync.ts <cloudns-username> <cloudns-password-parameter-name> [ttl [stackName...]]
20
20
  *
21
21
  * AWS_PROFILE=xxx - Specify your AWS profile in ~/.aws/credentials as an environment variable
22
22
  * <cloudns-username> - ClouDNS API sub-auth-user
23
23
  * <cloudns-password-parameter-name> - SSM Parameter with the encrypted ClouDNS API password
24
24
  * [ttl] - Optional TTL for generated records (defaults to 300)
25
+ * [stackName...] - Optional CloudFormation stack name(s) to limit the exports to scan (defaults to all stacks)
25
26
  */
26
27
  import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm'
27
28
  import { CloudFormationClient, ListExportsCommand, ListExportsOutput } from '@aws-sdk/client-cloudformation'
@@ -32,10 +33,19 @@ import * as querystring from 'querystring'
32
33
  process.env.AWS_SDK_LOAD_CONFIG = '1'
33
34
 
34
35
  async function cloudnsRestCall(cloudnsUsername: string, cloudnsPassword: string, method: string, relativeUrl: string, queryOptions: any) {
35
- let fullUrl = 'https://api.cloudns.net' + relativeUrl + '?' + querystring.stringify(Object.assign({
36
- 'sub-auth-user': cloudnsUsername,
37
- 'auth-password': cloudnsPassword,
38
- }, queryOptions || {}))
36
+ let fullUrl =
37
+ 'https://api.cloudns.net' +
38
+ relativeUrl +
39
+ '?' +
40
+ querystring.stringify(
41
+ Object.assign(
42
+ {
43
+ 'sub-auth-user': cloudnsUsername,
44
+ 'auth-password': cloudnsPassword,
45
+ },
46
+ queryOptions || {}
47
+ )
48
+ )
39
49
 
40
50
  // console.log('Note: Calling', fullUrl)
41
51
 
@@ -43,8 +53,8 @@ async function cloudnsRestCall(cloudnsUsername: string, cloudnsPassword: string,
43
53
  method: method,
44
54
  headers: {
45
55
  'Content-Type': 'application/json',
46
- 'Accept': 'application/json',
47
- }
56
+ Accept: 'application/json',
57
+ },
48
58
  })
49
59
  if (!response.ok) {
50
60
  const errorText = await response.text()
@@ -58,28 +68,32 @@ async function autoDetectCloudnsHostAndZone(cloudnsUsername: string, cloudnsPass
58
68
  const nameParts = name.split('.')
59
69
 
60
70
  // Zone and host name for xxx.tld
61
- const hostName1 = nameParts.slice(0, nameParts.length-2).join('.')
62
- const zoneName1 = nameParts.slice(nameParts.length-2).join('.')
71
+ const hostName1 = nameParts.slice(0, nameParts.length - 2).join('.')
72
+ const zoneName1 = nameParts.slice(nameParts.length - 2).join('.')
63
73
 
64
74
  // Zone and host name for xxx.subtld.tld
65
- const hostName2 = nameParts.slice(0, nameParts.length-3).join('.')
66
- const zoneName2 = nameParts.slice(nameParts.length-3).join('.')
75
+ const hostName2 = nameParts.slice(0, nameParts.length - 3).join('.')
76
+ const zoneName2 = nameParts.slice(nameParts.length - 3).join('.')
67
77
 
68
78
  // Check which zone exists
69
- const zoneResponse1 = zoneCache[zoneName1] || await cloudnsRestCall(cloudnsUsername, cloudnsPassword, 'GET', '/dns/get-zone-info.json', {
70
- 'domain-name': zoneName1,
71
- })
79
+ const zoneResponse1 =
80
+ zoneCache[zoneName1] ||
81
+ (await cloudnsRestCall(cloudnsUsername, cloudnsPassword, 'GET', '/dns/get-zone-info.json', {
82
+ 'domain-name': zoneName1,
83
+ }))
72
84
  zoneCache[zoneName1] = zoneResponse1
73
- const zoneResponse2 = zoneCache[zoneName2] || await cloudnsRestCall(cloudnsUsername, cloudnsPassword, 'GET', '/dns/get-zone-info.json', {
74
- 'domain-name': zoneName2,
75
- })
85
+ const zoneResponse2 =
86
+ zoneCache[zoneName2] ||
87
+ (await cloudnsRestCall(cloudnsUsername, cloudnsPassword, 'GET', '/dns/get-zone-info.json', {
88
+ 'domain-name': zoneName2,
89
+ }))
76
90
  zoneCache[zoneName2] = zoneResponse2
77
91
 
78
92
  // console.log('Note: Response for host', hostName1, 'in zone', zoneName1, ':', zoneResponse1)
79
93
  // console.log('Note: Response for host', hostName2, 'in zone', zoneName2, ':', zoneResponse2)
80
94
 
81
- const zoneName = (zoneResponse1.status === '1' ? zoneName1 : zoneResponse2.status === '1' ? zoneName2 : '')
82
- const hostName = (zoneResponse1.status === '1' ? hostName1 : zoneResponse2.status === '1' ? hostName2 : '')
95
+ const zoneName = zoneResponse1.status === '1' ? zoneName1 : zoneResponse2.status === '1' ? zoneName2 : ''
96
+ const hostName = zoneResponse1.status === '1' ? hostName1 : zoneResponse2.status === '1' ? hostName2 : ''
83
97
  if (!zoneName) {
84
98
  // Neither zone exists
85
99
  throw new Error('Zone Not Found: ' + name)
@@ -90,13 +104,21 @@ async function autoDetectCloudnsHostAndZone(cloudnsUsername: string, cloudnsPass
90
104
  }
91
105
  }
92
106
 
93
- async function createOrUpdateCloudnsResource(cloudnsUsername: string, cloudnsPassword: string, name: string, type: string, value: string, ttlValue: string, zoneCache: any) {
107
+ async function createOrUpdateCloudnsResource(
108
+ cloudnsUsername: string,
109
+ cloudnsPassword: string,
110
+ name: string,
111
+ type: string,
112
+ value: string,
113
+ ttlValue: string,
114
+ zoneCache: any
115
+ ) {
94
116
  const { zoneName, hostName } = await autoDetectCloudnsHostAndZone(cloudnsUsername, cloudnsPassword, name, zoneCache)
95
117
  // Does the record exist?
96
118
  const recordsResponse = await cloudnsRestCall(cloudnsUsername, cloudnsPassword, 'GET', '/dns/records.json', {
97
119
  'domain-name': zoneName,
98
- 'host': hostName,
99
- 'type': type,
120
+ host: hostName,
121
+ type: type,
100
122
  })
101
123
  const existingRecord: any = Object.values(recordsResponse)[0]
102
124
  if (existingRecord?.host === hostName && existingRecord?.type === type && existingRecord?.ttl === ttlValue && existingRecord?.record === value) {
@@ -108,60 +130,73 @@ async function createOrUpdateCloudnsResource(cloudnsUsername: string, cloudnsPas
108
130
  const result = await cloudnsRestCall(cloudnsUsername, cloudnsPassword, 'POST', '/dns/mod-record.json', {
109
131
  'domain-name': zoneName,
110
132
  'record-id': existingRecord?.id,
111
- 'host': hostName,
133
+ host: hostName,
112
134
  'record-type': type,
113
- 'record': value,
114
- 'ttl': ttlValue,
135
+ record: value,
136
+ ttl: ttlValue,
115
137
  })
116
138
  if (result.status === 'Failed') {
117
- throw new Error( 'Modify record failed: ' + (result.statusMessage || result.statusDescription))
139
+ throw new Error('Modify record failed: ' + (result.statusMessage || result.statusDescription))
118
140
  }
119
141
  } else {
120
142
  // Create record
121
143
  console.log('CREATE', name, type, ttlValue, value, 'ZONE', zoneName, 'HOST', hostName)
122
144
  const result = await cloudnsRestCall(cloudnsUsername, cloudnsPassword, 'POST', '/dns/add-record.json', {
123
145
  'domain-name': zoneName,
124
- 'host': hostName,
146
+ host: hostName,
125
147
  'record-type': type,
126
- 'record': value,
127
- 'ttl': ttlValue,
148
+ record: value,
149
+ ttl: ttlValue,
128
150
  })
129
151
  if (result.status === 'Failed') {
130
- throw new Error( 'Add record failed: ' + (result.statusMessage || result.statusDescription))
152
+ throw new Error('Add record failed: ' + (result.statusMessage || result.statusDescription))
131
153
  }
132
154
  }
133
155
  }
134
156
 
135
157
  export async function main() {
136
- console.log('ClouDNS CloudFormation Sync by Kenneth Falck <kennu@clouden.net> (C) Clouden Oy 2023')
158
+ console.log('ClouDNS CloudFormation Sync by Kenneth Falck <kennu@clouden.net> (C) Clouden Oy 2020-2024')
137
159
  const cloudnsUsername = process.argv[2]
138
160
  const cloudnsPasswordParameter = process.argv[3]
139
161
  const ttlValue = process.argv[4] || '300'
162
+ const stackNames = process.argv.slice(5)
140
163
  if (!cloudnsUsername) {
141
- console.error('Usage: cloudns-cloudformation-sync <cloudns-username> <cloudns-password-parameter-name> [ttl]')
164
+ console.error('Usage: cloudns-cloudformation-sync <cloudns-username> <cloudns-password-parameter-name> [ttl [stackName...]]')
142
165
  process.exit(1)
143
166
  }
144
167
  if (!cloudnsPasswordParameter) {
145
- console.error('Usage: cloudns-cloudformation-sync <cloudns-username> <cloudns-password-parameter-name> [ttl]')
168
+ console.error('Usage: cloudns-cloudformation-sync <cloudns-username> <cloudns-password-parameter-name> [ttl [stackName...]]')
146
169
  process.exit(1)
147
170
  }
148
171
 
149
172
  const ssm = new SSMClient({})
150
173
  const zoneCache = {}
151
174
 
152
- const response = await ssm.send(new GetParameterCommand({
153
- Name: cloudnsPasswordParameter,
154
- WithDecryption: true,
155
- }))
175
+ const response = await ssm.send(
176
+ new GetParameterCommand({
177
+ Name: cloudnsPasswordParameter,
178
+ WithDecryption: true,
179
+ })
180
+ )
156
181
  const cloudnsPassword = response.Parameter?.Value || ''
157
182
 
158
183
  const cloudFormation = new CloudFormationClient({})
159
184
  let nextToken
160
185
  do {
161
- const response: ListExportsOutput = await cloudFormation.send(new ListExportsCommand({
162
- NextToken: nextToken,
163
- }))
186
+ const response: ListExportsOutput = await cloudFormation.send(
187
+ new ListExportsCommand({
188
+ NextToken: nextToken,
189
+ })
190
+ )
164
191
  for (const exportObj of response.Exports || []) {
192
+ if (stackNames.length && !stackNames.includes(exportObj.ExportingStackId || '')) {
193
+ // Check if the name part of the ID matches arn:aws:cloudformation:eu-west-1:<xxx>:stack/<name>/<xxx>
194
+ const m = exportObj.ExportingStackId?.match(/^arn:[^:]+:cloudformation:[^:]+:[^:]+:stack\/([^\/]+)\//)
195
+ if (!m || !stackNames.includes(m[1])) {
196
+ // Stack ID name part didn't match given stackName, so skip it
197
+ continue
198
+ }
199
+ }
165
200
  if (exportObj.Name?.match(/^ClouDNS:/)) {
166
201
  const nameParts = exportObj.Name.split(':')
167
202
  const resourceType = nameParts[1]
@@ -173,4 +208,3 @@ export async function main() {
173
208
  nextToken = response.NextToken
174
209
  } while (nextToken)
175
210
  }
176
-