cloudmason 1.0.11 → 1.1.13

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,6 +61,27 @@ exports.deployS3Stack = async function(stackName,s3Url,params,tag,region){
61
61
  return result.StackId;
62
62
  }
63
63
 
64
+ exports.updateOrgStack = async function(yamlPath){
65
+ const client = new CloudFormationClient({ region: process.env.orgRegion });
66
+
67
+ const stackPath = path.resolve(yamlPath);
68
+ if (!fs.existsSync(stackPath)){
69
+ console.log('Infra Stack not found');
70
+ throw { message: 'Infra stack not found', at: 'deployStack'}
71
+ }
72
+ const stackYML = fs.readFileSync(stackPath,'utf-8');
73
+
74
+ const cmd = {
75
+ StackName: 'CoreInfra',
76
+ TemplateBody: stackYML,
77
+ Capabilities: ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
78
+ };
79
+ const command = new UpdateStackCommand(cmd);
80
+ const response = await client.send(command);
81
+ console.log('Update response',response);
82
+ return response.StackId;
83
+ }
84
+
64
85
  exports.updateStack = async function(stackName,s3Url,params,region){
65
86
  const client = new CloudFormationClient({ region });
66
87
  const cfParams = Object.keys(params).map(k=>{ return { ParameterKey: k, ParameterValue: params[k] } })
@@ -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:
@@ -117,6 +106,8 @@ Resources:
117
106
  Action: "sts:AssumeRoleWithWebIdentity"
118
107
  Condition:
119
108
  StringEquals:
109
+ "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
110
+ StringLike:
120
111
  "token.actions.githubusercontent.com:sub": !Sub "repo:${GitHubRepoName}:*"
121
112
  Policies:
122
113
  - PolicyName: "GitHubActionsPolicy"
@@ -126,8 +117,11 @@ Resources:
126
117
  - Effect: "Allow"
127
118
  Action: "*"
128
119
  Resource: "*"
129
-
130
- Outputs:
131
- GithubRoleArn:
132
- Description: "ARN of the GitHub Actions IAM Role"
133
- Value: !GetAtt GitHubActionsRole.Arn
120
+ GitHubOidcProvider:
121
+ Type: 'AWS::IAM::OIDCProvider'
122
+ Properties:
123
+ Url: 'https://token.actions.githubusercontent.com'
124
+ ClientIdList:
125
+ - 'sts.amazonaws.com'
126
+ ThumbprintList:
127
+ - '6938fd4d98bab03faadb97b34396831e3780aea1'
@@ -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){
@@ -28,6 +31,22 @@ exports.main = async function(args){
28
31
  return true;
29
32
  }
30
33
 
34
+ exports.updateOrgStack = async function(args){
35
+ console.log(`Updating Org Stack from ${args.stack}`)
36
+
37
+
38
+ // Deploy Stack
39
+ const success = await CF.updateOrgStack(args.stack)
40
+ if (success === false){
41
+ console.log('ERR:', success);
42
+ throw new Error('Unknown error updating org stack')
43
+ }
44
+
45
+ // Set org.txt
46
+ console.log('Updated org')
47
+ return true;
48
+ }
49
+
31
50
  exports.setOrg = async function(args){
32
51
  // Set org.txt
33
52
  const orgPath = path.resolve(__dirname,'..','org.txt');
@@ -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
+
@@ -37,11 +37,18 @@ exports.main = async function(args){
37
37
  // --- II UPDATE STACK ---
38
38
  // If stack arg, upload stack
39
39
  const stackKey = `apps/${args.app}/${args.v}/stack.yaml`;
40
- // If no stack arg, upload default stack if none exists
41
- const stackExists = await S3.infraFileExists(stackKey)
42
- if (!stackExists){
43
- console.log('Copying default stack to ' + `apps/${args.app}/${args.v}`);
44
- await S3.copyInfraFile(app.stackKey,stackKey)
40
+ if (args.stack){
41
+ console.log('Updating Stack');
42
+ const stackPath = path.resolve(args.stack);
43
+ if (!fs.existsSync(stackPath)){ throw new Error("Stack not found:" + stackPath)}
44
+ await S3.uploadInfraFile(stackKey,stackPath);
45
+ } else {
46
+ // If no stack arg, upload default stack if none exists
47
+ const stackExists = await S3.infraFileExists(stackKey)
48
+ if (!stackExists){
49
+ console.log('Copying default stack to ' + `apps/${args.app}/${args.v}`);
50
+ await S3.copyInfraFile(app.stackKey,stackKey)
51
+ }
45
52
  }
46
53
 
47
54
  // --- III BUILD IMAGE ---
@@ -10,10 +10,10 @@ exports.main = async function(args){
10
10
  console.log('Err: No app named ' + args.app);
11
11
  throw new Error('Err: No app named ' + args.app)
12
12
  }
13
- if (args.default === undefined && !app.versions[args.v]){
14
- console.log('Err: No app version ' + args.app + ' ' + args.v);
15
- throw new Error('Err: No app version ' + args.app + ' ' + args.v)
16
- }
13
+ // if (args.default === undefined && !app.versions[args.v]){
14
+ // console.log('Err: No app version ' + args.app + ' ' + args.v);
15
+ // throw new Error('Err: No app version ' + args.app + ' ' + args.v)
16
+ // }
17
17
  if (args.default === null && args.v){
18
18
  console.log('Err: Cannot set default and specify version');
19
19
  throw new Error('Err: Cannot set default version and specify version')
package/main.js CHANGED
@@ -15,6 +15,13 @@ const Commands = {
15
15
  {n: 'repo', desc: 'Github repo name', r: false}
16
16
  ]
17
17
  },
18
+ 'update-org': {
19
+ desc: "Update org stack",
20
+ exec: require('./commands/init_org').updateOrgStack,
21
+ args: [
22
+ {n: 'stack', desc: 'Updated Org Stack', r: false}
23
+ ]
24
+ },
18
25
  'set-org': {
19
26
  desc: "Set an exsiting organization",
20
27
  exec: require('./commands/init_org').setOrg,
@@ -85,6 +92,25 @@ const Commands = {
85
92
  {n: 'v', desc: 'Version to launch', pattern: `[0-9]{1,20}`, r: true}
86
93
  ]
87
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
+ },
88
114
  'inspect': {
89
115
  desc: 'Get stack status and Ec2 console logs for an instance',
90
116
  exec: require('./commands/inspect').main,
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"cloudmason","version":"1.0.11","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.13","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"}}