cloudmason 1.9.32 → 1.9.33

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,119 @@
1
+ const { MarketplaceCatalogClient, DescribeEntityCommand } = require("@aws-sdk/client-marketplace-catalog");
2
+ const Params = require('./helpers/params');
3
+
4
+ exports.main = async function(args){
5
+ // Get app and product ID
6
+ const app = await Params.getApp(args.app);
7
+ const productId = app.pid;
8
+ const version = args.v;
9
+
10
+ if (!productId) {
11
+ console.log('ERR: No marketplace listing found for app:', args.app);
12
+ throw new Error('No marketplace listing found. Use new-listing first.');
13
+ }
14
+
15
+ console.log('Waiting for AMI version to become available:');
16
+ console.log('\tProduct ID:', productId);
17
+ console.log('\tVersion:', version);
18
+ console.log('----------');
19
+
20
+ // Create AWS client
21
+ const client = new MarketplaceCatalogClient({ region: process.env.orgRegion });
22
+
23
+ // Wait for version availability
24
+ await waitForVersionAvailability(client, productId, version);
25
+
26
+ return true;
27
+ }
28
+
29
+ // Wait for Version Availability Function
30
+ const waitForVersionAvailability = async (client, productId, version) => {
31
+ const maxAttempts = 1080; // 90 minutes with 5-second intervals (90 * 60 / 5 = 1080)
32
+ let attempts = 0;
33
+
34
+ console.log(`Polling entity for version ${version} availability...`);
35
+ console.log(`Timeout: 90 minutes (will check every 5 seconds)`);
36
+
37
+ while (attempts < maxAttempts) {
38
+ await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5 seconds
39
+
40
+ try {
41
+ const describeEntityCommand = new DescribeEntityCommand({
42
+ Catalog: "AWSMarketplace",
43
+ EntityId: productId,
44
+ });
45
+
46
+ const entityResponse = await client.send(describeEntityCommand);
47
+
48
+ // Parse the Details field which contains version information
49
+ let details;
50
+ if (typeof entityResponse.Details === 'string') {
51
+ details = JSON.parse(entityResponse.Details);
52
+ } else {
53
+ details = entityResponse.Details;
54
+ }
55
+ // Check if version exists in Versions array
56
+ const versionInfo = details.Versions?.find(v => v.VersionTitle === version);
57
+ console.log('Version details:', versionInfo);
58
+
59
+ if (versionInfo) {
60
+ // Check 1: Version must have Sources array with AMI
61
+ const hasSources = Array.isArray(versionInfo.Sources) &&
62
+ versionInfo.Sources.length > 0 &&
63
+ versionInfo.Sources.some(source => source.Image && source.Image.startsWith('ami-'));
64
+
65
+ // Check 2: Version must have DeliveryOptions array
66
+ const hasDeliveryOptions = Array.isArray(versionInfo.DeliveryOptions) &&
67
+ versionInfo.DeliveryOptions.length > 0;
68
+
69
+ // Check 3: At least one DeliveryOption must have Visibility set to 'Public'
70
+ const isPubliclyVisible = hasDeliveryOptions &&
71
+ versionInfo.DeliveryOptions.some(option => option.Visibility === 'Public');
72
+
73
+ if (hasSources && hasDeliveryOptions && isPubliclyVisible) {
74
+ console.log(`✓ Version ${version} is now available to consumers`);
75
+
76
+ // Extract and display AMI details
77
+ const amiSources = versionInfo.Sources.filter(s => s.Image);
78
+ amiSources.forEach(source => {
79
+ console.log(` AMI ID: ${source.Image}`);
80
+ console.log(` Architecture: ${source.Architecture}`);
81
+ console.log(` Type: ${source.VirtualizationType}`);
82
+ });
83
+
84
+ // Display public delivery options
85
+ const publicOptions = versionInfo.DeliveryOptions.filter(opt => opt.Visibility === 'Public');
86
+ console.log(` Public Delivery Options: ${publicOptions.length}`);
87
+ publicOptions.forEach(opt => {
88
+ console.log(` - ${opt.Title || opt.Type}`);
89
+ if (opt.AmiAlias) console.log(` SSM Alias: ${opt.AmiAlias}`);
90
+ });
91
+
92
+ return true;
93
+ }
94
+
95
+ // Provide detailed feedback on what's missing
96
+ const elapsedMinutes = Math.floor((attempts * 5) / 60);
97
+ const reasons = [];
98
+ if (!hasSources) reasons.push('no AMI sources');
99
+ if (!hasDeliveryOptions) reasons.push('no delivery options');
100
+ if (hasDeliveryOptions && !isPubliclyVisible) reasons.push('not publicly visible yet');
101
+
102
+ console.log(`Version ${version} found but not yet fully available: ${reasons.join(', ')} (${elapsedMinutes}m ${(attempts * 5) % 60}s elapsed, attempt ${attempts + 1}/${maxAttempts})`);
103
+ } else {
104
+ const elapsedMinutes = Math.floor((attempts * 5) / 60);
105
+ console.log(`Version ${version} not yet visible in entity (${elapsedMinutes}m ${(attempts * 5) % 60}s elapsed, attempt ${attempts + 1}/${maxAttempts})`);
106
+ }
107
+
108
+ } catch (error) {
109
+ console.error("Error checking entity status:", error.message);
110
+ }
111
+
112
+ attempts++;
113
+ }
114
+
115
+ console.warn(`⚠ Warning: Version availability check timed out after 90 minutes`);
116
+ console.warn("Version may still be under AWS Marketplace review");
117
+ console.warn("The changeset succeeded, but the version is not yet publicly available to consumers");
118
+ return false;
119
+ };
@@ -1,4 +1,4 @@
1
- const { MarketplaceCatalogClient, StartChangeSetCommand,DescribeChangeSetCommand, DescribeEntityCommand} = require("@aws-sdk/client-marketplace-catalog");
1
+ const { MarketplaceCatalogClient, StartChangeSetCommand, DescribeChangeSetCommand } = require("@aws-sdk/client-marketplace-catalog");
2
2
  const { EC2Client, DescribeImagesCommand } = require("@aws-sdk/client-ec2");
3
3
  const fs = require('fs');
4
4
  const path = require('path');
@@ -15,8 +15,7 @@ exports.main = async function(args){
15
15
  // -- Get Version & Descriptions
16
16
  const pubArgs = {
17
17
  version: args.v,
18
- changeDescription: args.desc,
19
- wait: args.wait || false
18
+ changeDescription: args.desc
20
19
  };
21
20
 
22
21
  // -- Get Params
@@ -46,6 +45,11 @@ exports.main = async function(args){
46
45
  const newFileName = path.resolve(args.out);
47
46
  console.log('Updating Template:',newFileName);
48
47
  fs.writeFileSync(newFileName,stackTxt);
48
+
49
+ // -- Suggest next step
50
+ console.log('\nTo wait for this version to be publicly available in marketplace, run:');
51
+ console.log(` mason await-ami -app ${args.app} -v ${args.v}`);
52
+
49
53
  return true
50
54
  }
51
55
 
@@ -54,7 +58,7 @@ exports.main = async function(args){
54
58
 
55
59
  // Update AMI Function
56
60
 
57
- const updateAmiVersion = async ({productId, amiId, version, changeDescription, wait}) => {
61
+ const updateAmiVersion = async ({productId, amiId, version, changeDescription}) => {
58
62
  const client = new MarketplaceCatalogClient({ region: process.env.orgRegion }); // Update the region if needed
59
63
  console.log('Updating AMI version:',productId, amiId, version, changeDescription);
60
64
  try {
@@ -142,12 +146,6 @@ const updateAmiVersion = async ({productId, amiId, version, changeDescription, w
142
146
  }
143
147
  }
144
148
 
145
- // If wait flag is set, poll entity until version is publicly available
146
- if (wait && status === "SUCCEEDED") {
147
- console.log("Waiting for version to become available to consumers...");
148
- await waitForVersionAvailability(client, productId, version);
149
- }
150
-
151
149
  } catch (error) {
152
150
  console.error("Error updating AMI version:", error);
153
151
  throw error;
@@ -155,76 +153,6 @@ const updateAmiVersion = async ({productId, amiId, version, changeDescription, w
155
153
  };
156
154
 
157
155
 
158
- // Wait for Version Availability Function
159
- const waitForVersionAvailability = async (client, productId, version) => {
160
- const maxAttempts = 1080; // 90 minutes with 5-second intervals (90 * 60 / 5 = 1080)
161
- let attempts = 0;
162
-
163
- console.log(`Polling entity for version ${version} availability...`);
164
- console.log(`Timeout: 90 minutes (will check every 5 seconds)`);
165
-
166
- while (attempts < maxAttempts) {
167
- await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5 seconds
168
-
169
- try {
170
- const describeEntityCommand = new DescribeEntityCommand({
171
- Catalog: "AWSMarketplace",
172
- EntityId: productId,
173
- });
174
-
175
- const entityResponse = await client.send(describeEntityCommand);
176
-
177
- // Parse the Details field which contains version information
178
- let details;
179
- if (typeof entityResponse.Details === 'string') {
180
- details = JSON.parse(entityResponse.Details);
181
- } else {
182
- details = entityResponse.Details;
183
- }
184
-
185
- // Check if version exists in Versions array
186
- const versionInfo = details.Versions?.find(v => v.VersionTitle === version);
187
-
188
- if (versionInfo) {
189
- // Check if version has delivery options (indicates it's available)
190
- const hasDeliveryOptions = versionInfo.DeliveryOptions &&
191
- versionInfo.DeliveryOptions.length > 0;
192
-
193
- if (hasDeliveryOptions) {
194
- // Check if any delivery option has Sources (indicates AMI is accessible)
195
- const hasActiveSources = versionInfo.DeliveryOptions.some(
196
- option => option.Details?.AmiDeliveryOptionDetails?.AmiSource ||
197
- option.Details?.AmiSource
198
- );
199
-
200
- if (hasActiveSources) {
201
- console.log(`✓ Version ${version} is now available to consumers`);
202
- console.log("Version details:", JSON.stringify(versionInfo, null, 2));
203
- return true;
204
- }
205
- }
206
-
207
- const elapsedMinutes = Math.floor((attempts * 5) / 60);
208
- console.log(`Version ${version} found but not yet fully available (${elapsedMinutes}m ${(attempts * 5) % 60}s elapsed, attempt ${attempts + 1}/${maxAttempts})`);
209
- } else {
210
- const elapsedMinutes = Math.floor((attempts * 5) / 60);
211
- console.log(`Version ${version} not yet visible in entity (${elapsedMinutes}m ${(attempts * 5) % 60}s elapsed, attempt ${attempts + 1}/${maxAttempts})`);
212
- }
213
-
214
- } catch (error) {
215
- console.error("Error checking entity status:", error.message);
216
- }
217
-
218
- attempts++;
219
- }
220
-
221
- console.warn(`⚠ Warning: Version availability check timed out after 90 minutes`);
222
- console.warn("Version may still be under AWS Marketplace review");
223
- console.warn("The changeset succeeded, but the version is not yet publicly available to consumers");
224
- return false;
225
- };
226
-
227
-
228
156
  // Get AMI Ids Function
229
157
  const getRegions = async (productId) => {
230
158
  const client = new MarketplaceCatalogClient({ region: process.env.orgRegion }); // Update region if needed
package/main.js CHANGED
@@ -118,6 +118,14 @@ const Commands = {
118
118
  {n: 'out', desc: 'Output path of marketplace stack', r: true}
119
119
  ]
120
120
  },
121
+ 'await-ami': {
122
+ desc: 'Wait for an AMI version to be publicly available in marketplace',
123
+ exec: require('./commands/await-ami').main,
124
+ args: [
125
+ {n: 'app', desc: 'Name of existing app', pattern: `[A-Za-z]{2,20}`, r: true},
126
+ {n: 'v', desc: 'Version to wait for', pattern: `[0-9]{1,20}`, r: true}
127
+ ]
128
+ },
121
129
  'inspect': {
122
130
  desc: 'Get stack status and Ec2 console logs for an instance',
123
131
  exec: require('./commands/inspect').main,
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"cloudmason","version":"1.9.32","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":"1.9.33","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"}}