cloudmason2 1.4.0 → 1.6.0
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/helpers/mime.js +36 -0
- package/commands/index.html +1 -0
- package/commands/new_app.js +20 -14
- package/commands/publish.js +2 -1
- package/commands/update_app.js +4 -2
- package/commands/update_assets.js +2 -9
- package/commands/update_listing.js +47 -14
- package/main.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
const TYPES = {
|
|
4
|
+
'.html': 'text/html',
|
|
5
|
+
'.css': 'text/css',
|
|
6
|
+
'.js': 'application/javascript',
|
|
7
|
+
'.mjs': 'application/javascript',
|
|
8
|
+
'.json': 'application/json',
|
|
9
|
+
'.txt': 'text/plain',
|
|
10
|
+
'.md': 'text/markdown',
|
|
11
|
+
'.csv': 'text/csv',
|
|
12
|
+
'.xml': 'application/xml',
|
|
13
|
+
'.yaml': 'text/yaml',
|
|
14
|
+
'.yml': 'text/yaml',
|
|
15
|
+
'.png': 'image/png',
|
|
16
|
+
'.jpg': 'image/jpeg',
|
|
17
|
+
'.jpeg': 'image/jpeg',
|
|
18
|
+
'.gif': 'image/gif',
|
|
19
|
+
'.svg': 'image/svg+xml',
|
|
20
|
+
'.webp': 'image/webp',
|
|
21
|
+
'.ico': 'image/x-icon',
|
|
22
|
+
'.woff': 'font/woff',
|
|
23
|
+
'.woff2': 'font/woff2',
|
|
24
|
+
'.ttf': 'font/ttf',
|
|
25
|
+
'.otf': 'font/otf',
|
|
26
|
+
'.mp4': 'video/mp4',
|
|
27
|
+
'.webm': 'video/webm',
|
|
28
|
+
'.pdf': 'application/pdf',
|
|
29
|
+
'.zip': 'application/zip',
|
|
30
|
+
'.gz': 'application/gzip'
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Content type from a file path or s3 key
|
|
34
|
+
exports.contentType = function(p){
|
|
35
|
+
return TYPES[path.extname(p).toLowerCase()] || 'application/octet-stream';
|
|
36
|
+
}
|
package/commands/index.html
CHANGED
|
@@ -227,6 +227,7 @@ class MasonApp extends ELMNT
|
|
|
227
227
|
background: #ffffff;
|
|
228
228
|
}
|
|
229
229
|
.head:hover { background: #e8e8e8; }
|
|
230
|
+
.name { text-transform: uppercase; letter-spacing: 0.5px; }
|
|
230
231
|
.count { margin-left: auto; font-size: 12px; font-weight: 400; color: #525252; }
|
|
231
232
|
.chev { transition: transform 0.15s; color: #525252; }
|
|
232
233
|
.open .chev { transform: rotate(180deg); }
|
package/commands/new_app.js
CHANGED
|
@@ -105,6 +105,25 @@ function createLocalMarketplace(){
|
|
|
105
105
|
console.log(`Created ${destDir}`);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// Trust policy allowing GitHub Actions OIDC assumption from one repo.
|
|
109
|
+
// Shared with update-listing's -repo trust update
|
|
110
|
+
exports.githubTrustPolicy = function(oidcArn, repo){
|
|
111
|
+
return {
|
|
112
|
+
Version: "2012-10-17",
|
|
113
|
+
Statement: [
|
|
114
|
+
{
|
|
115
|
+
Effect: "Allow",
|
|
116
|
+
Principal: { Federated: oidcArn },
|
|
117
|
+
Action: "sts:AssumeRoleWithWebIdentity",
|
|
118
|
+
Condition: {
|
|
119
|
+
StringEquals: { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" },
|
|
120
|
+
StringLike: { "token.actions.githubusercontent.com:sub": `repo:${repo}:*` }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
108
127
|
// Create a role GitHub Actions can assume via OIDC from the given repo.
|
|
109
128
|
// Permissions cover update-listing, update-app, launch, and publish - not new-app
|
|
110
129
|
async function ensureGithubRole(repo, bucket, appPrefix, appName, productId){
|
|
@@ -124,20 +143,7 @@ async function ensureGithubRole(repo, bucket, appPrefix, appName, productId){
|
|
|
124
143
|
}
|
|
125
144
|
|
|
126
145
|
// Trust: only GitHub Actions workflows from this repo
|
|
127
|
-
const trustPolicy =
|
|
128
|
-
Version: "2012-10-17",
|
|
129
|
-
Statement: [
|
|
130
|
-
{
|
|
131
|
-
Effect: "Allow",
|
|
132
|
-
Principal: { Federated: oidcArn },
|
|
133
|
-
Action: "sts:AssumeRoleWithWebIdentity",
|
|
134
|
-
Condition: {
|
|
135
|
-
StringEquals: { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" },
|
|
136
|
-
StringLike: { "token.actions.githubusercontent.com:sub": `repo:${repo}:*` }
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
]
|
|
140
|
-
};
|
|
146
|
+
const trustPolicy = exports.githubTrustPolicy(oidcArn, repo);
|
|
141
147
|
const created = await iam.send(new CreateRoleCommand({
|
|
142
148
|
RoleName: roleName,
|
|
143
149
|
AssumeRolePolicyDocument: JSON.stringify(trustPolicy),
|
package/commands/publish.js
CHANGED
|
@@ -3,6 +3,7 @@ const { S3Client, PutObjectCommand, GetObjectCommand } = require("@aws-sdk/clien
|
|
|
3
3
|
const { SSMClient, GetParameterCommand } = require("@aws-sdk/client-ssm");
|
|
4
4
|
const OrgConfig = require('./helpers/org_config');
|
|
5
5
|
const Apps = require('./helpers/apps');
|
|
6
|
+
const Mime = require('./helpers/mime');
|
|
6
7
|
|
|
7
8
|
// The Marketplace Catalog API is only available in us-east-1
|
|
8
9
|
const CATALOG_REGION = 'us-east-1';
|
|
@@ -53,7 +54,7 @@ exports.main = async function(args){
|
|
|
53
54
|
const amiAlias = `resolve:ssm:/aws/service/marketplace/${manifest.productId}/${fullVersion}`;
|
|
54
55
|
const upgradeText = buildUpgradeTemplate(stackText, amiAlias);
|
|
55
56
|
const upgradeKey = `${appPrefix}/versions/${vFolder}/upgrade.yaml`;
|
|
56
|
-
await s3.send(new PutObjectCommand({ Bucket: bucket, Key: upgradeKey, Body: upgradeText }));
|
|
57
|
+
await s3.send(new PutObjectCommand({ Bucket: bucket, Key: upgradeKey, Body: upgradeText, ContentType: Mime.contentType(upgradeKey) }));
|
|
57
58
|
console.log(`Uploaded upgrade template s3://${bucket}/${upgradeKey}`);
|
|
58
59
|
|
|
59
60
|
// --- III START CHANGE SET ---
|
package/commands/update_app.js
CHANGED
|
@@ -5,6 +5,7 @@ const { S3Client, PutObjectCommand, GetObjectCommand } = require("@aws-sdk/clien
|
|
|
5
5
|
const { EC2Client, DescribeRegionsCommand, DescribeImagesCommand, DeregisterImageCommand, DeleteSnapshotCommand } = require("@aws-sdk/client-ec2");
|
|
6
6
|
const OrgConfig = require('./helpers/org_config');
|
|
7
7
|
const Apps = require('./helpers/apps');
|
|
8
|
+
const Mime = require('./helpers/mime');
|
|
8
9
|
const { buildAMI } = require('./ssh_build');
|
|
9
10
|
|
|
10
11
|
exports.main = async function(args){
|
|
@@ -93,7 +94,7 @@ exports.main = async function(args){
|
|
|
93
94
|
console.log('No AmiId parameter found in stack. Template unchanged');
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
|
-
await s3.send(new PutObjectCommand({ Bucket: bucket, Key: stackKey, Body: finalStack }));
|
|
97
|
+
await s3.send(new PutObjectCommand({ Bucket: bucket, Key: stackKey, Body: finalStack, ContentType: Mime.contentType(stackKey) }));
|
|
97
98
|
console.log(`Uploaded s3://${bucket}/${stackKey}`);
|
|
98
99
|
|
|
99
100
|
// --- V UPDATE MANIFEST ---
|
|
@@ -162,7 +163,8 @@ async function uploadFile(s3, bucket, key, localPath){
|
|
|
162
163
|
await s3.send(new PutObjectCommand({
|
|
163
164
|
Bucket: bucket,
|
|
164
165
|
Key: key,
|
|
165
|
-
Body: fs.createReadStream(localPath)
|
|
166
|
+
Body: fs.createReadStream(localPath),
|
|
167
|
+
ContentType: Mime.contentType(key)
|
|
166
168
|
}));
|
|
167
169
|
}
|
|
168
170
|
|
|
@@ -3,14 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
|
|
4
4
|
const OrgConfig = require('./helpers/org_config');
|
|
5
5
|
const Apps = require('./helpers/apps');
|
|
6
|
-
|
|
7
|
-
const CONTENT_TYPES = {
|
|
8
|
-
'.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript',
|
|
9
|
-
'.json': 'application/json', '.txt': 'text/plain', '.md': 'text/markdown',
|
|
10
|
-
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',
|
|
11
|
-
'.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.csv': 'text/csv',
|
|
12
|
-
'.yaml': 'text/yaml', '.yml': 'text/yaml', '.zip': 'application/zip', '.pdf': 'application/pdf'
|
|
13
|
-
};
|
|
6
|
+
const Mime = require('./helpers/mime');
|
|
14
7
|
|
|
15
8
|
// update-assets: upload a file or folder to the app's cdn folder
|
|
16
9
|
exports.main = async function(args){
|
|
@@ -45,7 +38,7 @@ exports.main = async function(args){
|
|
|
45
38
|
Bucket: bucket,
|
|
46
39
|
Key: key,
|
|
47
40
|
Body: fs.createReadStream(f),
|
|
48
|
-
ContentType:
|
|
41
|
+
ContentType: Mime.contentType(f)
|
|
49
42
|
}));
|
|
50
43
|
console.log(`Uploaded s3://${bucket}/${key}`);
|
|
51
44
|
uploaded += 1;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
|
|
4
|
+
const { IAMClient, GetRoleCommand, UpdateAssumeRolePolicyCommand } = require("@aws-sdk/client-iam");
|
|
5
|
+
const { STSClient, GetCallerIdentityCommand } = require("@aws-sdk/client-sts");
|
|
4
6
|
const OrgConfig = require('./helpers/org_config');
|
|
5
7
|
const Apps = require('./helpers/apps');
|
|
8
|
+
const Mime = require('./helpers/mime');
|
|
9
|
+
const { githubTrustPolicy } = require('./new_app');
|
|
6
10
|
|
|
7
11
|
// Marketplace listing text files stored as top-level manifest attributes.
|
|
8
12
|
// Names match the keys the AWS Marketplace Catalog API expects
|
|
@@ -26,25 +30,32 @@ exports.main = async function(args){
|
|
|
26
30
|
if (!app){ throw new Error('No app named ' + args.app + '. Run new-app') }
|
|
27
31
|
const { appPrefix, manifest } = app;
|
|
28
32
|
|
|
29
|
-
if (!args.assets && !args.pid && !args.ec2){
|
|
30
|
-
console.log('Nothing to update. Pass -assets, -pid, or -
|
|
33
|
+
if (!args.assets && !args.pid && !args.ec2 && !args.repo){
|
|
34
|
+
console.log('Nothing to update. Pass -assets, -pid, -ec2, or -repo');
|
|
31
35
|
return true;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
// Apply all manifest updates in one download/update/upload pass
|
|
35
|
-
if (args.assets){
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
if (args.assets || args.pid || args.ec2){
|
|
40
|
+
if (args.assets){
|
|
41
|
+
await applyAssets(s3, bucket, appPrefix, cfg.region, args.assets, manifest);
|
|
42
|
+
}
|
|
43
|
+
if (args.pid){
|
|
44
|
+
manifest.productId = args.pid;
|
|
45
|
+
console.log('Set productId');
|
|
46
|
+
}
|
|
47
|
+
if (args.ec2){
|
|
48
|
+
manifest.RecommendedInstanceType = args.ec2;
|
|
49
|
+
console.log('Set RecommendedInstanceType');
|
|
50
|
+
}
|
|
51
|
+
await putManifest(s3, bucket, appPrefix, manifest);
|
|
52
|
+
console.log('Updated manifest');
|
|
41
53
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
|
|
55
|
+
// Replace the CI role trust policy with one for the updated repo
|
|
56
|
+
if (args.repo){
|
|
57
|
+
await updateRepoTrust(appName, args.repo);
|
|
45
58
|
}
|
|
46
|
-
await putManifest(s3, bucket, appPrefix, manifest);
|
|
47
|
-
console.log('Updated manifest');
|
|
48
59
|
return true;
|
|
49
60
|
}
|
|
50
61
|
|
|
@@ -107,13 +118,35 @@ async function applyAssets(s3, bucket, appPrefix, region, assetsPath, manifest){
|
|
|
107
118
|
Bucket: bucket,
|
|
108
119
|
Key: key,
|
|
109
120
|
Body: fs.createReadStream(imgPath),
|
|
110
|
-
ContentType:
|
|
121
|
+
ContentType: Mime.contentType(img.file)
|
|
111
122
|
}));
|
|
112
123
|
manifest[img.attr] = `https://${bucket}.s3.${region}.amazonaws.com/${key}`;
|
|
113
124
|
console.log(`Uploaded ${img.file}: ${manifest[img.attr]}`);
|
|
114
125
|
}
|
|
115
126
|
}
|
|
116
127
|
|
|
128
|
+
async function updateRepoTrust(appName, repo){
|
|
129
|
+
if (!/^[\w.-]+\/[\w.-]+$/.test(repo)){ throw new Error('Invalid repo. Use org/repo format') }
|
|
130
|
+
const roleName = `mason-gha-${appName}`;
|
|
131
|
+
const iam = new IAMClient({ region: 'us-east-1' });
|
|
132
|
+
const sts = new STSClient({ region: 'us-east-1' });
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
await iam.send(new GetRoleCommand({ RoleName: roleName }));
|
|
136
|
+
} catch (e){
|
|
137
|
+
if (/NoSuchEntity/.test(e)){ throw new Error(`No CI role ${roleName} found. Run new-app with -repo first`) }
|
|
138
|
+
throw e;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const accountId = (await sts.send(new GetCallerIdentityCommand({}))).Account;
|
|
142
|
+
const oidcArn = `arn:aws:iam::${accountId}:oidc-provider/token.actions.githubusercontent.com`;
|
|
143
|
+
await iam.send(new UpdateAssumeRolePolicyCommand({
|
|
144
|
+
RoleName: roleName,
|
|
145
|
+
PolicyDocument: JSON.stringify(githubTrustPolicy(oidcArn, repo))
|
|
146
|
+
}));
|
|
147
|
+
console.log(`Updated ${roleName} trust policy to repo ${repo}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
117
150
|
async function putManifest(s3, bucket, appPrefix, manifest){
|
|
118
151
|
await s3.send(new PutObjectCommand({
|
|
119
152
|
Bucket: bucket,
|
package/main.js
CHANGED
|
@@ -39,7 +39,8 @@ const Commands = {
|
|
|
39
39
|
{n: 'app', desc: 'Name of existing app', pattern: `^[A-Za-z]{2,20}$`, r: true},
|
|
40
40
|
{n: 'assets', desc: 'Path to folder with application assets', r: false},
|
|
41
41
|
{n: 'pid', desc: 'AWS Product ID', r: false},
|
|
42
|
-
{n: 'ec2', desc: 'Instance type (default r8g.medium)', r: false}
|
|
42
|
+
{n: 'ec2', desc: 'Instance type (default r8g.medium)', r: false},
|
|
43
|
+
{n: 'repo', desc: 'GitHub repo (org/repo). Replaces the CI role trust policy with this repo', r: false}
|
|
43
44
|
]
|
|
44
45
|
},
|
|
45
46
|
'update-assets': {
|