UNPKG

4 kBJavaScriptView Raw
1'use strict'
2
3const co = require('co')
4const cli = require('heroku-cli-util')
5
6function prefix (transfer) {
7 if (transfer.from_type === 'pg_dump') {
8 if (transfer.to_type === 'pg_restore') {
9 return 'c'
10 } else {
11 return transfer.schedule ? 'a' : 'b'
12 }
13 } else {
14 if (transfer.to_type === 'pg_restore') {
15 return 'r'
16 } else {
17 return 'b'
18 }
19 }
20}
21
22module.exports = (context, heroku) => ({
23 filesize: (size, opts = {}) => {
24 Object.assign(opts, {
25 decimalPlaces: 2,
26 fixedDecimals: true
27 })
28 const bytes = require('bytes')
29 return bytes(size, opts)
30 },
31 transfer: {
32 num: co.wrap(function * (name) {
33 let m = name.match(/^[abcr](\d+)$/)
34 if (m) return parseInt(m[1])
35 m = name.match(/^o[ab]\d+$/)
36 if (m) {
37 const host = require('./host')()
38 let transfers = yield heroku.get(`/client/v11/apps/${context.app}/transfers`, { host })
39 let transfer = transfers.find(t => module.exports(context, heroku).transfer.name(t) === name)
40 if (transfer) return transfer.num
41 }
42 }),
43 name: transfer => {
44 let oldPGBName = transfer.options && transfer.options.pgbackups_name
45 if (oldPGBName) return `o${oldPGBName}`
46 return `${prefix(transfer)}${(transfer.num || '').toString().padStart(3, '0')}`
47 },
48 status: transfer => {
49 if (transfer.finished_at && transfer.succeeded) {
50 let warnings = transfer.warnings
51 if (warnings > 0) {
52 return `Finished with ${warnings} warnings`
53 } else {
54 return `Completed ${transfer.finished_at}`
55 }
56 } else if (transfer.finished_at) {
57 return `Failed ${transfer.finished_at}`
58 } else if (transfer.started_at) {
59 return `Running (processed ${module.exports(context, heroku).filesize(transfer.processed_bytes)})`
60 } else {
61 return 'Pending'
62 }
63 }
64 },
65 wait: (action, transferID, interval, verbose, app) => {
66 if (app === undefined) {
67 app = context.app
68 }
69 const wait = require('co-wait')
70 const host = require('./host')()
71 const pgbackups = module.exports(context, heroku)
72
73 let shownLogs = []
74 let displayLogs = logs => {
75 for (let log of logs) {
76 if (shownLogs.find(l => l.created_at === log.created_at && l.message === log.message)) continue
77 shownLogs.push(log)
78 cli.log(`${log.created_at} ${log.message}`)
79 }
80 }
81
82 let poll = co.wrap(function * () {
83 let tty = process.env.TERM !== 'dumb' && process.stderr.isTTY
84 let backup
85 let failures = 0
86
87 let quietUrl = `/client/v11/apps/${app}/transfers/${transferID}`
88 let verboseUrl = quietUrl + '?verbose=true'
89
90 let url = verbose ? verboseUrl : quietUrl
91
92 while (true) {
93 try {
94 backup = yield heroku.get(url, { host })
95 } catch (err) {
96 if (failures++ > 20) throw err
97 }
98 if (verbose) {
99 displayLogs(backup.logs)
100 } else if (tty) {
101 let msg = backup.started_at ? pgbackups.filesize(backup.processed_bytes) : 'pending'
102 let log = backup.logs && backup.logs.pop()
103 if (log) {
104 cli.action.status(`${msg}\n${log.created_at + ' ' + log.message}`)
105 } else {
106 cli.action.status(msg)
107 }
108 }
109 if (backup && backup.finished_at) {
110 if (backup.succeeded) return
111 else {
112 // logs is undefined unless verbose=true is passed
113 backup = yield heroku.get(verboseUrl, { host })
114
115 throw new Error(`An error occurred and the backup did not finish.
116
117${backup.logs.slice(-5).map(l => l.message).join('\n')}
118
119Run ${cli.color.cmd('heroku pg:backups:info ' + pgbackups.transfer.name(backup))} for more details.`)
120 }
121 }
122 yield wait(interval * 1000)
123 }
124 })
125
126 if (verbose) {
127 cli.log(`${action}...`)
128 return poll()
129 } else {
130 return cli.action(action, poll())
131 }
132 }
133})