@fishawack/lab-env 2.1.0 → 3.1.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/_Test/_fixtures/.gitkeep +0 -0
  3. package/_Test/_helpers/globals.js +10 -0
  4. package/_Test/boilerplate-craftcms.js +4 -4
  5. package/_Test/boilerplate-drupal.js +4 -4
  6. package/_Test/boilerplate-laravel.js +4 -4
  7. package/_Test/boilerplate-wordpress.js +4 -4
  8. package/_Test/boilerplate.js +4 -4
  9. package/_Test/check.js +29 -0
  10. package/_Test/key.js +33 -0
  11. package/_Test/provision.js +33 -0
  12. package/cli.js +6 -6
  13. package/commands/check.js +1 -1
  14. package/commands/create/cmds/dekey.js +72 -0
  15. package/commands/create/cmds/deprovision.js +54 -0
  16. package/commands/create/cmds/diagnose.js +15 -3
  17. package/commands/create/cmds/key.js +96 -0
  18. package/commands/create/cmds/provision.js +120 -0
  19. package/commands/create/libs/aws-cloudfront-auth.js +77 -0
  20. package/commands/create/libs/aws-cloudfront-simple.js +51 -0
  21. package/commands/create/libs/utilities.js +51 -2
  22. package/commands/create/services/aws/cloudfront.js +295 -0
  23. package/commands/create/services/aws/iam.js +170 -0
  24. package/commands/create/services/aws/index.js +25 -0
  25. package/commands/create/services/aws/misc.js +9 -0
  26. package/commands/create/services/aws/s3.js +109 -0
  27. package/commands/create/services/guide.js +13 -0
  28. package/commands/docker/rebuild.js +1 -1
  29. package/commands/execute.js +10 -1
  30. package/core/{0.1.0 → 0.2.0}/Dockerfile +8 -4
  31. package/core/{0.1.0 → 0.2.0}/entrypoint.sh +0 -0
  32. package/core/CHANGELOG.md +4 -0
  33. package/core/package.json +3 -2
  34. package/drupal/9/apache/apache.conf +1 -0
  35. package/drupal/9/docker-compose.yml +1 -1
  36. package/drupal/9/php/Dockerfile +1 -1
  37. package/globals.js +31 -1
  38. package/package.json +16 -5
@@ -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
+ }
@@ -8,6 +8,8 @@ const colorize = module.exports.colorize = (str, type) => {
8
8
  switch (type) {
9
9
  case 'success':
10
10
  return chalk.green(str);
11
+ case 'warning':
12
+ return chalk.yellow(str);
11
13
  case 'error':
12
14
  return chalk.red(str);
13
15
  case 'title':
@@ -23,9 +25,12 @@ const colorize = module.exports.colorize = (str, type) => {
23
25
  }
24
26
  };
25
27
 
28
+ let latestSpinner = null;
29
+
26
30
  module.exports.Spinner = class Spinner {
27
- constructor(startMessage) {
28
- this.ora = ora(colorize(startMessage, 'info')).start();
31
+ constructor(startMessage, simple = false) {
32
+ this.ora = ora(simple ? startMessage : colorize(startMessage, 'info')).start();
33
+ latestSpinner = this;
29
34
  }
30
35
 
31
36
  update(message, status) {
@@ -37,6 +42,50 @@ module.exports.Spinner = class Spinner {
37
42
  this.ora.succeed(colorize(message, status || 'success'));
38
43
  }
39
44
  }
45
+
46
+ action(message, action, success, failure) {
47
+ return new Promise(async (resolve, reject) => {
48
+ let instance = new this.constructor(message);
49
+ let res;
50
+
51
+ try {
52
+ res = await action();
53
+
54
+ instance.update(success);
55
+ } catch (e){
56
+ instance.update(failure, 'fail');
57
+ reject(e);
58
+ }
59
+
60
+ resolve(res);
61
+ });
62
+ }
63
+
64
+ simple(message, action) {
65
+ return new Promise(async (resolve, reject) => {
66
+ let instance = new this.constructor(message, true);
67
+ let res;
68
+
69
+ try {
70
+ res = await action();
71
+
72
+ instance.ora.succeed();
73
+ } catch (e){
74
+ instance.ora.fail();
75
+ reject(e);
76
+ }
77
+
78
+ resolve(res);
79
+ });
80
+ }
81
+
82
+ ping(){
83
+ latestSpinner.ora.color = latestSpinner.ora.color === 'cyan' ? 'magenta' : 'cyan';
84
+ let message = ' - check: ';
85
+ let split = latestSpinner.ora.text.split(message);
86
+ let iteration = split[1] && +split[1].split(':')[0] || 0;
87
+ latestSpinner.ora.text = `${split[0]}${message}${iteration+1}`;
88
+ }
40
89
  };
41
90
 
42
91
  module.exports.encode = (username, password) => {
@@ -0,0 +1,295 @@
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
+ const { createClient } = require('./misc.js');
5
+
6
+ module.exports.createCloudFrontDistribution = async (name, account, tags = [], FunctionARN = null, region = 'us-east-1') => {
7
+ const client = createClient(CloudFrontClient, account, region);
8
+
9
+ let OAI = await Spinner.prototype.simple(`Creating CloudFront OAI`, () => {
10
+ return client.send(
11
+ new CreateCloudFrontOriginAccessIdentityCommand({
12
+ CloudFrontOriginAccessIdentityConfig: {
13
+ CallerReference: name,
14
+ Comment: `lab-env provisioned CloudFront OAI for s3 bucket ${name}`,
15
+ }
16
+ })
17
+ )
18
+ });
19
+
20
+ let res;
21
+
22
+ try {
23
+ res = await Spinner.prototype.simple(`Creating CloudFront distribution with CloudFront OAI and tags`, () => {
24
+ return client.send(
25
+ new CreateDistributionWithTagsCommand({
26
+ DistributionConfigWithTags: {
27
+ DistributionConfig: {
28
+ Enabled: true,
29
+ CallerReference: name,
30
+ Comment: `lab-env provisioned CloudFront distribution for project ${name}`,
31
+ CustomErrorResponses: {
32
+ Items: [
33
+ {
34
+ ErrorCachingMinTTL: 0,
35
+ ErrorCode: 403,
36
+ ResponseCode: 200,
37
+ ResponsePagePath: '/index.html'
38
+ }
39
+ ],
40
+ Quantity: 1
41
+ },
42
+ DefaultCacheBehavior: {
43
+ Compress: true,
44
+ TargetOriginId: `${name}.s3.${region}.amazonaws.com`,
45
+ ViewerProtocolPolicy: 'redirect-to-https',
46
+ CachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6', // Built in, Managed AWS Policy - Cache Optimized
47
+ FunctionAssociations: FunctionARN && {
48
+ Items: [
49
+ {
50
+ EventType: 'viewer-request',
51
+ FunctionARN
52
+ }
53
+ ],
54
+ Quantity: 1
55
+ }
56
+ },
57
+ Origins: {
58
+ Items: [
59
+ {
60
+ DomainName: `${name}.s3.${region}.amazonaws.com`,
61
+ Id: `${name}.s3.${region}.amazonaws.com`,
62
+ S3OriginConfig: {
63
+ OriginAccessIdentity: `origin-access-identity/cloudfront/${OAI.CloudFrontOriginAccessIdentity.Id}`
64
+ }
65
+ }
66
+ ],
67
+ Quantity: 1
68
+ }
69
+ },
70
+ Tags: {
71
+ Items: [{Key: 'client', Value: account}].concat(tags)
72
+ }
73
+ }
74
+ })
75
+ )
76
+ });
77
+
78
+ await Spinner.prototype.simple(`Waiting for CloudFront distribution to deploy`, () => {
79
+ return module.exports.waitForCloudFrontDistribution(res.Distribution.Id, account);
80
+ });
81
+ } catch(e){
82
+ let Id = e.message.split(' ')[e.message.split(' ').length - 1];
83
+
84
+ res = await Spinner.prototype.simple(`Retrieving the already existing CloudFront distribution`, () => {
85
+ return client.send(
86
+ new GetDistributionCommand({ Id })
87
+ );
88
+ });
89
+ }
90
+
91
+ return res;
92
+ }
93
+
94
+ module.exports.removeCloudFrontDistribution = async (Id, account) => {
95
+ const client = createClient(CloudFrontClient, account);
96
+
97
+ let res = await Spinner.prototype.simple(`Retrieving the CloudFront distribution ${Id}`, () => {
98
+ return client.send(
99
+ new GetDistributionCommand({ Id })
100
+ );
101
+ });
102
+
103
+ let OAI = res.Distribution.DistributionConfig.Origins.Items[0].S3OriginConfig.OriginAccessIdentity.split('origin-access-identity/cloudfront/')[1];
104
+
105
+ res.Distribution.DistributionConfig.Enabled = false;
106
+
107
+ res = await Spinner.prototype.simple(`Disabling the CloudFront distribution`, () => {
108
+ return client.send(
109
+ new UpdateDistributionCommand({ DistributionConfig: res.Distribution.DistributionConfig, Id, IfMatch: res.ETag })
110
+ );
111
+ });
112
+
113
+ await Spinner.prototype.simple(`Waiting for CloudFront distribution to deploy`, () => {
114
+ return module.exports.waitForCloudFrontDistribution(res.Distribution.Id, account);
115
+ });
116
+
117
+ res = await Spinner.prototype.simple(`Deleting the CloudFront distribution`, () => {
118
+ return client.send(
119
+ new DeleteDistributionCommand({ Id, IfMatch: res.ETag })
120
+ );
121
+ });
122
+
123
+ res = await Spinner.prototype.simple(`Retrieving the CloudFront OAI`, () => {
124
+ return client.send(
125
+ new GetCloudFrontOriginAccessIdentityCommand({ Id: OAI })
126
+ );
127
+ });
128
+
129
+ res = await Spinner.prototype.simple(`Deleting the CloudFront OAI`, () => {
130
+ return client.send(
131
+ new DeleteCloudFrontOriginAccessIdentityCommand({ Id: OAI, IfMatch: res.ETag })
132
+ );
133
+ });
134
+ }
135
+
136
+ module.exports.waitForCloudFrontDistribution = async (Id, account) => {
137
+ const client = createClient(CloudFrontClient, account);
138
+
139
+ let status;
140
+
141
+ do{
142
+ await new Promise((resolve) => setTimeout(() => resolve(), 5000));
143
+
144
+ await Spinner.prototype.ping();
145
+
146
+ let check = await client.send(
147
+ new GetDistributionCommand({ Id })
148
+ );
149
+
150
+ status = check.Distribution.Status;
151
+ } while(status !== 'Deployed')
152
+ }
153
+
154
+ module.exports.createCloudFrontFunction = async (name, account, fn, config) => {
155
+ const client = createClient(CloudFrontClient, account);
156
+
157
+ let FunctionConfig = {
158
+ Comment: `lab-env provisioned cloudfront function for project ${name} using code snippet ${fn}.js`,
159
+ Runtime: `cloudfront-js-1.0`
160
+ };
161
+
162
+ let res;
163
+
164
+ try{
165
+ res = await Spinner.prototype.simple(`Creating CloudFront function`, () => {
166
+ return client.send(
167
+ new CreateFunctionCommand({
168
+ Name: name,
169
+ FunctionCode: new TextEncoder().encode(" "),
170
+ FunctionConfig
171
+ })
172
+ );
173
+ });
174
+ } catch (e){
175
+ res = await Spinner.prototype.simple(`Retrieving the already existing CloudFront function`, () => {
176
+ return client.send(
177
+ new GetFunctionCommand({
178
+ Name: name
179
+ })
180
+ );
181
+ });
182
+ }
183
+
184
+ let processedFn = fs.readFileSync(`${__dirname}/../../libs/${fn}.js`).toString();
185
+ processedFn = processedFn.replace(/<%=.*%>/g, (el) => JSON.stringify(config[el.slice(3, el.length - 2).trim()], null, 4));
186
+
187
+ res = await Spinner.prototype.simple(`Updating CloudFront function with ${fn}.js code`, () => {
188
+ return client.send(
189
+ new UpdateFunctionCommand({
190
+ Name: name,
191
+ FunctionCode: new TextEncoder().encode(processedFn),
192
+ FunctionConfig,
193
+ IfMatch: res.ETag
194
+ })
195
+ );
196
+ });
197
+
198
+ res = await Spinner.prototype.simple(`Publishing CloudFront function`, () => {
199
+ return client.send(
200
+ new PublishFunctionCommand({
201
+ Name: name,
202
+ IfMatch: res.ETag
203
+ })
204
+ );
205
+ });
206
+
207
+ return res;
208
+ }
209
+
210
+ module.exports.removeCloudFrontFunction = async (name, account) => {
211
+ const client = createClient(CloudFrontClient, account);
212
+
213
+ let res = await Spinner.prototype.simple(`Retrieving CloudFront function`, () => {
214
+ return client.send(
215
+ new GetFunctionCommand({
216
+ Name: name
217
+ })
218
+ );
219
+ });
220
+
221
+ res = await Spinner.prototype.simple(`Deleting CloudFront function`, () => {
222
+ return client.send(
223
+ new DeleteFunctionCommand({
224
+ Name: name,
225
+ IfMatch: res.ETag
226
+ })
227
+ );
228
+ });
229
+
230
+ return res;
231
+ }
232
+
233
+ module.exports.setCloudFrontFunctionAssociation = async (Id, account) => {
234
+ const client = createClient(CloudFrontClient, account);
235
+
236
+ let res = await Spinner.prototype.simple(`Retrieving CloudFront distribution`, () => {
237
+ return client.send(
238
+ new GetDistributionCommand({ Id })
239
+ );
240
+ });
241
+
242
+ let { FunctionSummary: { FunctionMetadata: { FunctionARN } } } = await Spinner.prototype.simple(`Retrieving CloudFront function`, () => {
243
+ return client.send(
244
+ new DescribeFunctionCommand({
245
+ Name: res.Distribution.DistributionConfig.CallerReference,
246
+ Stage: 'LIVE'
247
+ })
248
+ );
249
+ });
250
+
251
+ res.Distribution.DistributionConfig.DefaultCacheBehavior.FunctionAssociations = {
252
+ Items: [
253
+ {
254
+ EventType: 'viewer-request',
255
+ FunctionARN: FunctionARN
256
+ }
257
+ ],
258
+ Quantity: 1
259
+ };
260
+
261
+ res = await Spinner.prototype.simple(`Adding CloudFront function to CloudFront distribution`, () => {
262
+ return client.send(
263
+ new UpdateDistributionCommand({ DistributionConfig: res.Distribution.DistributionConfig, Id, IfMatch: res.ETag })
264
+ );
265
+ });
266
+
267
+ await Spinner.prototype.simple(`Waiting for CloudFront distribution to deploy`, () => {
268
+ return module.exports.waitForCloudFrontDistribution(res.Distribution.Id, account);
269
+ });
270
+ };
271
+
272
+ module.exports.removeCloudFrontFunctionAssociation = async (Id, account) => {
273
+ const client = createClient(CloudFrontClient, account);
274
+
275
+ let res = await Spinner.prototype.simple(`Retrieving CloudFront distribution`, () => {
276
+ return client.send(
277
+ new GetDistributionCommand({ Id })
278
+ );
279
+ });
280
+
281
+ res.Distribution.DistributionConfig.DefaultCacheBehavior.FunctionAssociations = {
282
+ Items: [],
283
+ Quantity: 0
284
+ };
285
+
286
+ res = await Spinner.prototype.simple(`Removing CloudFront function from CloudFront distribution`, () => {
287
+ return client.send(
288
+ new UpdateDistributionCommand({ DistributionConfig: res.Distribution.DistributionConfig, Id, IfMatch: res.ETag })
289
+ );
290
+ });
291
+
292
+ await Spinner.prototype.simple(`Waiting for CloudFront distribution to deploy`, () => {
293
+ return module.exports.waitForCloudFrontDistribution(res.Distribution.Id, account);
294
+ });
295
+ };