cloudmason 2.7.49 → 2.7.50

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.
@@ -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
+ };
@@ -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
- // Set org.txt
27
- const orgPath = path.resolve(__dirname,'..','org.txt');
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
- // Set org.txt
52
- const orgPath = path.resolve(__dirname,'..','org.txt');
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
 
@@ -28,24 +28,36 @@ exports.main = async function(args){
28
28
  }
29
29
  pubArgs.amiId = instanceVersion.baseAMI_Id;
30
30
  pubArgs.arch = instanceVersion.arch || 'x86_64';
31
- console.log('Publishing AMI:\n\t',Object.entries(pubArgs).map(([k,v])=>{return `${k}:${v}`}).join('\n\t'));
32
- console.log('----------')
31
+ if (args.cft){
32
+ pubArgs.cftS3Url = instanceVersion.stackURL;
33
+ if (!pubArgs.cftS3Url){
34
+ console.log('ERR: No stack URL found for version',pubArgs.version);
35
+ throw new Error('No stack URL found for version:' + pubArgs.version);
36
+ }
37
+ console.log('Publishing AMI + CFT:\n\t',Object.entries(pubArgs).map(([k,v])=>{return `${k}:${v}`}).join('\n\t'));
38
+ await exports.updateAmiCft(pubArgs);
39
+ } else {
40
+ if (!args.stack){
41
+ throw new Error('Missing required arg -stack for AMI-only publish');
42
+ }
43
+ console.log('Publishing AMI:\n\t',Object.entries(pubArgs).map(([k,v])=>{return `${k}:${v}`}).join('\n\t'));
44
+ await updateAmiVersion(pubArgs);
33
45
 
34
- // -- Publish AMI to Marketplace
35
- await updateAmiVersion(pubArgs);
46
+ // -- Get Marketplace AMI IDs
47
+ // AmiAlias: '/aws/service/marketplace/prod-shmtmk4gqrfge/1.2'
48
+ const amiAlias = `/aws/service/marketplace/${pubArgs.productId}/${pubArgs.version}`;
49
+ console.log('AMI Alias:',amiAlias);
50
+ let stackTxt = fs.readFileSync(path.resolve(args.stack),'utf8');
51
+ // stackTxt = stackTxt.replace(`ImageId: !Ref AmiId`,`ImageId: resolve:ssm:${amiAlias}`);
52
+ stackTxt = stackTxt.replace(/^#-Strip.+#-Strip/ms,'');
36
53
 
37
- // -- Get Marketplace AMI IDs
38
- // AmiAlias: '/aws/service/marketplace/prod-shmtmk4gqrfge/1.2'
39
- const amiAlias = `/aws/service/marketplace/${pubArgs.productId}/${pubArgs.version}`;
40
- console.log('AMI Alias:',amiAlias);
41
- let stackTxt = fs.readFileSync(path.resolve(args.stack),'utf8');
42
- // stackTxt = stackTxt.replace(`ImageId: !Ref AmiId`,`ImageId: resolve:ssm:${amiAlias}`);
43
- stackTxt = stackTxt.replace(/^#-Strip.+#-Strip/ms,'');
54
+ // -- Update CF Template with AMI IDs
55
+ // const newFileName = path.resolve(args.out);
56
+ // console.log('Updating Template:',newFileName);
57
+ // fs.writeFileSync(newFileName,stackTxt);
58
+ }
44
59
 
45
- // -- Update CF Template with AMI IDs
46
- const newFileName = path.resolve(args.out);
47
- console.log('Updating Template:',newFileName);
48
- fs.writeFileSync(newFileName,stackTxt);
60
+ console.log('----------')
49
61
 
50
62
  // -- Suggest next step
51
63
  console.log('\nTo wait for this version to be publicly available in marketplace, run:');
@@ -156,53 +168,93 @@ const updateAmiVersion = async ({productId, amiId, version, changeDescription, a
156
168
  };
157
169
 
158
170
 
159
- // Get AMI Ids Function
160
- const getRegions = async (productId) => {
161
- const client = new MarketplaceCatalogClient({ region: process.env.orgRegion }); // Update region if needed
171
+ // Update AMI + CloudFormation Template Function
172
+
173
+ exports.updateAmiCft = async ({productId, amiId, version, changeDescription, arch, cftS3Url}) => {
174
+ const client = new MarketplaceCatalogClient({ region: process.env.orgRegion });
175
+ console.log('Updating AMI+CFT version:', productId, amiId, version, changeDescription, cftS3Url);
176
+ try {
177
+ const changeSet = {
178
+ Catalog: "AWSMarketplace",
179
+ Intent: "APPLY",
180
+ ChangeSet: [
181
+ {
182
+ ChangeType: "AddDeliveryOptions",
183
+ Entity: {
184
+ Type: "AmiProduct@1.0",
185
+ Identifier: productId,
186
+ },
187
+ Details: JSON.stringify({
188
+ Version: {
189
+ VersionTitle: version,
190
+ ReleaseNotes: changeDescription,
191
+ },
192
+ DeliveryOptions: [
193
+ {
194
+ DeliveryOptionTitle: "AMI with CloudFormation Template",
195
+ Details: {
196
+ "DeploymentTemplateDeliveryOptionDetails": {
197
+ "UsageInstructions": "Visit Theorim.ai/install for installation instructions",
198
+ "RecommendedInstanceType": arch === 'arm' ? "r8g.medium" : "m6a.large",
199
+ "Template": cftS3Url,
200
+ "TemplateSources": [
201
+ {
202
+ "ParameterName": "AmiId",
203
+ "AmiSource": {
204
+ "AmiId": amiId,
205
+ "AccessRoleArn": "arn:aws:iam::590183947985:role/Theorim_MarketPlaceRole",
206
+ "UserName": "ec2-user",
207
+ "OperatingSystemName": "AMAZONLINUX",
208
+ "OperatingSystemVersion": arch === 'arm'
209
+ ? "Amazon Linux 2023 arm64 HVM"
210
+ : "Amazon Linux 2 AMI 2.0.20220207.1 x86_64 HVM gp2"
211
+ }
212
+ }
213
+ ]
214
+ }
215
+ }
216
+ }
217
+ ]
218
+ }),
219
+ },
220
+ ],
221
+ };
162
222
 
223
+ const startChangeSetCommand = new StartChangeSetCommand(changeSet);
224
+ const startResponse = await client.send(startChangeSetCommand);
225
+ console.log("Change set started:", startResponse);
163
226
 
164
- const command = new DescribeEntityCommand({
165
- Catalog: "AWSMarketplace",
166
- EntityId: productId,
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
- }
227
+ const changeSetId = startResponse.ChangeSetId;
176
228
 
177
- // List by Mktplc Listing
178
- const getAMIs = async (region,productName,productCode) => {
179
- const ec2Client = new EC2Client({ region });
229
+ let status = "IN_PROGRESS";
230
+ while (status === "IN_PROGRESS") {
231
+ await new Promise(resolve => setTimeout(resolve, 5000));
180
232
 
181
- try {
182
- const command = new DescribeImagesCommand({
183
- Filters: [
184
- {
185
- Name: "name",
186
- Values: [
187
- `${productName}-*-${productCode}` // Adjust the filter to match your product naming pattern
188
- ]
189
- }
190
- ]
233
+ const describeChangeSetCommand = new DescribeChangeSetCommand({
234
+ Catalog: "AWSMarketplace",
235
+ ChangeSetId: changeSetId,
191
236
  });
237
+ const describeResponse = await client.send(describeChangeSetCommand);
192
238
 
193
- const response = await ec2Client.send(command);
239
+ status = describeResponse.Status;
240
+ console.log("Change set status:", status);
194
241
 
195
- console.log(`Found ${response.Images.length} AMIs for product: ${productName} in region: ${region}`);
196
- for (const ami of response.Images) {
197
- console.log('ami',ami);
242
+ if (status === "SUCCEEDED") {
243
+ console.log("Change set succeeded:", describeResponse);
244
+ break;
245
+ } else if (status === "FAILED") {
246
+ console.error("Change set failed:", describeResponse);
247
+ throw new Error("Change set failed");
198
248
  }
249
+ }
199
250
 
200
- return response.Images;
201
251
  } catch (error) {
202
- console.error(`Error fetching AMIs for product: ${productName} in region: ${region}`, error);
203
- throw error;
252
+ console.error("Error updating AMI+CFT version:", error);
253
+ throw error;
204
254
  }
205
- }
255
+ };
256
+
257
+
206
258
 
207
259
 
208
260
 
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,9 @@ 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: true},
119
- {n: 'out', desc: 'Output path of marketplace stack', r: true}
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}
120
119
  ]
121
120
  },
122
121
  'await-ami': {
@@ -257,16 +256,12 @@ function checkNodeVersion() {
257
256
  }
258
257
 
259
258
  async function readOrgInfo(){
260
- const orgPath = path.resolve(__dirname,'org.txt');
261
- if (fs.existsSync(orgPath)){
262
- const orgInfo = fs.readFileSync(orgPath,'utf-8').split(',');
263
- process.env.orgName = orgInfo[0];
264
- process.env.orgRegion = orgInfo[1];
265
- process.env.orgBucket = await Params.getOrgBucket();
266
- return true;
267
- } else {
268
- return false;
269
- }
259
+ const cfg = OrgConfig.read();
260
+ if (!cfg) return false;
261
+ process.env.orgName = cfg.name;
262
+ process.env.orgRegion = cfg.region;
263
+ process.env.orgBucket = await Params.getOrgBucket();
264
+ return true;
270
265
  }
271
266
 
272
267
  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.49","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"}}
1
+ {"name":"cloudmason","version":"2.7.50","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"}}