@fishawack/lab-env 2.0.2 → 3.0.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 CHANGED
@@ -1,5 +1,24 @@
1
1
  ## Changelog
2
2
 
3
+ ### 3.0.0 (2022-08-17)
4
+ * [Feature] Added newly setup AWS accounts to the client prompts on `fw provision`
5
+ * [Feature] Added key/dekey commands
6
+ * [Change] Provisioned environment variables now append client to uniqueify the services created
7
+ * [Change] `fw provision` no longer does two polls to cloudfront to speed up the process
8
+ * [Change] Drupal now defaults to 8.1 php
9
+ * [Bug] Added protocol to www in cloudfront function to ensure redirect works correctly
10
+ * [Bug] Added posiexem to fix M1 chip issue on drupal
11
+
12
+ ### 2.2.0 (2022-08-10)
13
+ * [Feature] Can now skip diagnose in `fw diagnose`
14
+ * [Feature] Can now provision AWS environments using `fw provision`
15
+ * [Change] Now requires node > 16
16
+ * [Change] Auditted npm dependencies
17
+ * [Misc] Added test coverage for new AWS service
18
+
19
+ ### 2.1.0 (2022-05-27)
20
+ * [Change] Bumped core `0.0.21` to `0.1.0`
21
+
3
22
  ### 2.0.2 (2022-04-26)
4
23
  * [Change] Bumped core `0.0.20` to `0.0.21`
5
24
  * [Bug] Lock eb-cli due to breaking change
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
+ };
package/_Test/key.js ADDED
@@ -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
+
6
+ describe('key', async () => {
7
+ let account = 'fishawack';
8
+
9
+ before(async () => {
10
+ let res = await aws.iam.createFWIAMUser('fw-test-user', account);
11
+
12
+ // Wait for key as AWS doesn't provide a way to wait for it to becom eactive
13
+ await new Promise(resolve => setTimeout(() => resolve(), 10000));
14
+
15
+ process.env.AWS_ACCESS_KEY_ID = res.AccessKey.AccessKeyId;
16
+ process.env.AWS_SECRET_ACCESS_KEY = res.AccessKey.SecretAccessKey;
17
+ });
18
+
19
+ it('Should be able to access AWS service', async () => {
20
+ let res;
21
+
22
+ try{ res = await aws.s3.listS3Buckets(); } catch(e){ console.log(e.message); }
23
+
24
+ expect(res).to.not.be.undefined;
25
+ });
26
+
27
+ after(async () => {
28
+ delete process.env.AWS_ACCESS_KEY_ID;
29
+ delete process.env.AWS_SECRET_ACCESS_KEY;
30
+
31
+ await aws.iam.removeIAMUser('fw-test-user', account);
32
+ });
33
+ });
@@ -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')) return;
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', 'key', 'dekey'].forEach(d => cli.command(...require(`./commands/create/cmds/${d}.js`)));
63
63
 
64
64
  cli.demandCommand(1, '')
65
65
  .wrap(null)
@@ -0,0 +1,75 @@
1
+ const _ = require('../../../globals.js');
2
+ const utilities = require('../libs/utilities');
3
+ const inquirer = require('inquirer');
4
+ const aws = require('../services/aws/index.js');
5
+ const fs = require('fs');
6
+ const os = require('os');
7
+
8
+ module.exports = [
9
+ 'dekey',
10
+ false,
11
+ yargs => {},
12
+ async argv => {
13
+ let config = JSON.parse(fs.readFileSync(`${os.homedir()}/.lab-env`, {encoding: 'utf8'}));
14
+ let users = [];
15
+ let clients = [];
16
+
17
+ let answer = await inquirer.prompt([
18
+ {
19
+ type: 'confirm',
20
+ name: 'check',
21
+ message: `Remove keys for all users`,
22
+ default: 'Y'
23
+ }
24
+ ]);
25
+
26
+ if(answer.check){
27
+ users = config.users.map(d => d.username);
28
+ } else {
29
+ answer = await inquirer.prompt([
30
+ {
31
+ type: 'checkbox',
32
+ name: 'users',
33
+ message: 'Select users',
34
+ choices: config.users.map(d => d.username)
35
+ }
36
+ ]);
37
+
38
+ users = answer.users;
39
+ }
40
+
41
+ answer = await inquirer.prompt([
42
+ {
43
+ type: 'confirm',
44
+ name: 'check',
45
+ message: `Remove keys for all clients`,
46
+ default: 'Y'
47
+ }
48
+ ]);
49
+
50
+ if(answer.check){
51
+ clients = aws.clients;
52
+ } else {
53
+ answer = await inquirer.prompt([
54
+ {
55
+ type: 'checkbox',
56
+ name: 'clients',
57
+ message: 'Select clients',
58
+ choices: aws.clients
59
+ }
60
+ ]);
61
+
62
+ clients = answer.clients;
63
+ }
64
+
65
+ for(let i = 0; i < clients.length; i++){
66
+ let client = clients[i];
67
+
68
+ for(let j = 0; j < users.length; j++){
69
+ let user = users[j];
70
+
71
+ await aws.iam.removeIAMUser(`fw-automation-${user}`, client);
72
+ }
73
+ }
74
+ }
75
+ ];
@@ -0,0 +1,54 @@
1
+ const _ = require('../../../globals.js');
2
+ const inquirer = require('inquirer');
3
+ const aws = require('../services/aws/index.js');
4
+ const utilities = require('../libs/utilities');
5
+
6
+ module.exports = [
7
+ ['deprovision', 'deprov'],
8
+ false,
9
+ yargs => {
10
+ yargs.option('branch', {
11
+ alias: 'b',
12
+ describe: 'Branch to configure',
13
+ type: 'string'
14
+ });
15
+ },
16
+ async argv => {
17
+ let branch = argv.branch || _.branch;
18
+
19
+ const answers = await inquirer.prompt([
20
+ {
21
+ type: 'input',
22
+ name: 'id',
23
+ message: 'What is the Id of the CloudFront distribution?',
24
+ validate: (input) => !!input.length
25
+ },
26
+ {
27
+ type: 'list',
28
+ name: 'client',
29
+ message: 'Which AWS account is this deployed too?',
30
+ choices: aws.clients,
31
+ default: 'fishawack'
32
+ }
33
+ ]);
34
+
35
+ let answer = await inquirer.prompt([
36
+ {
37
+ type: 'confirm',
38
+ name: 'check',
39
+ message: `Deprovisioning ${utilities.colorize(aws.slug(_.repo, answers.client, branch), 'error')} from ${utilities.colorize(answers.client, 'error')} AWS account, are you sure you want to continue?`,
40
+ default: false
41
+ }
42
+ ]);
43
+
44
+ if(!answer.check){
45
+ process.exit(1);
46
+ }
47
+
48
+ try { await aws.s3.removeS3Bucket(aws.slug(_.repo, answers.client, branch), answers.client); } catch(e) {}
49
+
50
+ try { await aws.cloudfront.removeCloudFrontDistribution(answers.id, answers.client); } catch(e) {}
51
+
52
+ try { await aws.cloudfront.removeCloudFrontFunction(aws.slug(_.repo, answers.client, branch), answers.client); } catch(e) {}
53
+ }
54
+ ];
@@ -57,9 +57,11 @@ module.exports = [
57
57
  while(!await test.bitbucket() || !await bitbucket.check()){
58
58
  await guide.bitbucket();
59
59
  }
60
-
61
- while(!await test.gitlab() || !await gitlab.check()){
62
- await guide.gitlab();
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,99 @@
1
+ const _ = require('../../../globals.js');
2
+ const utilities = require('../libs/utilities');
3
+ const inquirer = require('inquirer');
4
+ const aws = require('../services/aws/index.js');
5
+ const fs = require('fs');
6
+ const os = require('os');
7
+
8
+ module.exports = [
9
+ 'key',
10
+ false,
11
+ yargs => {},
12
+ async argv => {
13
+ let config = JSON.parse(fs.readFileSync(`${os.homedir()}/.lab-env`, {encoding: 'utf8'}));
14
+ let users = [];
15
+ let clients = [];
16
+
17
+ let answer = await inquirer.prompt([
18
+ {
19
+ type: 'confirm',
20
+ name: 'check',
21
+ message: `Set keys for all users`,
22
+ default: 'Y'
23
+ }
24
+ ]);
25
+
26
+ if(answer.check){
27
+ users = config.users.map(d => d.username);
28
+ } else {
29
+ answer = await inquirer.prompt([
30
+ {
31
+ type: 'checkbox',
32
+ name: 'users',
33
+ message: 'Select users',
34
+ choices: config.users.map(d => d.username)
35
+ }
36
+ ]);
37
+
38
+ users = answer.users;
39
+ }
40
+
41
+ answer = await inquirer.prompt([
42
+ {
43
+ type: 'confirm',
44
+ name: 'check',
45
+ message: `Set keys for all clients`,
46
+ default: 'Y'
47
+ }
48
+ ]);
49
+
50
+ if(answer.check){
51
+ clients = aws.clients;
52
+ } else {
53
+ answer = await inquirer.prompt([
54
+ {
55
+ type: 'checkbox',
56
+ name: 'clients',
57
+ message: 'Select clients',
58
+ choices: aws.clients
59
+ }
60
+ ]);
61
+
62
+ clients = answer.clients;
63
+ }
64
+
65
+ let credentials = {};
66
+
67
+ for(let i = 0; i < clients.length; i++){
68
+ let client = clients[i];
69
+
70
+ for(let j = 0; j < users.length; j++){
71
+ let user = users[j];
72
+
73
+ if(!credentials[user]){
74
+ credentials[user] = {};
75
+ }
76
+
77
+ let res = await aws.iam.createFWIAMUser(`fw-automation-${user}`, client);
78
+
79
+ credentials[user][client] = {
80
+ key: res.AccessKey && res.AccessKey.AccessKeyId || res.AccessKeyMetadata[0].AccessKeyId,
81
+ secret: res.AccessKey && res.AccessKey.SecretAccessKey || '** secret **'
82
+ }
83
+ }
84
+ }
85
+
86
+ let output = '';
87
+
88
+ for(var user in credentials){
89
+ output += utilities.colorize(`\n${user}\n`, 'title');
90
+ for(var client in credentials[user]){
91
+ output += `\n[${client}]\n`;
92
+ output += `aws_access_key_id = ${credentials[user][client].key}\n`;
93
+ output += `aws_secret_access_key = ${credentials[user][client].secret}\n`;
94
+ }
95
+ }
96
+
97
+ console.log(output);
98
+ }
99
+ ];
@@ -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: aws.clients,
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(aws.slug(_.repo, answers.client, 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": `https://${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": `https://${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
+ }