cloudmason 1.0.10 → 1.0.12

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,30 @@
1
+ const S3 = require('./helpers/s3');
2
+ const Params = require('./helpers/params');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ exports.main = async function(args){
7
+ // Get App
8
+ const app = await Params.getApp(args.app);
9
+ if (!app){
10
+ console.log('Err: No app named ' + args.app);
11
+ throw new Error('Err: No app named ' + args.app)
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
+ }
17
+ if (args.default === null && args.v){
18
+ console.log('Err: Cannot set default and specify version');
19
+ throw new Error('Err: Cannot set default version and specify version')
20
+ }
21
+ console.log(args)
22
+ const outputPath = path.resolve(args.out);
23
+ if (!fs.statSync(outputPath).isDirectory()){ throw new Error("Invalid Output Path:" + args.out)}
24
+
25
+ console.log(`Pulling stack for v${args.v || 'Default'} ${args.app}`);
26
+ const stackKey = args.default === null ? `apps/${args.app}/default_stack.yaml` : `apps/${args.app}/${args.v}/stack.yaml`;
27
+ const stackText = await S3.getInfraFile(stackKey);
28
+ const outputFilePath = path.join(outputPath,`${args.app}_v${args.v || 'default'}_stack.yaml`);
29
+ fs.writeFileSync(outputFilePath,stackText,{ encoding: "utf8" });
30
+ }
@@ -22,7 +22,7 @@ exports.deployOrgStack = async function(region,params){
22
22
  StackName: `CoreInfra`,
23
23
  TemplateBody: stackYML,
24
24
  Capabilities: [
25
- "CAPABILITY_IAM" || "CAPABILITY_NAMED_IAM" || "CAPABILITY_AUTO_EXPAND",
25
+ "CAPABILITY_IAM", "CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND",
26
26
  ],
27
27
  Parameters: cfParams,
28
28
  Tags: [{ Key: 'purpose', Value: 'infra' }]
@@ -61,6 +61,28 @@ 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] } })
67
+
68
+ const stackPath = path.resolve(__dirname,'stacks',`infra.yaml`);
69
+ if (!fs.existsSync(stackPath)){
70
+ console.log('Infra Stack not found');
71
+ throw { message: 'Infra stack not found', at: 'deployStack'}
72
+ }
73
+ const stackYML = fs.readFileSync(stackPath,'utf-8');
74
+
75
+ const cmd = {
76
+ StackName: 'CoreInfra',
77
+ TemplateBody: stackYML,
78
+ Parameters: cfParams,
79
+ Capabilities: ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
80
+ };
81
+ const command = new UpdateStackCommand(cmd);
82
+ const response = await client.send(command);
83
+ return response.StackId;
84
+ }
85
+
64
86
  exports.updateStack = async function(stackName,s3Url,params,region){
65
87
  const client = new CloudFormationClient({ region });
66
88
  const cfParams = Object.keys(params).map(k=>{ return { ParameterKey: k, ParameterValue: params[k] } })
@@ -68,6 +68,27 @@ exports.copyInfraFile = async function(srcKey,destKey){
68
68
  return true;
69
69
  }
70
70
 
71
+ exports.getInfraFile = async function(fileKey){
72
+ const client = new S3Client({ region: process.env.orgRegion });
73
+ const params = {
74
+ Bucket: process.env.orgBucket,
75
+ Key: fileKey.toLowerCase(),
76
+ };
77
+ const command = new GetObjectCommand(params);
78
+ const response = await client.send(command);
79
+
80
+ const streamToString = (stream) =>
81
+ new Promise((resolve, reject) => {
82
+ const chunks = [];
83
+ stream.on("data", (chunk) => chunks.push(chunk));
84
+ stream.on("error", reject);
85
+ stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
86
+ });
87
+
88
+ const fileContent = await streamToString(response.Body);
89
+ return fileContent;
90
+ }
91
+
71
92
  exports.emptyBucket = async function(bucketName,region){
72
93
  await emptyS3Bucket(bucketName,region)
73
94
  return true;
@@ -229,6 +229,9 @@ Resources:
229
229
  KeyType: HASH
230
230
  - AttributeName: sk
231
231
  KeyType: RANGE
232
+ TimeToLiveSpecification:
233
+ AttributeName: "_ttl"
234
+ Enabled: true
232
235
  # S3 App Bucket
233
236
  AppBucket:
234
237
  Type: AWS::S3::Bucket
@@ -8,6 +8,9 @@ Parameters:
8
8
  VpcId:
9
9
  Type: String
10
10
  Description: Org VPC
11
+ GitHubRepoName:
12
+ Type: String
13
+ Description: "GitHub repository name in the format owner/repo"
11
14
 
12
15
 
13
16
  Resources:
@@ -99,4 +102,37 @@ Resources:
99
102
  Properties:
100
103
  Name: '/infra/infraBucket'
101
104
  Type: 'String'
102
- Value: !Ref InfraBucket
105
+ Value: !Ref InfraBucket
106
+ # Github Repo
107
+ GitHubActionsRole:
108
+ Type: "AWS::IAM::Role"
109
+ Properties:
110
+ RoleName: "GitHubActionsRole"
111
+ AssumeRolePolicyDocument:
112
+ Version: "2012-10-17"
113
+ Statement:
114
+ - Effect: "Allow"
115
+ Principal:
116
+ Federated: !Sub "arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com"
117
+ Action: "sts:AssumeRoleWithWebIdentity"
118
+ Condition:
119
+ StringEquals:
120
+ "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
121
+ StringLike:
122
+ "token.actions.githubusercontent.com:sub": !Sub "repo:${GitHubRepoName}:*"
123
+ Policies:
124
+ - PolicyName: "GitHubActionsPolicy"
125
+ PolicyDocument:
126
+ Version: "2012-10-17"
127
+ Statement:
128
+ - Effect: "Allow"
129
+ Action: "*"
130
+ Resource: "*"
131
+ GitHubOidcProvider:
132
+ Type: 'AWS::IAM::OIDCProvider'
133
+ Properties:
134
+ Url: 'https://token.actions.githubusercontent.com'
135
+ ClientIdList:
136
+ - 'sts.amazonaws.com'
137
+ ThumbprintList:
138
+ - '6938fd4d98bab03faadb97b34396831e3780aea1'
@@ -7,14 +7,14 @@ const CF = require('./helpers/cf');
7
7
 
8
8
 
9
9
  exports.main = async function(args){
10
- console.log(`Setting up ${args.name}@ in ${args.region}`)
10
+ console.log(`Setting up ${args.name}@ in ${args.region} with repo ${args.repo}`)
11
11
 
12
12
  // Get VPC ID
13
13
  const VpcId = await getDefaultVPC(args.region);
14
14
  console.log(`Default VPC: ${VpcId}`);
15
15
 
16
16
  // Deploy Stack
17
- const success = await CF.deployOrgStack(args.region, {orgName: args.name, VpcId: VpcId})
17
+ const success = await CF.deployOrgStack(args.region, {orgName: args.name, VpcId: VpcId, GitHubRepoName: args.repo})
18
18
  if (success === false){
19
19
  console.log('ERR: Org already exists. Only one org permitted per account');
20
20
  throw new Error('Org already exists')
@@ -28,6 +28,25 @@ exports.main = async function(args){
28
28
  return true;
29
29
  }
30
30
 
31
+ exports.updateOrgStack = async function(args){
32
+ console.log(`Updating ${args.name}@ in ${args.region} with repo ${args.repo}`)
33
+
34
+ // Get VPC ID
35
+ const VpcId = await getDefaultVPC(args.region);
36
+ console.log(`Default VPC: ${VpcId}`);
37
+
38
+ // Deploy Stack
39
+ const success = await CF.updateOrgStack(args.region, {orgName: args.name, VpcId: VpcId, GitHubRepoName: args.repo})
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');
@@ -33,7 +33,7 @@ exports.main = async function(args){
33
33
  // I.I WAIT FOR AMI TO BE AVAILABLE
34
34
  console.log(`Waiting for AMI ${targetAMI} to be available`);
35
35
  let isAvailable = false;
36
- for (let i = 0; i < 24; i++){
36
+ for (let i = 0; i < 40; i++){
37
37
  const status = await EC2.checkAMIStatus(targetAMI,targetRegion);
38
38
  if (status === true){
39
39
  console.log(`AMI ${targetAMI} available after ${i*30}s`);
@@ -43,7 +43,7 @@ exports.main = async function(args){
43
43
  console.log(`\tAMI Status Check ${i} @${i*30}s : Not Available`);
44
44
  await Common.sleep(30);
45
45
  }
46
- if (!isAvailable){ throw new Error(`AMI not available after 14 minutes. Try again in a few minutes.`) }
46
+ if (!isAvailable){ throw new Error(`AMI not available after 20 minutes. Try again in a few minutes.`) }
47
47
 
48
48
  // --- II DEPLOY CF STACK ---
49
49
  // Get Stack URL
@@ -29,18 +29,26 @@ exports.main = async function(args){
29
29
 
30
30
  // --- I PREP ZIP ---
31
31
  const zipPath = path.resolve(args.path);
32
- if (!fs.existsSync(zipPath)){ throw new Error("Path not found:" + args.path)}
32
+ if (!fs.existsSync(zipPath)){ throw new Error("Path not found:" + zipPath)}
33
+
33
34
  const zipFilePath = await prepZip(zipPath);
34
35
  await S3.uploadInfraFile(`apps/${args.app}/${args.v}/app.zip`,zipFilePath);
35
36
 
36
37
  // --- II UPDATE STACK ---
37
38
  // If stack arg, upload stack
38
39
  const stackKey = `apps/${args.app}/${args.v}/stack.yaml`;
39
- // If no stack arg, upload default stack if none exists
40
- const stackExists = await S3.infraFileExists(stackKey)
41
- if (!stackExists){
42
- console.log('Copying default stack to ' + `apps/${args.app}/${args.v}`);
43
- 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
+ }
44
52
  }
45
53
 
46
54
  // --- III BUILD IMAGE ---
@@ -174,7 +182,7 @@ async function waitUntilInstanceReady(instance_id,region){
174
182
  let totalSleepTime = 0;
175
183
  let ok = false;
176
184
  const command = new DescribeInstanceStatusCommand(input);
177
- for (let i=0; i<50; i++){
185
+ for (let i=0; i<100; i++){
178
186
  const response = await client.send(command);
179
187
  const status = response.InstanceStatuses[0].InstanceStatus.Status;
180
188
  console.log(`\tCheck ${i+1} @ ${totalSleepTime}s: EC2 Status is ${status}`)
@@ -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
@@ -11,7 +11,17 @@ const Commands = {
11
11
  exec: require('./commands/init_org').main,
12
12
  args: [
13
13
  {n: 'name', desc: 'Unique org Name. Letters only', r: true, pattern: `[A-Za-z]{2,20}`},
14
- {n: 'region', desc: 'AWS Region for Core Assets. Default us-east-1', r: false}
14
+ {n: 'region', desc: 'AWS Region for Core Assets. Default us-east-1', r: false},
15
+ {n: 'repo', desc: 'Github repo name', r: false}
16
+ ]
17
+ },
18
+ 'update-org': {
19
+ desc: "Update org stack",
20
+ exec: require('./commands/init_org').updateOrgStack,
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}
15
25
  ]
16
26
  },
17
27
  'set-org': {
@@ -65,6 +75,16 @@ const Commands = {
65
75
  {n: 'stack', desc: 'Path to updated JSON or YML stack', r: false}
66
76
  ]
67
77
  },
78
+ 'get-stack': {
79
+ desc: 'Get stack',
80
+ exec: require('./commands/get_stack').main,
81
+ args: [
82
+ {n: 'app', desc: 'Name of existing app', pattern: `[A-Za-z]{2,20}`, r: true},
83
+ {n: 'v', desc: 'Version to update', pattern: `[0-9]{1,20}`, r: false},
84
+ {n: 'default', desc: 'Update default version', r: false},
85
+ {n: 'out', desc: 'Path to output stack file', r: true}
86
+ ]
87
+ },
68
88
  'launch': {
69
89
  desc: 'Launch application version to an instance',
70
90
  exec: require('./commands/launch_app').main,
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"cloudmason","version":"1.0.10","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.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"}}
package/test.bat DELETED
@@ -1,16 +0,0 @@
1
- @REM node main.js init-org -name orgTheorem -region us-west-2
2
- node main.js list-apps
3
- @REM node main.js new-app -name ot -type asg
4
- @REM node main.js new-instance -app ot -domain local.elmnts.xyz -region us-west-2 -admin kkh@kkh.io -env local
5
- @REM node main.js update-app -app ot -v 1.0 -path ./commands/starters/asg_node
6
- @REM node main.js update-stack -app ot -v 1.0 -stack ./commands/helpers/stacks/asg.yaml
7
- @REM node main.js launch -app ot -v 1.0 -domain local.elmnts.xyz
8
- @REM node main.js inspect -app ot -domain local.elmnts.xyz -run
9
- @REM node main.js isvalid -p ./commands/helpers/stacks/asg.yaml
10
-
11
- @REM node main.js reset-stack -app meantto
12
- @REM node main.js delete-instance -app ot -domain ot.elmnts.xyz
13
- @REM node main.js delete-app -app inc
14
- @REM aws ec2 get-console-output --instance-id i-0fba7c360fc2de96f --region us-west-2 --latest
15
-
16
- @REM node main.js starter -type asg -l node -p ../../myfirstapp