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.
- package/commands/get_stack.js +30 -0
- package/commands/helpers/cf.js +23 -1
- package/commands/helpers/s3.js +21 -0
- package/commands/helpers/stacks/asg.yaml +3 -0
- package/commands/helpers/stacks/infra.yaml +37 -1
- package/commands/init_org.js +21 -2
- package/commands/launch_app.js +2 -2
- package/commands/update_app.js +15 -7
- package/commands/update_stack.js +4 -4
- package/main.js +21 -1
- package/package.json +1 -1
- package/test.bat +0 -16
|
@@ -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
|
+
}
|
package/commands/helpers/cf.js
CHANGED
|
@@ -22,7 +22,7 @@ exports.deployOrgStack = async function(region,params){
|
|
|
22
22
|
StackName: `CoreInfra`,
|
|
23
23
|
TemplateBody: stackYML,
|
|
24
24
|
Capabilities: [
|
|
25
|
-
"CAPABILITY_IAM"
|
|
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] } })
|
package/commands/helpers/s3.js
CHANGED
|
@@ -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;
|
|
@@ -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'
|
package/commands/init_org.js
CHANGED
|
@@ -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');
|
package/commands/launch_app.js
CHANGED
|
@@ -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 <
|
|
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
|
|
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
|
package/commands/update_app.js
CHANGED
|
@@ -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:" +
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
await S3.
|
|
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<
|
|
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}`)
|
package/commands/update_stack.js
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
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.
|
|
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
|