cloudmason 1.0.12 → 1.1.14

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.
@@ -61,11 +61,10 @@ exports.deployS3Stack = async function(stackName,s3Url,params,tag,region){
61
61
  return result.StackId;
62
62
  }
63
63
 
64
- exports.updateOrgStack = async function(region,params){
65
- const client = new CloudFormationClient({ region });
66
- const cfParams = Object.keys(params).map(k=>{ return { ParameterKey: k, ParameterValue: params[k] } })
64
+ exports.updateOrgStack = async function(yamlPath){
65
+ const client = new CloudFormationClient({ region: process.env.orgRegion });
67
66
 
68
- const stackPath = path.resolve(__dirname,'stacks',`infra.yaml`);
67
+ const stackPath = path.resolve(yamlPath);
69
68
  if (!fs.existsSync(stackPath)){
70
69
  console.log('Infra Stack not found');
71
70
  throw { message: 'Infra stack not found', at: 'deployStack'}
@@ -75,11 +74,11 @@ exports.updateOrgStack = async function(region,params){
75
74
  const cmd = {
76
75
  StackName: 'CoreInfra',
77
76
  TemplateBody: stackYML,
78
- Parameters: cfParams,
79
77
  Capabilities: ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
80
78
  };
81
79
  const command = new UpdateStackCommand(cmd);
82
80
  const response = await client.send(command);
81
+ console.log('Update response',response);
83
82
  return response.StackId;
84
83
  }
85
84
 
@@ -105,6 +105,28 @@ exports.updateAppV = async function(appName,version,vParams){
105
105
  }
106
106
 
107
107
  // INSTANCE PARAMS
108
+ exports.setOrgParams = async function(orgName,VpcId,repo){
109
+ if (!process.env.orgRegion){ throw new Error('Region not set') }
110
+ const r1 = await writeParam('/infra/org_name',orgName);
111
+ console.log('Set Org Name:',r1)
112
+ const r2 = await writeParam('/infra/vpc_id',VpcId);
113
+ console.og('Set VPC ID:',r2);
114
+ const r3 = await writeParam('/infra/GitHubRepoName',repo || '');
115
+ console.log('Set GitHub Repo:',r3)
116
+ }
117
+
118
+
119
+ exports.addPid = async function(appName,productId){
120
+ if (!process.env.orgRegion){ throw new Error('Region not set') }
121
+ const appKey = `/infra/apps/${appName.toLowerCase()}`
122
+ const appStr = await readParam(appKey,process.env.orgRegion);
123
+ const app = JSON.parse(appStr);
124
+
125
+ app.pid = productId;
126
+ await writeParam(appKey,JSON.stringify(app),process.env.orgRegion);
127
+ }
128
+
129
+
108
130
  exports.addInstance = async function(appName,instanceName,params){
109
131
  if (!process.env.orgRegion){ throw new Error('Region not set') }
110
132
  const appKey = `/infra/apps/${appName.toLowerCase()}`
@@ -20,23 +20,24 @@ Parameters:
20
20
  Type: Number
21
21
  Description: Max number of Ec2 instances
22
22
  Default: 2
23
- EC2InstanceType:
24
- Type: String
25
- Description: EC2 Instance Type
26
- Default: t2.small
27
23
  AdminEmail:
28
24
  Type: String
29
25
  Description: Email for the first admin user
30
- AmiId:
31
- Type: AWS::EC2::Image::Id
32
- Description: Max number of Ec2 instances
33
26
  AppVersion:
34
27
  Type: String
35
28
  Description: Major.minor.build
36
29
  InstanceEnvironment:
37
30
  Type: String
38
31
  Description: Instance enviroment (prod,dev). Setting prod will enable advanced security features.
39
-
32
+ #-Strip
33
+ EC2InstanceType:
34
+ Type: String
35
+ Description: EC2 Instance Type
36
+ Default: m6a.large
37
+ AmiId:
38
+ Type: AWS::EC2::Image::Id
39
+ Description: Max number of Ec2 instances
40
+ #-Strip
40
41
 
41
42
  Resources:
42
43
  # ACM Domain
@@ -196,7 +197,7 @@ Resources:
196
197
  source start.sh
197
198
  ImageId: !Ref AmiId
198
199
  DisableApiTermination: "true"
199
- InstanceType: !Ref EC2InstanceType
200
+ InstanceType: m6a.large
200
201
  SecurityGroupIds:
201
202
  - !Ref AppEc2SecurityGroup
202
203
  AppEc2SecurityGroup:
@@ -2,15 +2,10 @@ AWSTemplateFormatVersion: '2010-09-09'
2
2
  Description: Org Cloudformation Template
3
3
 
4
4
  Parameters:
5
- orgName:
6
- Type: String
7
- Description: Unique name of organization
8
- VpcId:
9
- Type: String
10
- Description: Org VPC
11
5
  GitHubRepoName:
12
- Type: String
6
+ Type: AWS::SSM::Parameter::Value<String>
13
7
  Description: "GitHub repository name in the format owner/repo"
8
+ Default: /infra/GitHubRepoName
14
9
 
15
10
 
16
11
  Resources:
@@ -60,7 +55,7 @@ Resources:
60
55
  SecurityGroupEgress:
61
56
  - IpProtocol: '-1'
62
57
  CidrIp: '0.0.0.0/0'
63
- VpcId: !Ref VpcId
58
+ VpcId: '{{resolve:ssm:/infra/vpc_id}}'
64
59
  # Bucket Policy
65
60
  InfraBucketPolicy:
66
61
  Type: AWS::S3::BucketPolicy
@@ -79,12 +74,6 @@ Resources:
79
74
  - !Sub arn:aws:s3:::${InfraBucket}
80
75
  - !Sub arn:aws:s3:::${InfraBucket}/*
81
76
  # Org Params
82
- ParamOrgInfo:
83
- Type: 'AWS::SSM::Parameter'
84
- Properties:
85
- Name: '/infra/orgName'
86
- Type: 'String'
87
- Value: !Ref orgName
88
77
  ParamInstanceProfile:
89
78
  Type: 'AWS::SSM::Parameter'
90
79
  Properties:
@@ -4,7 +4,7 @@ const { S3Client,HeadBucketCommand } = require("@aws-sdk/client-s3");
4
4
  const { EC2Client, DescribeVpcsCommand } = require("@aws-sdk/client-ec2");
5
5
 
6
6
  const CF = require('./helpers/cf');
7
-
7
+ const Params = require('./helpers/params');
8
8
 
9
9
  exports.main = async function(args){
10
10
  console.log(`Setting up ${args.name}@ in ${args.region} with repo ${args.repo}`)
@@ -13,6 +13,9 @@ exports.main = async function(args){
13
13
  const VpcId = await getDefaultVPC(args.region);
14
14
  console.log(`Default VPC: ${VpcId}`);
15
15
 
16
+ // Set Param
17
+ await Params.setOrgParams(args.name,VpcId,args.repo);
18
+
16
19
  // Deploy Stack
17
20
  const success = await CF.deployOrgStack(args.region, {orgName: args.name, VpcId: VpcId, GitHubRepoName: args.repo})
18
21
  if (success === false){
@@ -29,14 +32,11 @@ exports.main = async function(args){
29
32
  }
30
33
 
31
34
  exports.updateOrgStack = async function(args){
32
- console.log(`Updating ${args.name}@ in ${args.region} with repo ${args.repo}`)
35
+ console.log(`Updating Org Stack from ${args.stack}`)
33
36
 
34
- // Get VPC ID
35
- const VpcId = await getDefaultVPC(args.region);
36
- console.log(`Default VPC: ${VpcId}`);
37
37
 
38
38
  // Deploy Stack
39
- const success = await CF.updateOrgStack(args.region, {orgName: args.name, VpcId: VpcId, GitHubRepoName: args.repo})
39
+ const success = await CF.updateOrgStack(args.stack)
40
40
  if (success === false){
41
41
  console.log('ERR:', success);
42
42
  throw new Error('Unknown error updating org stack')
@@ -0,0 +1,198 @@
1
+ const { MarketplaceCatalogClient, StartChangeSetCommand,DescribeChangeSetCommand, DescribeEntityCommand} = require("@aws-sdk/client-marketplace-catalog");
2
+ const { EC2Client, DescribeImagesCommand } = require("@aws-sdk/client-ec2");
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const Params = require('./helpers/params');
6
+
7
+ exports.add_listing = async function(args){
8
+ console.log('Adding Listing>>', args.app, args.pid);
9
+ await Params.addPid(args.app,args.pid.trim());
10
+ const app = await Params.getApp(args.app);
11
+ console.log('Added listing:',args.app, app.pid);
12
+ }
13
+
14
+ exports.main = async function(args){
15
+ // -- Get Version & Descriptions
16
+ const pubArgs = {
17
+ version: args.v,
18
+ changeDescription: args.desc
19
+ };
20
+
21
+ // -- Get Params
22
+ const app = await Params.getApp(args.app);
23
+ pubArgs.productId = app.pid;
24
+ const instanceVersion = app.versions[pubArgs.version];
25
+ if (!instanceVersion){
26
+ console.log('ERR: Version not found:',pubArgs.version);
27
+ throw new Error('Version not found:' + pubArgs.version);
28
+ }
29
+ pubArgs.amiId = instanceVersion.baseAMI_Id;
30
+ console.log('Publishing AMI:\n\t',Object.entries(pubArgs).map(([k,v])=>{return `${k}:${v}`}).join('\n\t'));
31
+ console.log('----------')
32
+
33
+ // -- Publish AMI to Marketplace
34
+ await updateAmiVersion(pubArgs);
35
+
36
+ // -- Get Marketplace AMI IDs
37
+ // AmiAlias: '/aws/service/marketplace/prod-shmtmk4gqrfge/1.2'
38
+ const amiAlias = `/aws/service/marketplace/${pubArgs.productId}/${pubArgs.version}`;
39
+ let stackTxt = fs.readFileSync(path.resolve(args.stack),'utf8');
40
+ stackTxt = stackTxt.replace(`ImageId: !Ref AmiId`,`ImageId: resolve:ssm:${amiAlias}`);
41
+ stackTxt = stackTxt.replace(/^#-Strip.+#-Strip/ms,'');
42
+
43
+ // -- Update CF Template with AMI IDs
44
+ const newFileName = path.resolve(args.out);
45
+ console.log('Updating Template:',newFileName);
46
+ fs.writeFileSync(newFileName,stackTxt);
47
+ return true
48
+ }
49
+
50
+
51
+
52
+
53
+ // Update AMI Function
54
+
55
+ const updateAmiVersion = async ({productId, amiId, version, changeDescription}) => {
56
+ const client = new MarketplaceCatalogClient({ region: process.env.orgRegion }); // Update the region if needed
57
+ console.log('Updating AMI version:',productId, amiId, version, changeDescription);
58
+ try {
59
+ // Define the change set to update the AMI version
60
+ const changeSet = {
61
+ Catalog: "AWSMarketplace",
62
+ Intent: "VALIDATE",
63
+ ChangeSet: [
64
+ {
65
+ ChangeType: "AddDeliveryOptions",
66
+ Entity: {
67
+ Type: "AmiProduct@1.0",
68
+ Identifier: productId,
69
+ },
70
+ Details: JSON.stringify({
71
+ Version: {
72
+ VersionTitle: version,
73
+ ReleaseNotes: changeDescription,
74
+ },
75
+ DeliveryOptions:[
76
+ {
77
+ Details:
78
+ {
79
+ "AmiDeliveryOptionDetails": {
80
+ "AmiSource":{
81
+ "AmiId": amiId,
82
+ "AccessRoleArn": "arn:aws:iam::590183947985:role/Theorim_MarketPlaceRole",
83
+ "UserName": "ec2-user",
84
+ "OperatingSystemName": "AMAZONLINUX",
85
+ "OperatingSystemVersion": "Amazon Linux 2 AMI 2.0.20220207.1 x86_64 HVM gp2"
86
+ },
87
+ "UsageInstructions": "Visit Theorim.ai/install for installation instructions",
88
+ "RecommendedInstanceType": "m6a.large",
89
+ "SecurityGroups":
90
+ [
91
+ {
92
+ "IpProtocol": "tcp",
93
+ "FromPort": 443,
94
+ "ToPort": 443,
95
+ "IpRanges":["0.0.0.0/0"]
96
+ },
97
+ {
98
+ "IpProtocol": "tcp",
99
+ "FromPort": 8080,
100
+ "ToPort": 8080,
101
+ "IpRanges":["0.0.0.0/0"]
102
+ }
103
+ ]
104
+ }
105
+ }
106
+ }
107
+ ]
108
+ }),
109
+ },
110
+ ],
111
+ };
112
+
113
+ // Start the change set
114
+ const startChangeSetCommand = new StartChangeSetCommand(changeSet);
115
+ const startResponse = await client.send(startChangeSetCommand);
116
+ console.log("Change set started:", startResponse);
117
+
118
+ const changeSetId = startResponse.ChangeSetId;
119
+
120
+ // Poll for the status of the change set
121
+ let status = "IN_PROGRESS";
122
+ while (status === "IN_PROGRESS") {
123
+ await new Promise(resolve => setTimeout(resolve, 5000)); // Wait for 5 seconds before polling
124
+
125
+ const describeChangeSetCommand = new DescribeChangeSetCommand({
126
+ Catalog: "AWSMarketplace",
127
+ ChangeSetId: changeSetId,
128
+ });
129
+ const describeResponse = await client.send(describeChangeSetCommand);
130
+
131
+ status = describeResponse.Status;
132
+ console.log("Change set status:", status);
133
+
134
+ if (status === "SUCCEEDED") {
135
+ console.log("Change set succeeded:", describeResponse);
136
+ break;
137
+ } else if (status === "FAILED") {
138
+ console.error("Change set failed:", describeResponse);
139
+ break;
140
+ }
141
+ }
142
+ } catch (error) {
143
+ console.error("Error updating AMI version:", error);
144
+ }
145
+ };
146
+
147
+
148
+
149
+ // Get AMI Ids Function
150
+ const getRegions = async (productId) => {
151
+ const client = new MarketplaceCatalogClient({ region: process.env.orgRegion }); // Update region if needed
152
+
153
+
154
+ const command = new DescribeEntityCommand({
155
+ Catalog: "AWSMarketplace",
156
+ EntityId: productId,
157
+ });
158
+ const response = await client.send(command);
159
+ console.log('dd',response.DetailsDocument);
160
+ console.log('v',response.DetailsDocument.Versions[1].DeliveryOptions[0].AmiAlias);
161
+ return response.DetailsDocument.RegionAvailability.Regions;
162
+ // console.log("Regions:", response.DetailsDocument.Description.RegionAvailability.Regions);
163
+ // console.log("Version:", response.DetailsDocument.Description.RegionAvailability.Regions);
164
+ // Extract AMI details by region
165
+ }
166
+
167
+ // List by Mktplc Listing
168
+ const getAMIs = async (region,productName,productCode) => {
169
+ const ec2Client = new EC2Client({ region });
170
+
171
+ try {
172
+ const command = new DescribeImagesCommand({
173
+ Filters: [
174
+ {
175
+ Name: "name",
176
+ Values: [
177
+ `${productName}-*-${productCode}` // Adjust the filter to match your product naming pattern
178
+ ]
179
+ }
180
+ ]
181
+ });
182
+
183
+ const response = await ec2Client.send(command);
184
+
185
+ console.log(`Found ${response.Images.length} AMIs for product: ${productName} in region: ${region}`);
186
+ for (const ami of response.Images) {
187
+ console.log('ami',ami);
188
+ }
189
+
190
+ return response.Images;
191
+ } catch (error) {
192
+ console.error(`Error fetching AMIs for product: ${productName} in region: ${region}`, error);
193
+ throw error;
194
+ }
195
+ }
196
+
197
+
198
+
@@ -66,7 +66,8 @@ exports.main = async function(args){
66
66
  node: app.nodeV,
67
67
  py: app.pyV
68
68
  });
69
-
69
+
70
+ await sleep(60*1000);
70
71
  await waitUntilInstanceReady(instance_id,process.env.orgRegion);
71
72
 
72
73
  // Create AMI
package/main.js CHANGED
@@ -19,9 +19,7 @@ const Commands = {
19
19
  desc: "Update org stack",
20
20
  exec: require('./commands/init_org').updateOrgStack,
21
21
  args: [
22
- {n: 'name', desc: 'Unique org Name. Letters only', r: true, pattern: `[A-Za-z]{2,20}`},
23
- {n: 'region', desc: 'AWS Region for Core Assets. Default us-east-1', r: false},
24
- {n: 'repo', desc: 'Github repo name', r: false}
22
+ {n: 'stack', desc: 'Updated Org Stack', r: false}
25
23
  ]
26
24
  },
27
25
  'set-org': {
@@ -94,6 +92,25 @@ const Commands = {
94
92
  {n: 'v', desc: 'Version to launch', pattern: `[0-9]{1,20}`, r: true}
95
93
  ]
96
94
  },
95
+ 'new-listing': {
96
+ desc: 'Set a new listing for an app',
97
+ exec: require('./commands/publish').add_listing,
98
+ args: [
99
+ {n: 'app', desc: 'Name of existing app', pattern: `[A-Za-z]{2,20}`, r: true},
100
+ {n: 'pid', desc: 'Product Id', r: true}
101
+ ]
102
+ },
103
+ 'publish': {
104
+ desc: 'Publish app to marketplace',
105
+ exec: require('./commands/publish').main,
106
+ args: [
107
+ {n: 'app', desc: 'Name of existing app', pattern: `[A-Za-z]{2,20}`, r: true},
108
+ {n: 'desc', desc: 'Description of Changes', r: true},
109
+ {n: 'v', desc: 'Version to launch', pattern: `[0-9]{1,20}`, r: true},
110
+ {n: 'stack', desc: 'Path of stack.yaml', r: true},
111
+ {n: 'out', desc: 'Output path of marketplace stack', r: true}
112
+ ]
113
+ },
97
114
  'inspect': {
98
115
  desc: 'Get stack status and Ec2 console logs for an instance',
99
116
  exec: require('./commands/inspect').main,
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"cloudmason","version":"1.0.12","description":"","main":"main.js","scripts":{"build":"node build.js"},"bin":{"mason":"./main.js"},"repository":{"type":"git","url":"https://github.com/kai-harvey/secure-saas.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.416.0","@aws-sdk/client-iam":"^3.418.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"}}
1
+ {"name":"cloudmason","version":"1.1.14","description":"","main":"main.js","scripts":{"build":"node build.js"},"bin":{"mason":"./main.js"},"repository":{"type":"git","url":"https://github.com/kai-harvey/secure-saas.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.416.0","@aws-sdk/client-iam":"^3.418.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","yaml":"^2.6.1"}}