cloudmason 2.7.49 → 2.7.51
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/commands/helpers/org_config.js +26 -0
- package/commands/init_org.js +5 -12
- package/commands/publish.js +147 -51
- package/main.js +13 -15
- package/org.txt +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), '.cloudmason');
|
|
6
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
7
|
+
const LEGACY_PATH = path.resolve(__dirname, '..', '..', 'org.txt');
|
|
8
|
+
|
|
9
|
+
exports.read = function () {
|
|
10
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
11
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
|
12
|
+
}
|
|
13
|
+
if (fs.existsSync(LEGACY_PATH)) {
|
|
14
|
+
const [name, region] = fs.readFileSync(LEGACY_PATH, 'utf-8').split(',');
|
|
15
|
+
const cfg = { name, region };
|
|
16
|
+
exports.write(cfg);
|
|
17
|
+
try { fs.unlinkSync(LEGACY_PATH); } catch (_) {}
|
|
18
|
+
return cfg;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
exports.write = function (cfg) {
|
|
24
|
+
if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
25
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), 'utf-8');
|
|
26
|
+
};
|
package/commands/init_org.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
1
|
const { S3Client,HeadBucketCommand } = require("@aws-sdk/client-s3");
|
|
4
2
|
const { EC2Client, DescribeVpcsCommand } = require("@aws-sdk/client-ec2");
|
|
5
3
|
|
|
6
4
|
const CF = require('./helpers/cf');
|
|
7
5
|
const Params = require('./helpers/params');
|
|
6
|
+
const OrgConfig = require('./helpers/org_config');
|
|
8
7
|
|
|
9
8
|
exports.main = async function(args){
|
|
10
9
|
console.log(`Setting up ${args.name}@ in ${args.region} with repo ${args.repo}`)
|
|
@@ -23,11 +22,8 @@ exports.main = async function(args){
|
|
|
23
22
|
throw new Error('Org already exists')
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const orgData = `${args.name},${args.region}`;
|
|
29
|
-
fs.writeFileSync(orgPath,orgData,'utf-8')
|
|
30
|
-
console.log('Set up org:',orgData)
|
|
25
|
+
OrgConfig.write({ name: args.name, region: args.region });
|
|
26
|
+
console.log('Set up org:', args.name, args.region)
|
|
31
27
|
return true;
|
|
32
28
|
}
|
|
33
29
|
|
|
@@ -48,11 +44,8 @@ exports.updateOrgStack = async function(args){
|
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
exports.setOrg = async function(args){
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const orgData = `${args.name},${args.region}`;
|
|
54
|
-
fs.writeFileSync(orgPath,orgData,'utf-8')
|
|
55
|
-
console.log('Set org:',orgData)
|
|
47
|
+
OrgConfig.write({ name: args.name, region: args.region });
|
|
48
|
+
console.log('Set org:', args.name, args.region)
|
|
56
49
|
return true;
|
|
57
50
|
}
|
|
58
51
|
|
package/commands/publish.js
CHANGED
|
@@ -1,9 +1,37 @@
|
|
|
1
1
|
const { MarketplaceCatalogClient, StartChangeSetCommand, DescribeChangeSetCommand } = require("@aws-sdk/client-marketplace-catalog");
|
|
2
2
|
const { EC2Client, DescribeImagesCommand } = require("@aws-sdk/client-ec2");
|
|
3
|
+
const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3");
|
|
3
4
|
const fs = require('fs');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
const Params = require('./helpers/params');
|
|
6
7
|
|
|
8
|
+
// Parse path-style, virtual-hosted, or s3:// URLs into {bucket, key, region}
|
|
9
|
+
function parseS3Url(url){
|
|
10
|
+
if (!url){ throw new Error('Missing S3 URL'); }
|
|
11
|
+
let m;
|
|
12
|
+
if ((m = url.match(/^s3:\/\/([^/]+)\/(.+)$/))) { return { bucket: m[1], key: m[2], region: null }; }
|
|
13
|
+
if ((m = url.match(/^https:\/\/s3\.([^.]+)\.amazonaws\.com\/([^/]+)\/(.+)$/))) { return { bucket: m[2], key: m[3], region: m[1] }; }
|
|
14
|
+
if ((m = url.match(/^https:\/\/s3\.amazonaws\.com\/([^/]+)\/(.+)$/))) { return { bucket: m[1], key: m[2], region: null }; }
|
|
15
|
+
if ((m = url.match(/^https:\/\/([^.]+)\.s3\.([^.]+)\.amazonaws\.com\/(.+)$/))) { return { bucket: m[1], key: m[3], region: m[2] }; }
|
|
16
|
+
if ((m = url.match(/^https:\/\/([^.]+)\.s3\.amazonaws\.com\/(.+)$/))) { return { bucket: m[1], key: m[2], region: null }; }
|
|
17
|
+
throw new Error('Unrecognized S3 URL format: ' + url);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// AWS Marketplace requires virtual-hosted-style URLs for Template/ArchitectureDiagram
|
|
21
|
+
function toVirtualHostedUrl(url){
|
|
22
|
+
const { bucket, key, region } = parseS3Url(url);
|
|
23
|
+
return region
|
|
24
|
+
? `https://${bucket}.s3.${region}.amazonaws.com/${key}`
|
|
25
|
+
: `https://${bucket}.s3.amazonaws.com/${key}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function readS3Text(url){
|
|
29
|
+
const { bucket, key, region } = parseS3Url(url);
|
|
30
|
+
const client = new S3Client({ region: region || process.env.orgRegion });
|
|
31
|
+
const resp = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
|
|
32
|
+
return await resp.Body.transformToString();
|
|
33
|
+
}
|
|
34
|
+
|
|
7
35
|
exports.add_listing = async function(args){
|
|
8
36
|
console.log('Adding Listing>>', args.app, args.pid);
|
|
9
37
|
await Params.addPid(args.app,args.pid.trim());
|
|
@@ -28,24 +56,43 @@ exports.main = async function(args){
|
|
|
28
56
|
}
|
|
29
57
|
pubArgs.amiId = instanceVersion.baseAMI_Id;
|
|
30
58
|
pubArgs.arch = instanceVersion.arch || 'x86_64';
|
|
31
|
-
|
|
32
|
-
|
|
59
|
+
if (args.cft){
|
|
60
|
+
pubArgs.cftS3Url = instanceVersion.stackURL;
|
|
61
|
+
if (!pubArgs.cftS3Url){
|
|
62
|
+
console.log('ERR: No stack URL found for version',pubArgs.version);
|
|
63
|
+
throw new Error('No stack URL found for version:' + pubArgs.version);
|
|
64
|
+
}
|
|
65
|
+
const missing = ['short','long','diagram'].filter(k => !args[k]);
|
|
66
|
+
if (missing.length){
|
|
67
|
+
throw new Error('Missing required CFT args: ' + missing.map(k=>'-'+k).join(', '));
|
|
68
|
+
}
|
|
69
|
+
pubArgs.shortDescS3Url = args.short;
|
|
70
|
+
pubArgs.longDescS3Url = args.long;
|
|
71
|
+
pubArgs.archDiagramS3Url = args.diagram;
|
|
72
|
+
console.log('Publishing AMI + CFT:\n\t',Object.entries(pubArgs).map(([k,v])=>{return `${k}:${v}`}).join('\n\t'));
|
|
73
|
+
await exports.updateAmiCft(pubArgs);
|
|
74
|
+
} else {
|
|
75
|
+
if (!args.stack){
|
|
76
|
+
throw new Error('Missing required arg -stack for AMI-only publish');
|
|
77
|
+
}
|
|
78
|
+
console.log('Publishing AMI:\n\t',Object.entries(pubArgs).map(([k,v])=>{return `${k}:${v}`}).join('\n\t'));
|
|
79
|
+
await updateAmiVersion(pubArgs);
|
|
33
80
|
|
|
34
|
-
|
|
35
|
-
|
|
81
|
+
// -- Get Marketplace AMI IDs
|
|
82
|
+
// AmiAlias: '/aws/service/marketplace/prod-shmtmk4gqrfge/1.2'
|
|
83
|
+
const amiAlias = `/aws/service/marketplace/${pubArgs.productId}/${pubArgs.version}`;
|
|
84
|
+
console.log('AMI Alias:',amiAlias);
|
|
85
|
+
let stackTxt = fs.readFileSync(path.resolve(args.stack),'utf8');
|
|
86
|
+
// stackTxt = stackTxt.replace(`ImageId: !Ref AmiId`,`ImageId: resolve:ssm:${amiAlias}`);
|
|
87
|
+
stackTxt = stackTxt.replace(/^#-Strip.+#-Strip/ms,'');
|
|
36
88
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// stackTxt = stackTxt.replace(`ImageId: !Ref AmiId`,`ImageId: resolve:ssm:${amiAlias}`);
|
|
43
|
-
stackTxt = stackTxt.replace(/^#-Strip.+#-Strip/ms,'');
|
|
89
|
+
// -- Update CF Template with AMI IDs
|
|
90
|
+
// const newFileName = path.resolve(args.out);
|
|
91
|
+
// console.log('Updating Template:',newFileName);
|
|
92
|
+
// fs.writeFileSync(newFileName,stackTxt);
|
|
93
|
+
}
|
|
44
94
|
|
|
45
|
-
|
|
46
|
-
const newFileName = path.resolve(args.out);
|
|
47
|
-
console.log('Updating Template:',newFileName);
|
|
48
|
-
fs.writeFileSync(newFileName,stackTxt);
|
|
95
|
+
console.log('----------')
|
|
49
96
|
|
|
50
97
|
// -- Suggest next step
|
|
51
98
|
console.log('\nTo wait for this version to be publicly available in marketplace, run:');
|
|
@@ -156,53 +203,102 @@ const updateAmiVersion = async ({productId, amiId, version, changeDescription, a
|
|
|
156
203
|
};
|
|
157
204
|
|
|
158
205
|
|
|
159
|
-
//
|
|
160
|
-
const getRegions = async (productId) => {
|
|
161
|
-
const client = new MarketplaceCatalogClient({ region: process.env.orgRegion }); // Update region if needed
|
|
206
|
+
// Update AMI + CloudFormation Template Function
|
|
162
207
|
|
|
208
|
+
exports.updateAmiCft = async ({productId, amiId, version, changeDescription, arch, cftS3Url, shortDescS3Url, longDescS3Url, archDiagramS3Url}) => {
|
|
209
|
+
const client = new MarketplaceCatalogClient({ region: process.env.orgRegion });
|
|
210
|
+
console.log('Updating AMI+CFT version:', productId, amiId, version, changeDescription, cftS3Url);
|
|
163
211
|
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const response = await client.send(command);
|
|
169
|
-
console.log('dd',response.DetailsDocument);
|
|
170
|
-
console.log('v',response.DetailsDocument.Versions[1].DeliveryOptions[0].AmiAlias);
|
|
171
|
-
return response.DetailsDocument.RegionAvailability.Regions;
|
|
172
|
-
// console.log("Regions:", response.DetailsDocument.Description.RegionAvailability.Regions);
|
|
173
|
-
// console.log("Version:", response.DetailsDocument.Description.RegionAvailability.Regions);
|
|
174
|
-
// Extract AMI details by region
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// List by Mktplc Listing
|
|
178
|
-
const getAMIs = async (region,productName,productCode) => {
|
|
179
|
-
const ec2Client = new EC2Client({ region });
|
|
212
|
+
const shortDescription = await readS3Text(shortDescS3Url);
|
|
213
|
+
const longDescription = await readS3Text(longDescS3Url);
|
|
214
|
+
const templateUrl = toVirtualHostedUrl(cftS3Url);
|
|
215
|
+
const diagramUrl = toVirtualHostedUrl(archDiagramS3Url);
|
|
180
216
|
|
|
181
217
|
try {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
218
|
+
const changeSet = {
|
|
219
|
+
Catalog: "AWSMarketplace",
|
|
220
|
+
Intent: "APPLY",
|
|
221
|
+
ChangeSet: [
|
|
222
|
+
{
|
|
223
|
+
ChangeType: "AddDeliveryOptions",
|
|
224
|
+
Entity: {
|
|
225
|
+
Type: "AmiProduct@1.0",
|
|
226
|
+
Identifier: productId,
|
|
227
|
+
},
|
|
228
|
+
Details: JSON.stringify({
|
|
229
|
+
Version: {
|
|
230
|
+
VersionTitle: version,
|
|
231
|
+
ReleaseNotes: changeDescription,
|
|
232
|
+
},
|
|
233
|
+
DeliveryOptions: [
|
|
234
|
+
{
|
|
235
|
+
DeliveryOptionTitle: "AMI with CloudFormation Template",
|
|
236
|
+
Details: {
|
|
237
|
+
"DeploymentTemplateDeliveryOptionDetails": {
|
|
238
|
+
"ShortDescription": shortDescription,
|
|
239
|
+
"LongDescription": longDescription,
|
|
240
|
+
"UsageInstructions": "Visit Theorim.ai/install for installation instructions",
|
|
241
|
+
"RecommendedInstanceType": arch === 'arm' ? "r8g.medium" : "m6a.large",
|
|
242
|
+
"ArchitectureDiagram": diagramUrl,
|
|
243
|
+
"Template": templateUrl,
|
|
244
|
+
"TemplateSources": [
|
|
245
|
+
{
|
|
246
|
+
"ParameterName": "AmiId",
|
|
247
|
+
"AmiSource": {
|
|
248
|
+
"AmiId": amiId,
|
|
249
|
+
"AccessRoleArn": "arn:aws:iam::590183947985:role/Theorim_MarketPlaceRole",
|
|
250
|
+
"UserName": "ec2-user",
|
|
251
|
+
"OperatingSystemName": "AMAZONLINUX",
|
|
252
|
+
"OperatingSystemVersion": arch === 'arm'
|
|
253
|
+
? "Amazon Linux 2023 arm64 HVM"
|
|
254
|
+
: "Amazon Linux 2 AMI 2.0.20220207.1 x86_64 HVM gp2"
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
]
|
|
262
|
+
}),
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const startChangeSetCommand = new StartChangeSetCommand(changeSet);
|
|
268
|
+
const startResponse = await client.send(startChangeSetCommand);
|
|
269
|
+
console.log("Change set started:", startResponse);
|
|
270
|
+
|
|
271
|
+
const changeSetId = startResponse.ChangeSetId;
|
|
272
|
+
|
|
273
|
+
let status = "IN_PROGRESS";
|
|
274
|
+
while (status === "IN_PROGRESS") {
|
|
275
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
276
|
+
|
|
277
|
+
const describeChangeSetCommand = new DescribeChangeSetCommand({
|
|
278
|
+
Catalog: "AWSMarketplace",
|
|
279
|
+
ChangeSetId: changeSetId,
|
|
191
280
|
});
|
|
281
|
+
const describeResponse = await client.send(describeChangeSetCommand);
|
|
192
282
|
|
|
193
|
-
|
|
283
|
+
status = describeResponse.Status;
|
|
284
|
+
console.log("Change set status:", status);
|
|
194
285
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
286
|
+
if (status === "SUCCEEDED") {
|
|
287
|
+
console.log("Change set succeeded:", describeResponse);
|
|
288
|
+
break;
|
|
289
|
+
} else if (status === "FAILED") {
|
|
290
|
+
console.error("Change set failed:", describeResponse);
|
|
291
|
+
throw new Error("Change set failed");
|
|
198
292
|
}
|
|
293
|
+
}
|
|
199
294
|
|
|
200
|
-
return response.Images;
|
|
201
295
|
} catch (error) {
|
|
202
|
-
|
|
203
|
-
|
|
296
|
+
console.error("Error updating AMI+CFT version:", error);
|
|
297
|
+
throw error;
|
|
204
298
|
}
|
|
205
|
-
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
|
|
206
302
|
|
|
207
303
|
|
|
208
304
|
|
package/main.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const path = require('path')
|
|
4
|
-
const fs = require('fs')
|
|
5
|
-
|
|
6
3
|
const Params = require('./commands/helpers/params');
|
|
4
|
+
const OrgConfig = require('./commands/helpers/org_config');
|
|
7
5
|
|
|
8
6
|
const Commands = {
|
|
9
7
|
'init-org': {
|
|
@@ -115,8 +113,12 @@ const Commands = {
|
|
|
115
113
|
{n: 'app', desc: 'Name of existing app', pattern: `[A-Za-z]{2,20}`, r: true},
|
|
116
114
|
{n: 'desc', desc: 'Description of Changes', r: true},
|
|
117
115
|
{n: 'v', desc: 'Version to launch', pattern: `[0-9]{1,20}`, r: true},
|
|
118
|
-
{n: 'stack', desc: 'Path of stack.yaml', r:
|
|
119
|
-
{n: 'out', desc: 'Output path of marketplace stack', r:
|
|
116
|
+
{n: 'stack', desc: 'Path of stack.yaml (required for AMI-only publish)', r: false},
|
|
117
|
+
{n: 'out', desc: 'Output path of marketplace stack (AMI-only publish)', r: false},
|
|
118
|
+
{n: 'cft', desc: 'Publish as AMI + CloudFormation Template delivery', r: false},
|
|
119
|
+
{n: 'short', desc: 'S3 URL of short-description text file (required when -cft is set)', r: false},
|
|
120
|
+
{n: 'long', desc: 'S3 URL of long-description text file (required when -cft is set)', r: false},
|
|
121
|
+
{n: 'diagram', desc: 'S3 URL of architecture diagram image (required when -cft is set)', r: false}
|
|
120
122
|
]
|
|
121
123
|
},
|
|
122
124
|
'await-ami': {
|
|
@@ -257,16 +259,12 @@ function checkNodeVersion() {
|
|
|
257
259
|
}
|
|
258
260
|
|
|
259
261
|
async function readOrgInfo(){
|
|
260
|
-
const
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return true;
|
|
267
|
-
} else {
|
|
268
|
-
return false;
|
|
269
|
-
}
|
|
262
|
+
const cfg = OrgConfig.read();
|
|
263
|
+
if (!cfg) return false;
|
|
264
|
+
process.env.orgName = cfg.name;
|
|
265
|
+
process.env.orgRegion = cfg.region;
|
|
266
|
+
process.env.orgBucket = await Params.getOrgBucket();
|
|
267
|
+
return true;
|
|
270
268
|
}
|
|
271
269
|
|
|
272
270
|
function parseArgs(){
|
package/org.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
theorim,us-east-1
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"cloudmason","version":"2.7.
|
|
1
|
+
{"name":"cloudmason","version":"2.7.51","description":"","main":"main.js","scripts":{"build":"node build.js"},"bin":{"mason":"./main.js"},"repository":{"type":"git","url":"https://github.com/kai-harvey/cloudmason.git"},"author":"Kai Harvey","license":"ISC","dependencies":{"@aws-sdk/client-acm":"^3.418.0","@aws-sdk/client-auto-scaling":"^3.470.0","@aws-sdk/client-cloudformation":"^3.418.0","@aws-sdk/client-ec2":"^3.864.0","@aws-sdk/client-iam":"^3.864.0","@aws-sdk/client-marketplace-catalog":"^3.716.0","@aws-sdk/client-route-53":"^3.425.0","@aws-sdk/client-s3":"^3.418.0","@aws-sdk/client-ssm":"^3.421.0","adm-zip":"^0.5.10","ssh2":"^1.16.0","yaml":"^2.6.1"}}
|