@fishawack/lab-env 3.2.0 → 4.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,11 @@
1
1
  ## Changelog
2
2
 
3
+ ### 4.0.0 (2022-09-12)
4
+ * [Feature] Added new s3Safe repo to enforce safe s3 service names
5
+ * [Feature] Added newly setup AWS accounts to the client prompts on `fw provision`
6
+ * [Feature] key command can now send credentials via email
7
+ * [Bug] deprov command now uses repo safe
8
+
3
9
  ### 3.2.0 (2022-09-12)
4
10
  * [Feature] Added newly setup AWS accounts to the client prompts on `fw provision`
5
11
  * [Change] Clients now listed in alphabetical order
package/_Test/email.js ADDED
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ const expect = require('chai').expect;
4
+ const email = require("../commands/create/services/email.js");
5
+
6
+ describe('email', async () => {
7
+ it('Should send email', async () => {
8
+ let info = await email.send('mike.mellor@fishawack.com', 'New AWS Keys', 'hello', null, true);
9
+
10
+ expect(info.response).to.contain('250');
11
+ });
12
+ });
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ const expect = require('chai').expect;
4
+ const { s3Safe } = require("../commands/create/libs/utilities.js");
5
+
6
+ describe('utilities', async () => {
7
+ describe('s3Safe', () => {
8
+ it('Should enforce service name to be at least 3 chars', async () => {
9
+ expect(s3Safe('a').length).to.be.greaterThanOrEqual(3);
10
+ });
11
+
12
+ it('Should limit service name to 63 chars', async () => {
13
+ expect(s3Safe('a'.repeat(100)).length).to.be.lessThanOrEqual(63);
14
+ });
15
+
16
+ it('Should remove any uppercase chars', async () => {
17
+ expect(/^[A-Z]*$/.test(s3Safe('TEST'))).to.be.false;
18
+ });
19
+
20
+ it('Should convert . to -', async () => {
21
+ expect(s3Safe('...').match(/\./g)).to.be.null;
22
+ });
23
+
24
+ it('Should convert _ to -', async () => {
25
+ expect(s3Safe('___').match(/\_/g)).to.be.null;
26
+ });
27
+
28
+ it('Should start with a number or char', async () => {
29
+ expect(/[a-z0-9]/i.test(s3Safe('___').charAt(0))).to.be.true;
30
+ });
31
+
32
+ it('Should end with a number or char', async () => {
33
+ expect(/[a-z0-9]/i.test(s3Safe('___').charAt(s3Safe('___').length - 1))).to.be.true;
34
+ });
35
+
36
+ it('Should remove any special chars', async () => {
37
+ expect(s3Safe('!@#$%^&*()=_+;,./<>?~`|').match(/[\!\@\#\$\%\^\&\*\(\)\=\_\+\;\,\.\/\<\>\?\~\`\|]/g)).to.be.null;
38
+ });
39
+ })
40
+ });
package/cli.js CHANGED
@@ -11,9 +11,6 @@ const { hideBin } = require('yargs/helpers');
11
11
 
12
12
  const args = hideBin(process.argv);
13
13
 
14
- // Stop here if docker process not running and command isn't version or origin which don't require docker
15
- if(!_.services && !(args[0] === 'origin' || args[0] === '--version')) process.exit();
16
-
17
14
  (async () => {
18
15
  const updateNotifier = (await import('update-notifier')).default;
19
16
  const pkg = require('./package.json');
@@ -36,7 +36,7 @@ module.exports = [
36
36
  {
37
37
  type: 'confirm',
38
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?`,
39
+ message: `Deprovisioning ${utilities.colorize(aws.slug(_.repo_safe, answers.client, branch), 'error')} from ${utilities.colorize(answers.client, 'error')} AWS account, are you sure you want to continue?`,
40
40
  default: false
41
41
  }
42
42
  ]);
@@ -45,10 +45,10 @@ module.exports = [
45
45
  process.exit(1);
46
46
  }
47
47
 
48
- try { await aws.s3.removeS3Bucket(aws.slug(_.repo, answers.client, branch), answers.client); } catch(e) {}
48
+ try { await aws.s3.removeS3Bucket(aws.slug(_.repo_safe, answers.client, branch), answers.client); } catch(e) {}
49
49
 
50
50
  try { await aws.cloudfront.removeCloudFrontDistribution(answers.id, answers.client); } catch(e) {}
51
51
 
52
- try { await aws.cloudfront.removeCloudFrontFunction(aws.slug(_.repo, answers.client, branch), answers.client); } catch(e) {}
52
+ try { await aws.cloudfront.removeCloudFrontFunction(aws.slug(_.repo_safe, answers.client, branch), answers.client); } catch(e) {}
53
53
  }
54
54
  ];
@@ -2,6 +2,7 @@ const _ = require('../../../globals.js');
2
2
  const utilities = require('../libs/utilities');
3
3
  const inquirer = require('inquirer');
4
4
  const aws = require('../services/aws/index.js');
5
+ const email = require('../services/email.js');
5
6
 
6
7
  module.exports = [
7
8
  'key',
@@ -10,6 +11,7 @@ module.exports = [
10
11
  async argv => {
11
12
  let users = [];
12
13
  let clients = [];
14
+ let sendEmail = false;
13
15
 
14
16
  let answer = await inquirer.prompt([
15
17
  {
@@ -59,6 +61,17 @@ module.exports = [
59
61
  clients = answer.clients;
60
62
  }
61
63
 
64
+ answer = await inquirer.prompt([
65
+ {
66
+ type: 'confirm',
67
+ name: 'check',
68
+ message: `Would you like to email out the credentials after creation (requires office365 credentials in ~/targets/misc.json)?`,
69
+ default: 'Y'
70
+ }
71
+ ]);
72
+
73
+ sendEmail = answer.check
74
+
62
75
  let credentials = {};
63
76
 
64
77
  for(let i = 0; i < clients.length; i++){
@@ -84,10 +97,17 @@ module.exports = [
84
97
 
85
98
  for(var user in credentials){
86
99
  output += utilities.colorize(`\n${user}\n`, 'title');
100
+
101
+ let outputUser = '';
87
102
  for(var client in credentials[user]){
88
- output += `\n[${client}]\n`;
89
- output += `aws_access_key_id = ${credentials[user][client].key}\n`;
90
- output += `aws_secret_access_key = ${credentials[user][client].secret}\n`;
103
+ outputUser += `\n[${client}]\n`;
104
+ outputUser += `aws_access_key_id = ${credentials[user][client].key}\n`;
105
+ outputUser += `aws_secret_access_key = ${credentials[user][client].secret}\n`;
106
+ }
107
+ output += outputUser;
108
+
109
+ if(sendEmail){
110
+ await email.send(_.config.users.find(d => d.username === user).email, 'New AWS Keys', null, outputUser);
91
111
  }
92
112
  }
93
113
 
@@ -94,7 +94,7 @@ module.exports = [
94
94
  let infastructure;
95
95
 
96
96
  try{
97
- infastructure = await aws.static(aws.slug(_.repo, answers.client, branch), answers.client, [{Key: 'repository', Value: _.repo}, {Key: 'environment', Value: branch}], credentials);
97
+ infastructure = await aws.static(aws.slug(_.repo_safe, answers.client, branch), answers.client, [{Key: 'repository', Value: _.repo}, {Key: 'environment', Value: branch}], credentials);
98
98
  } catch(e){
99
99
  console.log(e.message);
100
100
  process.exit(1);
@@ -1,5 +1,6 @@
1
1
  const chalk = require('chalk');
2
2
  const ora = require('ora');
3
+ const crypto = require('crypto');
3
4
 
4
5
  // Text
5
6
  module.exports.capitalize = str => str.charAt(0).toUpperCase() + str.slice(1);
@@ -96,4 +97,21 @@ module.exports.encode = (username, password) => {
96
97
  "Content-Type": "application/json",
97
98
  "Authorization": `Basic ${base64data}`
98
99
  };
100
+ }
101
+
102
+ module.exports.s3Safe = (name) => {
103
+ let safe = name;
104
+ let hash = crypto.createHash('md5').update(name).digest('hex');
105
+ let suffix = `-${hash.substring(0, 8)}`;
106
+ let prefix = 'fw-auto-';
107
+
108
+ safe = safe.substring(0, (63 - suffix.length - prefix.length));
109
+ safe = safe.replace(/[^a-zA-Z0-9-_. ]/g, ""); // Remove special chars except . _ - as these are all transformed into -
110
+ safe = safe.toLowerCase();
111
+ safe = `${prefix}${safe}`;
112
+ safe = `${safe}${suffix}`;
113
+ safe = safe.replace(/\./g, '-');
114
+ safe = safe.replace(/\_/g, '-');
115
+
116
+ return safe;
99
117
  }
@@ -1,10 +1,12 @@
1
+ const { s3Safe } = require("../../libs/utilities.js");
2
+
1
3
  module.exports.s3 = require("./s3.js");
2
4
  module.exports.cloudfront = require("./cloudfront.js");
3
5
  module.exports.iam = require("./iam.js");
4
6
 
5
- module.exports.slug = (repo, client, branch) => `fw-auto-${client}-${repo}-${branch}`;
7
+ module.exports.slug = (repo, client, branch) => s3Safe(`${branch}-${repo}-${client}`);
6
8
 
7
- module.exports.clients = ['fishawack', 'abbvie', 'sanofigenzyme', 'gsk', 'janssen', 'astrazeneca', 'ptc', 'jazz', 'pfizer', 'heron', 'novartis', 'training', 'merck', 'acadia', 'travere'];
9
+ module.exports.clients = ['fishawack', 'abbvie', 'sanofigenzyme', 'gsk', 'janssen', 'astrazeneca', 'ptc', 'jazz', 'pfizer', 'heron', 'novartis', 'training', 'merck', 'acadia', 'travere', 'roche', 'utc', 'bayer', 'alcon'];
8
10
 
9
11
  module.exports.static = async (name, account, tags = [], credentials = []) => {
10
12
  let s3 = await module.exports.s3.createS3Bucket(name, account, tags);
@@ -0,0 +1,44 @@
1
+ const nodemailer = require("nodemailer");
2
+ const { misc } = require('../libs/vars');
3
+
4
+ module.exports.send = async (to, subject, html, text, test = false, from = `digitalautomation@fishawack.com`) => {
5
+ let transporter = await module.exports.transport(test);
6
+
7
+ let info = await transporter.sendMail({
8
+ from,
9
+ to,
10
+ subject,
11
+ html,
12
+ text
13
+ });
14
+
15
+ if(test){
16
+ console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info));
17
+ }
18
+
19
+ return info;
20
+ };
21
+
22
+ module.exports.transport = async (test = false) => {
23
+ let username = misc?.nodemailer?.office365.username;
24
+ let password = misc?.nodemailer?.office365.password;
25
+
26
+ if(test){
27
+ let testAccount = await nodemailer.createTestAccount();
28
+
29
+ username = testAccount.user;
30
+ password = testAccount.pass;
31
+ }
32
+
33
+ let transporter = nodemailer.createTransport({
34
+ host: test ? 'smtp.ethereal.email' : 'smtp.office365.com',
35
+ port: 587,
36
+ secure: false,
37
+ auth: {
38
+ user: username,
39
+ pass: password,
40
+ },
41
+ });
42
+
43
+ return transporter;
44
+ };
package/globals.js CHANGED
@@ -34,15 +34,6 @@ try{
34
34
  config = {};
35
35
  }
36
36
 
37
- if(args[0] !== 'diag' && args[0] !== 'diagnose'){
38
- if(!config.diagnosis || semver.diff(config.diagnosis, diagnosis) === 'major'){
39
- console.log(`${utilities.colorize(`@fishawack/lab-env`, 'title')} diagnosis is ${utilities.colorize(`outdated`, 'error')}.\n\nRun ${utilities.colorize(`fw diagnose`, 'success')} to reconfigure.`);
40
- process.exit(1);
41
- } else if(semver.diff(config.diagnosis, diagnosis) !== null){
42
- console.log(`${utilities.colorize(`@fishawack/lab-env`, 'title')} diagnosis is ${utilities.colorize(`outdated`, 'warning')}.\n\nRun ${utilities.colorize(`fw diagnose`, 'success')} to reconfigure.`);
43
- }
44
- }
45
-
46
37
  try{
47
38
  branch = process.env.BRANCH || process.env.CI_COMMIT_REF_NAME || require('git-branch').sync();
48
39
  } catch(e){
@@ -132,8 +123,22 @@ try{
132
123
  exec = `exec ${process.env.CI_BUILD_ID ? '-T' : ''}`;
133
124
  method = running ? exec : 'run --rm --service-ports';
134
125
  run = `${method} core bash -l`;
135
- } catch(e){
136
- console.log("Docker does not appear to be running...");
126
+ } catch(e){}
127
+
128
+ // Always allow diagnose, diag, version, help and origin through
129
+ if(args[0] !== 'diag' && args[0] !== 'diagnose' && args[0] !== 'origin' && args[0] !== '--version' && args[0] !== '--help'){
130
+ // Stop here if diagnosis either unset or outdated
131
+ if(!config.diagnosis || semver.diff(config.diagnosis, diagnosis) === 'major'){
132
+ console.log(`${utilities.colorize(`@fishawack/lab-env`, 'title')} diagnosis is ${utilities.colorize(`outdated`, 'error')}.\n\nRun ${utilities.colorize(`fw diagnose`, 'success')} to reconfigure.`);
133
+ process.exit(1);
134
+ // Warn if diagnosis only minor/patch outdated
135
+ } else if(semver.diff(config.diagnosis, diagnosis) !== null){
136
+ console.log(`${utilities.colorize(`@fishawack/lab-env`, 'title')} diagnosis is ${utilities.colorize(`outdated`, 'warning')}.\n\nRun ${utilities.colorize(`fw diagnose`, 'success')} to reconfigure.`);
137
+ // Stop here if docker process not running
138
+ } else if(!services) {
139
+ console.log(`${utilities.colorize(`Docker`, 'info')} does not appear to be running...`);
140
+ process.exit();
141
+ }
137
142
  }
138
143
 
139
144
  module.exports = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fishawack/lab-env",
3
- "version": "3.2.0",
3
+ "version": "4.0.0",
4
4
  "description": "Docker manager for FW",
5
5
  "main": "cli.js",
6
6
  "scripts": {
@@ -32,6 +32,7 @@
32
32
  "git-branch": "^2.0.1",
33
33
  "glob": "7.1.7",
34
34
  "inquirer": "8.1.2",
35
+ "nodemailer": "^6.7.8",
35
36
  "ora": "5.4.1",
36
37
  "semver": "7.3.4",
37
38
  "update-notifier": "^6.0.2",