@fishawack/lab-env 2.0.1 → 2.2.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/CHANGELOG.md +16 -0
- package/_Test/_fixtures/.gitkeep +0 -0
- package/_Test/_helpers/globals.js +10 -0
- package/_Test/provision.js +33 -0
- package/cli.js +6 -6
- package/commands/create/cmds/deprovision.js +53 -0
- package/commands/create/cmds/diagnose.js +5 -3
- package/commands/create/cmds/provision.js +120 -0
- package/commands/create/libs/aws-cloudfront-auth.js +77 -0
- package/commands/create/libs/aws-cloudfront-simple.js +51 -0
- package/commands/create/libs/utilities.js +49 -2
- package/commands/create/services/aws/cloudfront.js +289 -0
- package/commands/create/services/aws/index.js +22 -0
- package/commands/create/services/aws/s3.js +100 -0
- package/commands/create/services/guide.js +13 -0
- package/core/{0.0.20 → 0.1.0}/Dockerfile +7 -1
- package/core/{0.0.20 → 0.1.0}/entrypoint.sh +0 -0
- package/core/CHANGELOG.md +4 -0
- package/core/package.json +2 -2
- package/craftcms/3/docker-compose.yml +2 -1
- package/craftcms/3/php/Dockerfile +2 -5
- package/craftcms/3/php/policy.xml +99 -0
- package/drupal/9/docker-compose.yml +1 -1
- package/drupal/9/php/Dockerfile +0 -6
- package/globals.js +8 -0
- package/laravel/8/docker-compose.yml +1 -1
- package/laravel/8/php/Dockerfile +2 -8
- package/package.json +15 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
+
### 2.2.0 (2022-08-10)
|
|
4
|
+
* [Feature] Can now skip diagnose in `fw diagnose`
|
|
5
|
+
* [Feature] Can now provision AWS environments using `fw provision`
|
|
6
|
+
* [Change] Now requires node > 16
|
|
7
|
+
* [Change] Auditted npm dependencies
|
|
8
|
+
* [Misc] Added test coverage for new AWS service
|
|
9
|
+
|
|
10
|
+
### 2.1.0 (2022-05-27)
|
|
11
|
+
* [Change] Bumped core `0.0.21` to `0.1.0`
|
|
12
|
+
|
|
13
|
+
### 2.0.2 (2022-04-26)
|
|
14
|
+
* [Change] Bumped core `0.0.20` to `0.0.21`
|
|
15
|
+
* [Bug] Lock eb-cli due to breaking change
|
|
16
|
+
* [Bug] Removed imagemagick from php containers as it's now included in base image
|
|
17
|
+
* [Misc] Updated core publish postversion command
|
|
18
|
+
|
|
3
19
|
### 2.0.1 (2022-04-13)
|
|
4
20
|
* [Bug] Explicitly set platform to fix M1 chip issues
|
|
5
21
|
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const host = "ftp-fishawack.egnyte.com";
|
|
4
|
+
const creds = JSON.parse(fs.readFileSync(`${os.homedir()}/targets/.ftppass`))[host];
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
opts: {encoding: 'utf8', stdio: process.argv.includes('--publish') ? 'pipe' : 'inherit'},
|
|
8
|
+
host,
|
|
9
|
+
creds
|
|
10
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const expect = require('chai').expect;
|
|
4
|
+
const aws = require("../commands/create/services/aws/index.js");
|
|
5
|
+
var fetch;
|
|
6
|
+
|
|
7
|
+
describe('provision', async () => {
|
|
8
|
+
let config;
|
|
9
|
+
let repo = 'lab-env-test-suite';
|
|
10
|
+
let account = 'fishawack';
|
|
11
|
+
|
|
12
|
+
before(async () => {
|
|
13
|
+
fetch = (await import('node-fetch')).default;
|
|
14
|
+
|
|
15
|
+
config = await aws.static(repo, account);
|
|
16
|
+
|
|
17
|
+
await aws.s3.addFileToS3Bucket(repo, account, 'index.html', new TextEncoder().encode("test"));
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('Should provision s3 bucket', async () => {
|
|
21
|
+
expect((await fetch(config.url)).status).to.be.equal(200);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
after(async () => {
|
|
25
|
+
await aws.s3.removeFileToS3Bucket(repo, account, 'index.html');
|
|
26
|
+
|
|
27
|
+
await aws.s3.removeS3Bucket(repo, account);
|
|
28
|
+
|
|
29
|
+
await aws.cloudfront.removeCloudFrontDistribution(config.cloudfront, account);
|
|
30
|
+
|
|
31
|
+
await aws.cloudfront.removeCloudFrontFunction(repo, account);
|
|
32
|
+
});
|
|
33
|
+
});
|
package/cli.js
CHANGED
|
@@ -4,10 +4,6 @@ process.env.CWD = process.cwd();
|
|
|
4
4
|
|
|
5
5
|
const _ = require('./globals.js');
|
|
6
6
|
|
|
7
|
-
const updateNotifier = require('update-notifier');
|
|
8
|
-
const pkg = require('./package.json');
|
|
9
|
-
updateNotifier({pkg, updateCheckInterval: 0}).notify();
|
|
10
|
-
|
|
11
7
|
const execSync = require('child_process').execSync;
|
|
12
8
|
|
|
13
9
|
const yargs = require('yargs/yargs');
|
|
@@ -16,9 +12,13 @@ const { hideBin } = require('yargs/helpers');
|
|
|
16
12
|
const args = hideBin(process.argv);
|
|
17
13
|
|
|
18
14
|
// Stop here if docker process not running and command isn't version or origin which don't require docker
|
|
19
|
-
if(!_.services && !(args[0] === 'origin' || args[0] === '--version'))
|
|
15
|
+
if(!_.services && !(args[0] === 'origin' || args[0] === '--version')) process.exit();
|
|
20
16
|
|
|
21
17
|
(async () => {
|
|
18
|
+
const updateNotifier = (await import('update-notifier')).default;
|
|
19
|
+
const pkg = require('./package.json');
|
|
20
|
+
updateNotifier({pkg, updateCheckInterval: 0}).notify();
|
|
21
|
+
|
|
22
22
|
process.env.REPO = _.repo;
|
|
23
23
|
|
|
24
24
|
await _.ports.set();
|
|
@@ -59,7 +59,7 @@ if(!_.services && !(args[0] === 'origin' || args[0] === '--version')) return;
|
|
|
59
59
|
['build', 'config', 'down', 'mocha', 'rebuild', 'up', 'volumes', 'compose'].forEach(d => cli.command(...require(`./commands/docker/${d}.js`)));
|
|
60
60
|
|
|
61
61
|
// Create commands
|
|
62
|
-
['new', 'diagnose', 'delete'].forEach(d => cli.command(...require(`./commands/create/cmds/${d}.js`)));
|
|
62
|
+
['new', 'provision', 'deprovision', 'diagnose', 'delete'].forEach(d => cli.command(...require(`./commands/create/cmds/${d}.js`)));
|
|
63
63
|
|
|
64
64
|
cli.demandCommand(1, '')
|
|
65
65
|
.wrap(null)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const _ = require('../../../globals.js');
|
|
2
|
+
const inquirer = require('inquirer');
|
|
3
|
+
const aws = require('../services/aws/index.js');
|
|
4
|
+
|
|
5
|
+
module.exports = [
|
|
6
|
+
['deprovision', 'deprov'],
|
|
7
|
+
false,
|
|
8
|
+
yargs => {
|
|
9
|
+
yargs.option('branch', {
|
|
10
|
+
alias: 'b',
|
|
11
|
+
describe: 'Branch to configure',
|
|
12
|
+
type: 'string'
|
|
13
|
+
});
|
|
14
|
+
},
|
|
15
|
+
async argv => {
|
|
16
|
+
let branch = argv.branch || _.branch;
|
|
17
|
+
|
|
18
|
+
let answer = await inquirer.prompt([
|
|
19
|
+
{
|
|
20
|
+
type: 'confirm',
|
|
21
|
+
name: 'check',
|
|
22
|
+
message: `Deprovisioning fw-auto-${_.repo}-${branch}, are you sure you want to continue?`,
|
|
23
|
+
default: 'Y'
|
|
24
|
+
}
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
if(!answer.check){
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const answers = await inquirer.prompt([
|
|
32
|
+
{
|
|
33
|
+
type: 'input',
|
|
34
|
+
name: 'id',
|
|
35
|
+
message: 'What is the Id of the CloudFront distribution?',
|
|
36
|
+
validate: (input) => !!input.length
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'list',
|
|
40
|
+
name: 'client',
|
|
41
|
+
message: 'Which AWS account is this deployed too?',
|
|
42
|
+
choices: ['fishawack', 'abbvie', 'sanofigenzyme', 'gsk', 'janssen', 'astrazeneca', 'training'],
|
|
43
|
+
default: 'fishawack'
|
|
44
|
+
}
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
try { await aws.s3.removeS3Bucket(`fw-auto-${_.repo}-${branch}`, answers.client); } catch(e) {}
|
|
48
|
+
|
|
49
|
+
try { await aws.cloudfront.removeCloudFrontDistribution(answers.id, answers.client); } catch(e) {}
|
|
50
|
+
|
|
51
|
+
try { await aws.cloudfront.removeCloudFrontFunction(`fw-auto-${_.repo}-${branch}`, answers.client); } catch(e) {}
|
|
52
|
+
}
|
|
53
|
+
];
|
|
@@ -57,9 +57,11 @@ module.exports = [
|
|
|
57
57
|
while(!await test.bitbucket() || !await bitbucket.check()){
|
|
58
58
|
await guide.bitbucket();
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
await
|
|
60
|
+
|
|
61
|
+
if(!await guide.gitlabSkip()) {
|
|
62
|
+
while(!await test.gitlab() || !await gitlab.check()){
|
|
63
|
+
await guide.gitlab();
|
|
64
|
+
}
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
const userRepoName = vars.misc.bitbucket.username.split('@')[0].replace(/\./, '-');
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const _ = require('../../../globals.js');
|
|
2
|
+
const utilities = require('../libs/utilities');
|
|
3
|
+
const execSync = require('child_process').execSync;
|
|
4
|
+
const inquirer = require('inquirer');
|
|
5
|
+
const aws = require('../services/aws/index.js');
|
|
6
|
+
const generator = require('generate-password');
|
|
7
|
+
|
|
8
|
+
module.exports = [
|
|
9
|
+
['provision', 'prov'],
|
|
10
|
+
'Provisions the deployment target',
|
|
11
|
+
yargs => {
|
|
12
|
+
yargs.option('branch', {
|
|
13
|
+
alias: 'b',
|
|
14
|
+
describe: 'Branch to configure',
|
|
15
|
+
type: 'string'
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
async argv => {
|
|
19
|
+
let branch = argv.branch || _.branch;
|
|
20
|
+
|
|
21
|
+
let answer = await inquirer.prompt([
|
|
22
|
+
{
|
|
23
|
+
type: 'confirm',
|
|
24
|
+
name: 'check',
|
|
25
|
+
message: `Provisioning the ${utilities.colorize(branch, 'success')} branch, is this correct?`,
|
|
26
|
+
default: 'Y'
|
|
27
|
+
}
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
if(!answer.check){
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const answers = await inquirer.prompt([
|
|
35
|
+
{
|
|
36
|
+
type: 'list',
|
|
37
|
+
name: 'stack',
|
|
38
|
+
message: 'What type of project are you deploying?',
|
|
39
|
+
choices: ['static'],
|
|
40
|
+
default: 'static'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'list',
|
|
44
|
+
name: 'client',
|
|
45
|
+
message: 'Which AWS account should this be deployed too?',
|
|
46
|
+
choices: ['fishawack', 'abbvie', 'sanofigenzyme', 'gsk', 'janssen', 'astrazeneca', 'training'],
|
|
47
|
+
default: 'fishawack'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: 'confirm',
|
|
51
|
+
name: 'protected',
|
|
52
|
+
message: 'Should the site be password protected?',
|
|
53
|
+
default: true
|
|
54
|
+
}
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
let credentials = [];
|
|
58
|
+
|
|
59
|
+
if(answers.protected){
|
|
60
|
+
let user = {
|
|
61
|
+
another: true
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
while(user.another){
|
|
65
|
+
user = await inquirer.prompt([
|
|
66
|
+
{
|
|
67
|
+
type: 'input',
|
|
68
|
+
name: 'username',
|
|
69
|
+
message: 'Username',
|
|
70
|
+
default: `${_.repo_safe}User${credentials.length ? `-${credentials.length}` : ''}`,
|
|
71
|
+
validate: (input) => !!input.length
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: 'input',
|
|
75
|
+
name: 'password',
|
|
76
|
+
message: 'Password (leave empty to generate)',
|
|
77
|
+
default: generator.generate({ length: 10, numbers: true }),
|
|
78
|
+
validate: (input) => !!input.length
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
type: 'confirm',
|
|
82
|
+
name: 'another',
|
|
83
|
+
message: 'Add another user?',
|
|
84
|
+
default: false
|
|
85
|
+
}
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
credentials.push({username: user.username, password: user.password});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let config = {};
|
|
93
|
+
let infastructure;
|
|
94
|
+
|
|
95
|
+
try{
|
|
96
|
+
infastructure = await aws.static(`fw-auto-${_.repo}-${branch}`, answers.client, [{Key: 'repository', Value: _.repo}, {Key: 'environment', Value: branch}], credentials);
|
|
97
|
+
} catch(e){
|
|
98
|
+
console.log(e.message);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
config[branch] = {
|
|
103
|
+
deploy: {
|
|
104
|
+
"url": infastructure.url,
|
|
105
|
+
"location": infastructure.bucket.slice(1), // Remove / from start
|
|
106
|
+
"aws-s3": answers.client,
|
|
107
|
+
"aws-cloudfront": infastructure.cloudfront
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if(credentials.length){
|
|
112
|
+
config[branch].deploy.users = credentials;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let stringify = JSON.stringify(config, null, 4);
|
|
116
|
+
let output = stringify.substring(1, stringify.length-1).trim();
|
|
117
|
+
execSync(`printf '${output}' | pbcopy`);
|
|
118
|
+
console.log(utilities.colorize(`\n${output}\n\n(copied to clipboard)`, 'title'));
|
|
119
|
+
}
|
|
120
|
+
];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
function handler(event) {
|
|
2
|
+
// Redirect if www to non-www
|
|
3
|
+
if (event.request.headers.host.value.includes('www.')) {
|
|
4
|
+
var query = '';
|
|
5
|
+
var index = 0;
|
|
6
|
+
|
|
7
|
+
for(var key in event.request.querystring){
|
|
8
|
+
query += `${index ? '&' : '?'}${key}=${event.request.querystring[key].value}`;
|
|
9
|
+
index++;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
var response = {
|
|
13
|
+
statusCode: 301,
|
|
14
|
+
statusDescription: 'Moved Permanently',
|
|
15
|
+
headers: { "location": { "value": `${event.request.headers.host.value.split('www.')[1]}${event.request.uri}${query}` } }
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return response;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Redirect if no trailing slash
|
|
22
|
+
if(!event.request.uri.includes('.') && !event.request.uri.endsWith('/')){
|
|
23
|
+
var query = '';
|
|
24
|
+
var index = 0;
|
|
25
|
+
|
|
26
|
+
for(var key in event.request.querystring){
|
|
27
|
+
query += `${index ? '&' : '?'}${key}=${event.request.querystring[key].value}`;
|
|
28
|
+
index++;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
statusCode: 301,
|
|
33
|
+
statusDescription: 'Moved Permanently',
|
|
34
|
+
headers: { "location": { "value": `${event.request.uri}/${query}` } }
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
var authHeaders = event.request.headers.authorization;
|
|
39
|
+
|
|
40
|
+
// The Base64-encoded Auth string that should be present.
|
|
41
|
+
// It is an encoding of `Basic base64([username]:[password])`
|
|
42
|
+
var expected = <%= credentials %>;
|
|
43
|
+
|
|
44
|
+
// If an Authorization header is supplied and it's an exact match, pass the
|
|
45
|
+
// request on through to CF/the origin without any modification.
|
|
46
|
+
if(authHeaders && expected.find(d => d === authHeaders.value)) {
|
|
47
|
+
// Rewrite url to append index.html if not present
|
|
48
|
+
var request = event.request;
|
|
49
|
+
var uri = request.uri;
|
|
50
|
+
|
|
51
|
+
// Check whether the URI is missing a file name.
|
|
52
|
+
if(uri.endsWith('/')) {
|
|
53
|
+
request.uri += 'index.html';
|
|
54
|
+
}
|
|
55
|
+
// Check whether the URI is missing a file extension.
|
|
56
|
+
else if(!uri.includes('.')) {
|
|
57
|
+
request.uri += '/index.html';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return request;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// But if we get here, we must either be missing the auth header or the
|
|
64
|
+
// credentials failed to match what we expected.
|
|
65
|
+
// Request the browser present the Basic Auth dialog.
|
|
66
|
+
var response = {
|
|
67
|
+
statusCode: 401,
|
|
68
|
+
statusDescription: 'Unauthorized',
|
|
69
|
+
headers: {
|
|
70
|
+
"www-authenticate": {
|
|
71
|
+
value: 'Basic'
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return response;
|
|
77
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
function handler(event) {
|
|
2
|
+
// Redirect if www to non-www
|
|
3
|
+
if (event.request.headers.host.value.includes('www.')) {
|
|
4
|
+
var query = '';
|
|
5
|
+
var index = 0;
|
|
6
|
+
|
|
7
|
+
for(var key in event.request.querystring){
|
|
8
|
+
query += `${index ? '&' : '?'}${key}=${event.request.querystring[key].value}`;
|
|
9
|
+
index++;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
var response = {
|
|
13
|
+
statusCode: 301,
|
|
14
|
+
statusDescription: 'Moved Permanently',
|
|
15
|
+
headers: { "location": { "value": `${event.request.headers.host.value.split('www.')[1]}${event.request.uri}${query}` } }
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return response;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Redirect if no trailing slash
|
|
22
|
+
if(!event.request.uri.includes('.') && !event.request.uri.endsWith('/')){
|
|
23
|
+
var query = '';
|
|
24
|
+
var index = 0;
|
|
25
|
+
|
|
26
|
+
for(var key in event.request.querystring){
|
|
27
|
+
query += `${index ? '&' : '?'}${key}=${event.request.querystring[key].value}`;
|
|
28
|
+
index++;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
statusCode: 301,
|
|
33
|
+
statusDescription: 'Moved Permanently',
|
|
34
|
+
headers: { "location": { "value": `${event.request.uri}/${query}` } }
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
var request = event.request;
|
|
39
|
+
var uri = request.uri;
|
|
40
|
+
|
|
41
|
+
// Check whether the URI is missing a file name.
|
|
42
|
+
if(uri.endsWith('/')) {
|
|
43
|
+
request.uri += 'index.html';
|
|
44
|
+
}
|
|
45
|
+
// Check whether the URI is missing a file extension.
|
|
46
|
+
else if(!uri.includes('.')) {
|
|
47
|
+
request.uri += '/index.html';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return request;
|
|
51
|
+
}
|
|
@@ -23,9 +23,12 @@ const colorize = module.exports.colorize = (str, type) => {
|
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
let latestSpinner = null;
|
|
27
|
+
|
|
26
28
|
module.exports.Spinner = class Spinner {
|
|
27
|
-
constructor(startMessage) {
|
|
28
|
-
this.ora = ora(colorize(startMessage, 'info')).start();
|
|
29
|
+
constructor(startMessage, simple = false) {
|
|
30
|
+
this.ora = ora(simple ? startMessage : colorize(startMessage, 'info')).start();
|
|
31
|
+
latestSpinner = this;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
update(message, status) {
|
|
@@ -37,6 +40,50 @@ module.exports.Spinner = class Spinner {
|
|
|
37
40
|
this.ora.succeed(colorize(message, status || 'success'));
|
|
38
41
|
}
|
|
39
42
|
}
|
|
43
|
+
|
|
44
|
+
action(message, action, success, failure) {
|
|
45
|
+
return new Promise(async (resolve, reject) => {
|
|
46
|
+
let instance = new this.constructor(message);
|
|
47
|
+
let res;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
res = await action();
|
|
51
|
+
|
|
52
|
+
instance.update(success);
|
|
53
|
+
} catch (e){
|
|
54
|
+
instance.update(failure, 'fail');
|
|
55
|
+
reject(e);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
resolve(res);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
simple(message, action) {
|
|
63
|
+
return new Promise(async (resolve, reject) => {
|
|
64
|
+
let instance = new this.constructor(message, true);
|
|
65
|
+
let res;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
res = await action();
|
|
69
|
+
|
|
70
|
+
instance.ora.succeed();
|
|
71
|
+
} catch (e){
|
|
72
|
+
instance.ora.fail();
|
|
73
|
+
reject(e);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
resolve(res);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
ping(){
|
|
81
|
+
latestSpinner.ora.color = latestSpinner.ora.color === 'cyan' ? 'magenta' : 'cyan';
|
|
82
|
+
let message = ' - check: ';
|
|
83
|
+
let split = latestSpinner.ora.text.split(message);
|
|
84
|
+
let iteration = split[1] && +split[1].split(':')[0] || 0;
|
|
85
|
+
latestSpinner.ora.text = `${split[0]}${message}${iteration+1}`;
|
|
86
|
+
}
|
|
40
87
|
};
|
|
41
88
|
|
|
42
89
|
module.exports.encode = (username, password) => {
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
const { CloudFrontClient, CreateDistributionWithTagsCommand, CreateCloudFrontOriginAccessIdentityCommand, DeleteDistributionCommand , DeleteCloudFrontOriginAccessIdentityCommand, GetDistributionCommand, UpdateDistributionCommand, GetCloudFrontOriginAccessIdentityCommand, CreateFunctionCommand, GetFunctionCommand, UpdateFunctionCommand, PublishFunctionCommand, DeleteFunctionCommand, DescribeFunctionCommand } = require("@aws-sdk/client-cloudfront");
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const { Spinner } = require('../../libs/utilities');
|
|
4
|
+
|
|
5
|
+
let region = `us-east-1`;
|
|
6
|
+
|
|
7
|
+
const client = new CloudFrontClient({ region });
|
|
8
|
+
|
|
9
|
+
module.exports.createCloudFrontDistribution = async (name, account, tags = []) => {
|
|
10
|
+
process.env.AWS_PROFILE = account;
|
|
11
|
+
|
|
12
|
+
let OAI = await Spinner.prototype.simple(`Creating CloudFront OAI`, () => {
|
|
13
|
+
return client.send(
|
|
14
|
+
new CreateCloudFrontOriginAccessIdentityCommand({
|
|
15
|
+
CloudFrontOriginAccessIdentityConfig: {
|
|
16
|
+
CallerReference: name,
|
|
17
|
+
Comment: `lab-env provisioned CloudFront OAI for s3 bucket ${name}`,
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
)
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
let res;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
res = await Spinner.prototype.simple(`Creating CloudFront distribution with CloudFront OAI and tags`, () => {
|
|
27
|
+
return client.send(
|
|
28
|
+
new CreateDistributionWithTagsCommand({
|
|
29
|
+
DistributionConfigWithTags: {
|
|
30
|
+
DistributionConfig: {
|
|
31
|
+
Enabled: true,
|
|
32
|
+
CallerReference: name,
|
|
33
|
+
Comment: `lab-env provisioned CloudFront distribution for project ${name}`,
|
|
34
|
+
CustomErrorResponses: {
|
|
35
|
+
Items: [
|
|
36
|
+
{
|
|
37
|
+
ErrorCachingMinTTL: 0,
|
|
38
|
+
ErrorCode: 403,
|
|
39
|
+
ResponseCode: 200,
|
|
40
|
+
ResponsePagePath: '/index.html'
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
Quantity: 1
|
|
44
|
+
},
|
|
45
|
+
DefaultCacheBehavior: {
|
|
46
|
+
Compress: true,
|
|
47
|
+
TargetOriginId: `${name}.s3.${region}.amazonaws.com`,
|
|
48
|
+
ViewerProtocolPolicy: 'redirect-to-https',
|
|
49
|
+
CachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6' // Built in, Managed AWS Policy - Cache Optimized
|
|
50
|
+
},
|
|
51
|
+
Origins: {
|
|
52
|
+
Items: [
|
|
53
|
+
{
|
|
54
|
+
DomainName: `${name}.s3.${region}.amazonaws.com`,
|
|
55
|
+
Id: `${name}.s3.${region}.amazonaws.com`,
|
|
56
|
+
S3OriginConfig: {
|
|
57
|
+
OriginAccessIdentity: `origin-access-identity/cloudfront/${OAI.CloudFrontOriginAccessIdentity.Id}`
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
Quantity: 1
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
Tags: {
|
|
65
|
+
Items: [{Key: 'client', Value: account}].concat(tags)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
)
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await Spinner.prototype.simple(`Waiting for CloudFront distribution to deploy`, () => {
|
|
73
|
+
return module.exports.waitForCloudFrontDistribution(res.Distribution.Id, account);
|
|
74
|
+
});
|
|
75
|
+
} catch(e){
|
|
76
|
+
let Id = e.message.split(' ')[e.message.split(' ').length - 1];
|
|
77
|
+
|
|
78
|
+
res = await Spinner.prototype.simple(`Retrieving the already existing CloudFront distribution`, () => {
|
|
79
|
+
return client.send(
|
|
80
|
+
new GetDistributionCommand({ Id })
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return res;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports.removeCloudFrontDistribution = async (Id, account) => {
|
|
89
|
+
process.env.AWS_PROFILE = account;
|
|
90
|
+
|
|
91
|
+
let res = await Spinner.prototype.simple(`Retrieving the CloudFront distribution ${Id}`, () => {
|
|
92
|
+
return client.send(
|
|
93
|
+
new GetDistributionCommand({ Id })
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
let OAI = res.Distribution.DistributionConfig.Origins.Items[0].S3OriginConfig.OriginAccessIdentity.split('origin-access-identity/cloudfront/')[1];
|
|
98
|
+
|
|
99
|
+
res.Distribution.DistributionConfig.Enabled = false;
|
|
100
|
+
|
|
101
|
+
res = await Spinner.prototype.simple(`Disabling the CloudFront distribution`, () => {
|
|
102
|
+
return client.send(
|
|
103
|
+
new UpdateDistributionCommand({ DistributionConfig: res.Distribution.DistributionConfig, Id, IfMatch: res.ETag })
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await Spinner.prototype.simple(`Waiting for CloudFront distribution to deploy`, () => {
|
|
108
|
+
return module.exports.waitForCloudFrontDistribution(res.Distribution.Id, account);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
res = await Spinner.prototype.simple(`Deleting the CloudFront distribution`, () => {
|
|
112
|
+
return client.send(
|
|
113
|
+
new DeleteDistributionCommand({ Id, IfMatch: res.ETag })
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
res = await Spinner.prototype.simple(`Retrieving the CloudFront OAI`, () => {
|
|
118
|
+
return client.send(
|
|
119
|
+
new GetCloudFrontOriginAccessIdentityCommand({ Id: OAI })
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
res = await Spinner.prototype.simple(`Deleting the CloudFront OAI`, () => {
|
|
124
|
+
return client.send(
|
|
125
|
+
new DeleteCloudFrontOriginAccessIdentityCommand({ Id: OAI, IfMatch: res.ETag })
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports.waitForCloudFrontDistribution = async (Id, account) => {
|
|
131
|
+
process.env.AWS_PROFILE = account;
|
|
132
|
+
|
|
133
|
+
let status;
|
|
134
|
+
|
|
135
|
+
do{
|
|
136
|
+
await new Promise((resolve) => setTimeout(() => resolve(), 5000));
|
|
137
|
+
|
|
138
|
+
await Spinner.prototype.ping();
|
|
139
|
+
|
|
140
|
+
let check = await client.send(
|
|
141
|
+
new GetDistributionCommand({ Id })
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
status = check.Distribution.Status;
|
|
145
|
+
} while(status !== 'Deployed')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports.createCloudFrontFunction = async (name, account, fn, config) => {
|
|
149
|
+
process.env.AWS_PROFILE = account;
|
|
150
|
+
|
|
151
|
+
let FunctionConfig = {
|
|
152
|
+
Comment: `lab-env provisioned cloudfront function for project ${name} using code snippet ${fn}.js`,
|
|
153
|
+
Runtime: `cloudfront-js-1.0`
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
let res;
|
|
157
|
+
|
|
158
|
+
try{
|
|
159
|
+
res = await Spinner.prototype.simple(`Creating CloudFront function`, () => {
|
|
160
|
+
return client.send(
|
|
161
|
+
new CreateFunctionCommand({
|
|
162
|
+
Name: name,
|
|
163
|
+
FunctionCode: new TextEncoder().encode(" "),
|
|
164
|
+
FunctionConfig
|
|
165
|
+
})
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
} catch (e){
|
|
169
|
+
res = await Spinner.prototype.simple(`Retrieving the already existing CloudFront function`, () => {
|
|
170
|
+
return client.send(
|
|
171
|
+
new GetFunctionCommand({
|
|
172
|
+
Name: name
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let processedFn = fs.readFileSync(`${__dirname}/../../libs/${fn}.js`).toString();
|
|
179
|
+
processedFn = processedFn.replace(/<%=.*%>/g, (el) => JSON.stringify(config[el.slice(3, el.length - 2).trim()], null, 4));
|
|
180
|
+
|
|
181
|
+
res = await Spinner.prototype.simple(`Updating CloudFront function with ${fn}.js code`, () => {
|
|
182
|
+
return client.send(
|
|
183
|
+
new UpdateFunctionCommand({
|
|
184
|
+
Name: name,
|
|
185
|
+
FunctionCode: new TextEncoder().encode(processedFn),
|
|
186
|
+
FunctionConfig,
|
|
187
|
+
IfMatch: res.ETag
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
res = await Spinner.prototype.simple(`Publishing CloudFront function`, () => {
|
|
193
|
+
return client.send(
|
|
194
|
+
new PublishFunctionCommand({
|
|
195
|
+
Name: name,
|
|
196
|
+
IfMatch: res.ETag
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return res;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports.removeCloudFrontFunction = async (name, account) => {
|
|
205
|
+
process.env.AWS_PROFILE = account;
|
|
206
|
+
|
|
207
|
+
let res = await Spinner.prototype.simple(`Retrieving CloudFront function`, () => {
|
|
208
|
+
return client.send(
|
|
209
|
+
new GetFunctionCommand({
|
|
210
|
+
Name: name
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
res = await Spinner.prototype.simple(`Deleting CloudFront function`, () => {
|
|
216
|
+
return client.send(
|
|
217
|
+
new DeleteFunctionCommand({
|
|
218
|
+
Name: name,
|
|
219
|
+
IfMatch: res.ETag
|
|
220
|
+
})
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return res;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports.setCloudFrontFunctionAssociation = async (Id, account) => {
|
|
228
|
+
process.env.AWS_PROFILE = account;
|
|
229
|
+
|
|
230
|
+
let res = await Spinner.prototype.simple(`Retrieving CloudFront distribution`, () => {
|
|
231
|
+
return client.send(
|
|
232
|
+
new GetDistributionCommand({ Id })
|
|
233
|
+
);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
let { FunctionSummary: { FunctionMetadata: { FunctionARN } } } = await Spinner.prototype.simple(`Retrieving CloudFront function`, () => {
|
|
237
|
+
return client.send(
|
|
238
|
+
new DescribeFunctionCommand({
|
|
239
|
+
Name: res.Distribution.DistributionConfig.CallerReference,
|
|
240
|
+
Stage: 'LIVE'
|
|
241
|
+
})
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
res.Distribution.DistributionConfig.DefaultCacheBehavior.FunctionAssociations = {
|
|
246
|
+
Items: [
|
|
247
|
+
{
|
|
248
|
+
EventType: 'viewer-request',
|
|
249
|
+
FunctionARN: FunctionARN
|
|
250
|
+
}
|
|
251
|
+
],
|
|
252
|
+
Quantity: 1
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
res = await Spinner.prototype.simple(`Adding CloudFront function to CloudFront distribution`, () => {
|
|
256
|
+
return client.send(
|
|
257
|
+
new UpdateDistributionCommand({ DistributionConfig: res.Distribution.DistributionConfig, Id, IfMatch: res.ETag })
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
await Spinner.prototype.simple(`Waiting for CloudFront distribution to deploy`, () => {
|
|
262
|
+
return module.exports.waitForCloudFrontDistribution(res.Distribution.Id, account);
|
|
263
|
+
});
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
module.exports.removeCloudFrontFunctionAssociation = async (Id, account) => {
|
|
267
|
+
process.env.AWS_PROFILE = account;
|
|
268
|
+
|
|
269
|
+
let res = await Spinner.prototype.simple(`Retrieving CloudFront distribution`, () => {
|
|
270
|
+
return client.send(
|
|
271
|
+
new GetDistributionCommand({ Id })
|
|
272
|
+
);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
res.Distribution.DistributionConfig.DefaultCacheBehavior.FunctionAssociations = {
|
|
276
|
+
Items: [],
|
|
277
|
+
Quantity: 0
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
res = await Spinner.prototype.simple(`Removing CloudFront function from CloudFront distribution`, () => {
|
|
281
|
+
return client.send(
|
|
282
|
+
new UpdateDistributionCommand({ DistributionConfig: res.Distribution.DistributionConfig, Id, IfMatch: res.ETag })
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
await Spinner.prototype.simple(`Waiting for CloudFront distribution to deploy`, () => {
|
|
287
|
+
return module.exports.waitForCloudFrontDistribution(res.Distribution.Id, account);
|
|
288
|
+
});
|
|
289
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module.exports.s3 = require("./s3.js");
|
|
2
|
+
module.exports.cloudfront = require("./cloudfront.js");
|
|
3
|
+
|
|
4
|
+
module.exports.static = async (name, account, tags = [], credentials = []) => {
|
|
5
|
+
let s3 = await module.exports.s3.createS3Bucket(name, account, tags);
|
|
6
|
+
|
|
7
|
+
let cloudfront = await module.exports.cloudfront.createCloudFrontDistribution(name, account, tags);
|
|
8
|
+
|
|
9
|
+
await module.exports.s3.setS3BucketPolicy(name, account, cloudfront.Distribution.DistributionConfig.Origins.Items[0].S3OriginConfig.OriginAccessIdentity.split('origin-access-identity/cloudfront/')[1]);
|
|
10
|
+
|
|
11
|
+
await module.exports.cloudfront.createCloudFrontFunction(name, account, credentials.length ? 'aws-cloudfront-auth' : 'aws-cloudfront-simple', {credentials: credentials.map(d => `Basic ${Buffer.from(`${d.username}:${d.password}`).toString('base64')}`)});
|
|
12
|
+
|
|
13
|
+
await module.exports.cloudfront.setCloudFrontFunctionAssociation(cloudfront.Distribution.Id, account);
|
|
14
|
+
|
|
15
|
+
let config = {
|
|
16
|
+
"bucket": s3.Location,
|
|
17
|
+
"url": `https://${cloudfront.Distribution.DomainName}`,
|
|
18
|
+
"cloudfront": cloudfront.Distribution.Id,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return config;
|
|
22
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const { S3Client, CreateBucketCommand, DeleteBucketCommand, PutPublicAccessBlockCommand, PutBucketTaggingCommand, PutBucketPolicyCommand, PutObjectCommand, DeleteObjectCommand } = require("@aws-sdk/client-s3");
|
|
2
|
+
const { Spinner } = require('../../libs/utilities');
|
|
3
|
+
|
|
4
|
+
let region = `us-east-1`;
|
|
5
|
+
|
|
6
|
+
const client = new S3Client({ region });
|
|
7
|
+
|
|
8
|
+
module.exports.createS3Bucket = async (bucket, account, tags = []) => {
|
|
9
|
+
process.env.AWS_PROFILE = account;
|
|
10
|
+
|
|
11
|
+
let res = await Spinner.prototype.simple(`Creating s3 bucket ${bucket}`, () => {
|
|
12
|
+
return client.send(
|
|
13
|
+
new CreateBucketCommand({ Bucket: bucket })
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
await Spinner.prototype.simple(`Blocking all public access to s3 bucket`, () => {
|
|
18
|
+
return client.send(
|
|
19
|
+
new PutPublicAccessBlockCommand({ Bucket: bucket, PublicAccessBlockConfiguration: { BlockPublicAcls: true, BlockPublicPolicy: true, IgnorePublicAcls: true, RestrictPublicBuckets: true } })
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
await Spinner.prototype.simple(`Adding tags to s3 bucket`, () => {
|
|
24
|
+
return client.send(
|
|
25
|
+
new PutBucketTaggingCommand({ Bucket: bucket, Tagging: {TagSet: [{Key: 'client', Value: account}].concat(tags)} })
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return res;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports.removeS3Bucket = async (bucket, account) => {
|
|
33
|
+
process.env.AWS_PROFILE = account;
|
|
34
|
+
|
|
35
|
+
await Spinner.prototype.simple(`Removing s3 bucket ${bucket}`, () => {
|
|
36
|
+
return client.send(
|
|
37
|
+
new DeleteBucketCommand({ Bucket: bucket })
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports.setS3BucketPolicy = async (bucket, account, OAI) => {
|
|
43
|
+
process.env.AWS_PROFILE = account;
|
|
44
|
+
|
|
45
|
+
let res = await Spinner.prototype.simple(`Updating s3 bucket policy`, () => {
|
|
46
|
+
return client.send(
|
|
47
|
+
new PutBucketPolicyCommand({
|
|
48
|
+
Bucket: bucket,
|
|
49
|
+
Policy: JSON.stringify({
|
|
50
|
+
"Version": "2008-10-17",
|
|
51
|
+
"Id": "PolicyForCloudFrontPrivateContent",
|
|
52
|
+
"Statement": [
|
|
53
|
+
{
|
|
54
|
+
"Sid": "1",
|
|
55
|
+
"Effect": "Allow",
|
|
56
|
+
"Principal": {
|
|
57
|
+
"AWS": `arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${OAI}`
|
|
58
|
+
},
|
|
59
|
+
"Action": "s3:GetObject",
|
|
60
|
+
"Resource": `arn:aws:s3:::${bucket}/*`
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return res;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports.addFileToS3Bucket = async (bucket, account, filepath, file) => {
|
|
72
|
+
process.env.AWS_PROFILE = account;
|
|
73
|
+
|
|
74
|
+
let res = await Spinner.prototype.simple(`Adding file to s3 bucket`, () => {
|
|
75
|
+
return client.send(
|
|
76
|
+
new PutObjectCommand({
|
|
77
|
+
Bucket: bucket,
|
|
78
|
+
Body: file,
|
|
79
|
+
Key: filepath
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return res;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
module.exports.removeFileToS3Bucket = async (bucket, account, filepath) => {
|
|
88
|
+
process.env.AWS_PROFILE = account;
|
|
89
|
+
|
|
90
|
+
let res = await Spinner.prototype.simple(`Removing file from s3 bucket`, () => {
|
|
91
|
+
return client.send(
|
|
92
|
+
new DeleteObjectCommand({
|
|
93
|
+
Bucket: bucket,
|
|
94
|
+
Key: filepath
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return res;
|
|
100
|
+
}
|
|
@@ -194,6 +194,19 @@ module.exports.preset = async () => {
|
|
|
194
194
|
return inputs.preset;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
module.exports.gitlabSkip = async () => {
|
|
198
|
+
let inputs = await inquirer.prompt([
|
|
199
|
+
{
|
|
200
|
+
type: 'confirm',
|
|
201
|
+
name: 'confirm',
|
|
202
|
+
message: 'Do you want to test gitlab setup? (VPN required)',
|
|
203
|
+
default: true
|
|
204
|
+
}
|
|
205
|
+
]);
|
|
206
|
+
|
|
207
|
+
return !inputs.confirm;
|
|
208
|
+
}
|
|
209
|
+
|
|
197
210
|
module.exports.config = async () => {
|
|
198
211
|
let inputs = await inquirer.prompt([
|
|
199
212
|
{
|
|
@@ -132,13 +132,19 @@ RUN apt-get install -y locales
|
|
|
132
132
|
# Install AWS Elastic Beanstalk cli
|
|
133
133
|
RUN apt-get install -y zlib1g-dev libssl-dev libncurses-dev libffi-dev libsqlite3-dev libreadline-dev libbz2-dev
|
|
134
134
|
|
|
135
|
-
RUN git clone https://github.com/aws/aws-elastic-beanstalk-cli-setup.git
|
|
135
|
+
RUN git clone https://github.com/aws/aws-elastic-beanstalk-cli-setup.git --depth 1 --branch v0.1.2
|
|
136
136
|
RUN ./aws-elastic-beanstalk-cli-setup/scripts/bundled_installer
|
|
137
137
|
RUN rm -rf ./aws-elastic-beanstalk-cli-setup
|
|
138
138
|
|
|
139
139
|
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && \
|
|
140
140
|
locale-gen
|
|
141
141
|
|
|
142
|
+
# Install AWS-CLI@2
|
|
143
|
+
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
144
|
+
RUN unzip awscliv2.zip
|
|
145
|
+
RUN ./aws/install
|
|
146
|
+
RUN rm -rf ./aws && rm -rf awscliv2.zip
|
|
147
|
+
|
|
142
148
|
# Cleanup apt-get install folders
|
|
143
149
|
RUN apt-get clean && \
|
|
144
150
|
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
File without changes
|
package/core/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "core",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "lab-env docker config for the @fishawack/core npm module",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"postversion": "docker
|
|
6
|
+
"postversion": "mv ./*/ ./$npm_package_version && docker build ./*/ -t fishawack/core:$npm_package_version -t fishawack/core:latest && docker push fishawack/core:$npm_package_version && docker push fishawack/core:latest && git add . && git commit -m 'Bumped core to $npm_package_version'"
|
|
7
7
|
},
|
|
8
8
|
"author": "Mike Mellor",
|
|
9
9
|
"license": "ISC"
|
|
@@ -28,7 +28,7 @@ services:
|
|
|
28
28
|
build:
|
|
29
29
|
context: ./php/
|
|
30
30
|
dockerfile: Dockerfile
|
|
31
|
-
image: lab-env/craftcms/3/php:0.0.
|
|
31
|
+
image: lab-env/craftcms/3/php:0.0.2
|
|
32
32
|
init: true
|
|
33
33
|
working_dir: /app
|
|
34
34
|
networks:
|
|
@@ -36,6 +36,7 @@ services:
|
|
|
36
36
|
volumes:
|
|
37
37
|
- $CWD/:/app
|
|
38
38
|
- ./php/custom.conf:/opt/bitnami/php/etc/custom.conf
|
|
39
|
+
- ./php/policy.xml:/etc/ImageMagick-6/policy.xml
|
|
39
40
|
- vendor:/app/vendor
|
|
40
41
|
networks:
|
|
41
42
|
default:
|
|
@@ -6,11 +6,8 @@ MAINTAINER Mike Mellor
|
|
|
6
6
|
RUN apt-get update && \
|
|
7
7
|
apt-get install -y mariadb-client
|
|
8
8
|
|
|
9
|
-
# Install
|
|
10
|
-
RUN apt-get install -y
|
|
11
|
-
|
|
12
|
-
# PHP Imagick ext
|
|
13
|
-
RUN pecl install imagick && docker-php-ext-enable imagick
|
|
9
|
+
# Install ghostscript
|
|
10
|
+
RUN apt-get install -y ghostscript
|
|
14
11
|
|
|
15
12
|
# Cleanup apt-get install folders
|
|
16
13
|
RUN apt-get clean && \
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE policymap [
|
|
3
|
+
<!ELEMENT policymap (policy)*>
|
|
4
|
+
<!ATTLIST policymap xmlns CDATA #FIXED ''>
|
|
5
|
+
<!ELEMENT policy EMPTY>
|
|
6
|
+
<!ATTLIST policy xmlns CDATA #FIXED '' domain NMTOKEN #REQUIRED
|
|
7
|
+
name NMTOKEN #IMPLIED pattern CDATA #IMPLIED rights NMTOKEN #IMPLIED
|
|
8
|
+
stealth NMTOKEN #IMPLIED value CDATA #IMPLIED>
|
|
9
|
+
]>
|
|
10
|
+
<!--
|
|
11
|
+
Configure ImageMagick policies.
|
|
12
|
+
|
|
13
|
+
Domains include system, delegate, coder, filter, path, or resource.
|
|
14
|
+
|
|
15
|
+
Rights include none, read, write, execute and all. Use | to combine them,
|
|
16
|
+
for example: "read | write" to permit read from, or write to, a path.
|
|
17
|
+
|
|
18
|
+
Use a glob expression as a pattern.
|
|
19
|
+
|
|
20
|
+
Suppose we do not want users to process MPEG video images:
|
|
21
|
+
|
|
22
|
+
<policy domain="delegate" rights="none" pattern="mpeg:decode" />
|
|
23
|
+
|
|
24
|
+
Here we do not want users reading images from HTTP:
|
|
25
|
+
|
|
26
|
+
<policy domain="coder" rights="none" pattern="HTTP" />
|
|
27
|
+
|
|
28
|
+
The /repository file system is restricted to read only. We use a glob
|
|
29
|
+
expression to match all paths that start with /repository:
|
|
30
|
+
|
|
31
|
+
<policy domain="path" rights="read" pattern="/repository/*" />
|
|
32
|
+
|
|
33
|
+
Lets prevent users from executing any image filters:
|
|
34
|
+
|
|
35
|
+
<policy domain="filter" rights="none" pattern="*" />
|
|
36
|
+
|
|
37
|
+
Any large image is cached to disk rather than memory:
|
|
38
|
+
|
|
39
|
+
<policy domain="resource" name="area" value="1GP"/>
|
|
40
|
+
|
|
41
|
+
Use the default system font unless overwridden by the application:
|
|
42
|
+
|
|
43
|
+
<policy domain="system" name="font" value="/usr/share/fonts/favorite.ttf"/>
|
|
44
|
+
|
|
45
|
+
Define arguments for the memory, map, area, width, height and disk resources
|
|
46
|
+
with SI prefixes (.e.g 100MB). In addition, resource policies are maximums
|
|
47
|
+
for each instance of ImageMagick (e.g. policy memory limit 1GB, -limit 2GB
|
|
48
|
+
exceeds policy maximum so memory limit is 1GB).
|
|
49
|
+
|
|
50
|
+
Rules are processed in order. Here we want to restrict ImageMagick to only
|
|
51
|
+
read or write a small subset of proven web-safe image types:
|
|
52
|
+
|
|
53
|
+
<policy domain="delegate" rights="none" pattern="*" />
|
|
54
|
+
<policy domain="filter" rights="none" pattern="*" />
|
|
55
|
+
<policy domain="coder" rights="none" pattern="*" />
|
|
56
|
+
<policy domain="coder" rights="read|write" pattern="{GIF,JPEG,PNG,WEBP}" />
|
|
57
|
+
-->
|
|
58
|
+
<policymap>
|
|
59
|
+
<!-- <policy domain="resource" name="temporary-path" value="/tmp"/> -->
|
|
60
|
+
<policy domain="resource" name="memory" value="256MiB"/>
|
|
61
|
+
<policy domain="resource" name="map" value="512MiB"/>
|
|
62
|
+
<policy domain="resource" name="width" value="16KP"/>
|
|
63
|
+
<policy domain="resource" name="height" value="16KP"/>
|
|
64
|
+
<!-- <policy domain="resource" name="list-length" value="128"/> -->
|
|
65
|
+
<policy domain="resource" name="area" value="128MP"/>
|
|
66
|
+
<policy domain="resource" name="disk" value="1GiB"/>
|
|
67
|
+
<!-- <policy domain="resource" name="file" value="768"/> -->
|
|
68
|
+
<!-- <policy domain="resource" name="thread" value="4"/> -->
|
|
69
|
+
<!-- <policy domain="resource" name="throttle" value="0"/> -->
|
|
70
|
+
<!-- <policy domain="resource" name="time" value="3600"/> -->
|
|
71
|
+
<!-- <policy domain="coder" rights="none" pattern="MVG" /> -->
|
|
72
|
+
<!-- <policy domain="module" rights="none" pattern="{PS,PDF,XPS}" /> -->
|
|
73
|
+
<!-- <policy domain="path" rights="none" pattern="@*" /> -->
|
|
74
|
+
<!-- <policy domain="cache" name="memory-map" value="anonymous"/> -->
|
|
75
|
+
<!-- <policy domain="cache" name="synchronize" value="True"/> -->
|
|
76
|
+
<!-- <policy domain="cache" name="shared-secret" value="passphrase" stealth="true"/> -->
|
|
77
|
+
<!-- <policy domain="system" name="max-memory-request" value="256MiB"/> -->
|
|
78
|
+
<!-- <policy domain="system" name="shred" value="2"/> -->
|
|
79
|
+
<!-- <policy domain="system" name="precision" value="6"/> -->
|
|
80
|
+
<!-- <policy domain="system" name="font" value="/path/to/font.ttf"/> -->
|
|
81
|
+
<!-- <policy domain="system" name="pixel-cache-memory" value="anonymous"/> -->
|
|
82
|
+
<!-- <policy domain="system" name="shred" value="2"/> -->
|
|
83
|
+
<!-- <policy domain="system" name="precision" value="6"/> -->
|
|
84
|
+
<!-- not needed due to the need to use explicitly by mvg: -->
|
|
85
|
+
<!-- <policy domain="delegate" rights="none" pattern="MVG" /> -->
|
|
86
|
+
<!-- use curl -->
|
|
87
|
+
<policy domain="delegate" rights="none" pattern="URL" />
|
|
88
|
+
<policy domain="delegate" rights="none" pattern="HTTPS" />
|
|
89
|
+
<policy domain="delegate" rights="none" pattern="HTTP" />
|
|
90
|
+
<!-- in order to avoid to get image with password text -->
|
|
91
|
+
<policy domain="path" rights="none" pattern="@*"/>
|
|
92
|
+
<!-- disable ghostscript format types -->
|
|
93
|
+
<!-- <policy domain="coder" rights="none" pattern="PS" />
|
|
94
|
+
<policy domain="coder" rights="none" pattern="PS2" />
|
|
95
|
+
<policy domain="coder" rights="none" pattern="PS3" />
|
|
96
|
+
<policy domain="coder" rights="none" pattern="EPS" />
|
|
97
|
+
<policy domain="coder" rights="none" pattern="PDF" />
|
|
98
|
+
<policy domain="coder" rights="none" pattern="XPS" /> -->
|
|
99
|
+
</policymap>
|
package/drupal/9/php/Dockerfile
CHANGED
|
@@ -6,12 +6,6 @@ MAINTAINER Mike Mellor
|
|
|
6
6
|
RUN apt-get update && \
|
|
7
7
|
apt-get install -y mariadb-client
|
|
8
8
|
|
|
9
|
-
# Install Imagemagick
|
|
10
|
-
RUN apt-get install -y libmagickwand-dev --no-install-recommends
|
|
11
|
-
|
|
12
|
-
# PHP Imagick ext
|
|
13
|
-
RUN pecl install imagick && docker-php-ext-enable imagick
|
|
14
|
-
|
|
15
9
|
# Install ghostscript
|
|
16
10
|
RUN apt-get install -y ghostscript
|
|
17
11
|
|
package/globals.js
CHANGED
|
@@ -20,8 +20,15 @@ var run;
|
|
|
20
20
|
var exec;
|
|
21
21
|
var running;
|
|
22
22
|
var services;
|
|
23
|
+
var branch;
|
|
23
24
|
var opts = {encoding: 'utf8', stdio: 'inherit', shell: '/bin/bash'};
|
|
24
25
|
|
|
26
|
+
try{
|
|
27
|
+
branch = process.env.BRANCH || process.env.CI_COMMIT_REF_NAME || require('git-branch').sync();
|
|
28
|
+
} catch(e){
|
|
29
|
+
branch = 'unknown';
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
try{
|
|
26
33
|
repo = execSync('basename "$(git rev-parse --show-toplevel)"', {encoding: 'utf8', stdio: 'pipe'}).trim() || path.basename(process.cwd());
|
|
27
34
|
} catch(e){
|
|
@@ -113,6 +120,7 @@ module.exports = {
|
|
|
113
120
|
services,
|
|
114
121
|
platform,
|
|
115
122
|
repo,
|
|
123
|
+
branch,
|
|
116
124
|
repo_safe: repo.replace(/\./g, ''),
|
|
117
125
|
pkg,
|
|
118
126
|
docker,
|
package/laravel/8/php/Dockerfile
CHANGED
|
@@ -2,15 +2,9 @@ FROM chialab/php:7.4-fpm
|
|
|
2
2
|
|
|
3
3
|
MAINTAINER Mike Mellor
|
|
4
4
|
|
|
5
|
-
# Install Imagemagick
|
|
6
|
-
RUN apt-get update && \
|
|
7
|
-
apt-get install -y libmagickwand-dev --no-install-recommends
|
|
8
|
-
|
|
9
|
-
# PHP Imagick ext
|
|
10
|
-
RUN pecl install imagick && docker-php-ext-enable imagick
|
|
11
|
-
|
|
12
5
|
# Install ghostscript
|
|
13
|
-
RUN apt-get
|
|
6
|
+
RUN apt-get update && \
|
|
7
|
+
apt-get install -y ghostscript
|
|
14
8
|
|
|
15
9
|
# Cleanup apt-get install folders
|
|
16
10
|
RUN apt-get clean && \
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fishawack/lab-env",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Docker manager for FW",
|
|
5
5
|
"main": "cli.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "rm -rf _Test/_fixtures
|
|
7
|
+
"test": "rm -rf _Test/_fixtures/boilerplate*; mocha _Test/*.js --timeout 1200s --bail",
|
|
8
8
|
"preversion": "npm test",
|
|
9
9
|
"postversion": "git push && git push --tags && npm publish",
|
|
10
10
|
"postpublish": "git checkout development && git merge master && git push"
|
|
@@ -16,23 +16,33 @@
|
|
|
16
16
|
"author": "",
|
|
17
17
|
"license": "ISC",
|
|
18
18
|
"homepage": "https://bitbucket.org/fishawackdigital/lab-env#readme",
|
|
19
|
+
"type": "commonjs",
|
|
19
20
|
"bin": {
|
|
20
21
|
"lab-env": "./cli.js",
|
|
21
22
|
"fw": "./cli.js"
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
24
|
-
"
|
|
25
|
+
"@aws-sdk/client-cloudfront": "^3.141.0",
|
|
26
|
+
"@aws-sdk/client-s3": "^3.141.0",
|
|
27
|
+
"axios": "^0.21.4",
|
|
25
28
|
"chalk": "4.1.0",
|
|
29
|
+
"generate-password": "^1.7.0",
|
|
26
30
|
"get-port": "5.1.1",
|
|
31
|
+
"git-branch": "^2.0.1",
|
|
27
32
|
"glob": "7.1.7",
|
|
28
33
|
"inquirer": "8.1.2",
|
|
29
34
|
"ora": "5.4.1",
|
|
30
35
|
"semver": "7.3.4",
|
|
31
|
-
"update-notifier": "
|
|
36
|
+
"update-notifier": "^6.0.2",
|
|
32
37
|
"yargs": "16.2.0"
|
|
33
38
|
},
|
|
34
39
|
"devDependencies": {
|
|
35
40
|
"chai": "4.3.4",
|
|
36
|
-
"mocha": "9.
|
|
41
|
+
"mocha": "^9.2.2",
|
|
42
|
+
"node-fetch": "^3.2.10"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"npm": ">=8",
|
|
46
|
+
"node": ">=18"
|
|
37
47
|
}
|
|
38
48
|
}
|