UNPKG

7.19 kBJavaScriptView Raw
1/* @flow */
2'use strict'
3
4/* ::
5import type BlinkMobileIdentity, {
6 AWSCredentials
7} from '@blinkmobile/bm-identity'
8
9import type {
10 BlinkMRCServer
11} from '../types.js'
12
13type BMServerAuthentication = [
14 AWSCredentials,
15 string | void
16]
17*/
18
19const fs = require('fs')
20const path = require('path')
21
22const archiver = require('archiver')
23const AWS = require('aws-sdk')
24const chalk = require('chalk')
25const inquirer = require('inquirer')
26const logSymbols = require('log-symbols')
27const request = require('request')
28const temp = require('temp').track()
29
30const logUpdates = require('./utils/log-updates.js')
31const pkg = require('../package.json')
32const values = require('./values.js')
33
34const EXT = 'zip'
35
36function 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
62function 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(`
71Please 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
82function 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 // Make request to start deployment
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 // Ensure deployment started successfully
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 // Start polling deployment for status updates
122 const intervalId = setInterval(() => {
123 baseRequest.get(`/deployments/${deployData.id}`, (error, response, data) => {
124 // Ensure request for status of deployment is successful
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 // If status has a result on it, it has finished.
145 // We can stop the polling and display origin deployed to.
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
157function 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 // Note that total may be undefined until the payload size is known.
175 // http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3/ManagedUpload.html
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
195function 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 // Still unsure on whether or not this will be needed.
208 // If projects have private packages, we can not ignore node_modules
209 // as the server cli service will not be able to download them.
210 // 'node_modules/**'
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
230module.exports = {
231 authenticate,
232 confirm,
233 deploy,
234 upload,
235 zip
236}