1 |
|
2 | 'use strict'
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | const fs = require('fs')
|
20 | const path = require('path')
|
21 |
|
22 | const archiver = require('archiver')
|
23 | const AWS = require('aws-sdk')
|
24 | const chalk = require('chalk')
|
25 | const inquirer = require('inquirer')
|
26 | const logSymbols = require('log-symbols')
|
27 | const request = require('request')
|
28 | const temp = require('temp').track()
|
29 |
|
30 | const logUpdates = require('./utils/log-updates.js')
|
31 | const pkg = require('../package.json')
|
32 | const values = require('./values.js')
|
33 |
|
34 | const EXT = 'zip'
|
35 |
|
36 | function authenticate (
|
37 | config /* : BlinkMRCServer */,
|
38 | blinkMobileIdentity /* : BlinkMobileIdentity */,
|
39 | env /* : string */
|
40 | ) /* : Promise<BMServerAuthentication> */ {
|
41 | const stopUpdates = logUpdates(() => 'Authenticating...')
|
42 | return Promise.resolve()
|
43 | .then(() => ({
|
44 | bmProject: config.project,
|
45 | command: 'deploy',
|
46 | environment: env
|
47 | }))
|
48 | .then((parameters) => Promise.all([
|
49 | blinkMobileIdentity.assumeAWSRole(parameters),
|
50 | blinkMobileIdentity.getAccessToken()
|
51 | ]))
|
52 | .then((results) => {
|
53 | stopUpdates((logUpdater) => logUpdater(logSymbols.success, 'Authentication complete!'))
|
54 | return results
|
55 | })
|
56 | .catch((err) => {
|
57 | stopUpdates((logUpdater) => logUpdater(logSymbols.error, 'Authentication failed...'))
|
58 | return Promise.reject(err)
|
59 | })
|
60 | }
|
61 |
|
62 | function confirm (
|
63 | logger /* : typeof console */,
|
64 | force /* : boolean */,
|
65 | env /* : string */
|
66 | ) /* : Promise<boolean> */ {
|
67 | if (force) {
|
68 | return Promise.resolve(true)
|
69 | }
|
70 | logger.log(chalk.yellow(`
|
71 | Please check configuration before continuing
|
72 | `))
|
73 | const promptQuestions = [{
|
74 | type: 'confirm',
|
75 | name: 'confirmation',
|
76 | message: `Are you sure you want to deploy to environment "${env}": [Y]`
|
77 | }]
|
78 | return inquirer.prompt(promptQuestions)
|
79 | .then(results => results.confirmation)
|
80 | }
|
81 |
|
82 | function deploy (
|
83 | bundleKey /* : string */,
|
84 | accessToken /* : string | void */,
|
85 | env /* : string */,
|
86 | config /* : BlinkMRCServer */
|
87 | ) /* : Promise<void> */ {
|
88 | const serviceCliService = config.service || {}
|
89 | serviceCliService.bucket = serviceCliService.bucket || values.SERVER_CLI_SERVICE_S3_BUCKET
|
90 | serviceCliService.origin = serviceCliService.origin || values.SERVER_CLI_SERVICE_ORIGIN
|
91 | const stopUpdates = logUpdates(() => `Deploying project - this may take several minutes...`)
|
92 | return new Promise((resolve, reject) => {
|
93 |
|
94 | const baseRequest = request.defaults({
|
95 | auth: {
|
96 | bearer: accessToken
|
97 | },
|
98 | baseUrl: `${serviceCliService.origin}/v1`,
|
99 | json: true
|
100 | })
|
101 | baseRequest.post('/deploy', {
|
102 | json: {
|
103 | bundleBucket: serviceCliService.bucket,
|
104 | bundleKey,
|
105 | env,
|
106 | bmServerVersion: pkg.version
|
107 | }
|
108 | }, (err, deployResponse, deployData) => {
|
109 |
|
110 | if (err) {
|
111 | stopUpdates((logUpdater) => logUpdater(logSymbols.error, 'Deployment failed...'))
|
112 | reject(err)
|
113 | return
|
114 | }
|
115 | if (deployResponse.statusCode !== 202) {
|
116 | stopUpdates((logUpdater) => logUpdater(logSymbols.error, `Deployment failed - ${deployData.statusCode} ${deployData.error}`))
|
117 | reject(new Error(deployData.message))
|
118 | return
|
119 | }
|
120 |
|
121 |
|
122 | const intervalId = setInterval(() => {
|
123 | baseRequest.get(`/deployments/${deployData.id}`, (error, response, data) => {
|
124 |
|
125 | if (error) {
|
126 | stopUpdates((logUpdater) => logUpdater(logSymbols.error, 'Deployment failed...'))
|
127 | clearInterval(intervalId)
|
128 | reject(error)
|
129 | return
|
130 | }
|
131 | if (response.statusCode !== 200) {
|
132 | stopUpdates((logUpdater) => logUpdater(logSymbols.error, `Deployment failed - ${data.statusCode} ${data.error}`))
|
133 | clearInterval(intervalId)
|
134 | reject(new Error(data.message))
|
135 | return
|
136 | }
|
137 | if (data.error) {
|
138 | stopUpdates((logUpdater) => logUpdater(logSymbols.error, `Deployment failed - ${data.error.name}`))
|
139 | clearInterval(intervalId)
|
140 | reject(new Error(data.error.message))
|
141 | return
|
142 | }
|
143 |
|
144 |
|
145 |
|
146 | if (data.result) {
|
147 | stopUpdates((logUpdater) => logUpdater(logSymbols.success, `Deployment complete - Origin: ${data.result.baseUrl}`))
|
148 | clearInterval(intervalId)
|
149 | resolve(data.result)
|
150 | }
|
151 | })
|
152 | }, 5000)
|
153 | })
|
154 | })
|
155 | }
|
156 |
|
157 | function upload (
|
158 | zipFilePath /* : string */,
|
159 | awsCredentials /* : AWSCredentials */,
|
160 | config /* : BlinkMRCServer */
|
161 | ) /* : Promise<string> */ {
|
162 | const src = fs.createReadStream(zipFilePath)
|
163 | const key = path.basename(zipFilePath)
|
164 | const s3 = new AWS.S3(awsCredentials)
|
165 | const params = {
|
166 | Bucket: (config.service || {}).bucket || values.SERVER_CLI_SERVICE_S3_BUCKET,
|
167 | Key: key,
|
168 | Body: src
|
169 | }
|
170 |
|
171 | const manager = s3.upload(params)
|
172 | let progress = 0
|
173 | manager.on('httpUploadProgress', uploadProgress => {
|
174 |
|
175 |
|
176 | if (uploadProgress.total) {
|
177 | progress = Math.floor(uploadProgress.loaded / uploadProgress.total * 100)
|
178 | }
|
179 | })
|
180 |
|
181 | const stopUpdates = logUpdates(() => `Transferring project: ${progress}%`)
|
182 | return new Promise((resolve, reject) => {
|
183 | manager.send((err, data) => stopUpdates((logUpdater) => {
|
184 | if (err) {
|
185 | reject(err)
|
186 | logUpdater(logSymbols.error, `Transfer failed: ${progress}%`)
|
187 | return
|
188 | }
|
189 | logUpdater(logSymbols.success, 'Transfer complete!')
|
190 | resolve(data.Key)
|
191 | }))
|
192 | })
|
193 | }
|
194 |
|
195 | function zip (
|
196 | cwd /* : string */
|
197 | ) /* : Promise<string> */ {
|
198 | const archive = archiver.create(EXT, {})
|
199 | const output = temp.createWriteStream({suffix: `.${EXT}`})
|
200 | archive.pipe(output)
|
201 | archive.glob('**/*', {
|
202 | cwd,
|
203 | nodir: true,
|
204 | dot: true,
|
205 | ignore: [
|
206 | '.git/**'
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | ]
|
212 | })
|
213 | archive.finalize()
|
214 | const stopUpdates = logUpdates(() => 'Compressing project...')
|
215 | return new Promise((resolve, reject) => {
|
216 | const fail = (err) => {
|
217 | stopUpdates((logUpdater) => logUpdater(logSymbols.error, 'Compression failed...'))
|
218 | reject(err)
|
219 | }
|
220 |
|
221 | archive.on('error', (err) => fail(err))
|
222 | output.on('error', (err) => fail(err))
|
223 | output.on('finish', () => {
|
224 | stopUpdates((logUpdater) => logUpdater(logSymbols.success, 'Compression complete!'))
|
225 | resolve(output.path)
|
226 | })
|
227 | })
|
228 | }
|
229 |
|
230 | module.exports = {
|
231 | authenticate,
|
232 | confirm,
|
233 | deploy,
|
234 | upload,
|
235 | zip
|
236 | }
|