1 | 'use strict'
|
2 |
|
3 | const co = require('co')
|
4 | const cli = require('heroku-cli-util')
|
5 |
|
6 | function 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 |
|
22 | module.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 |
|
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 |
|
119 | Run ${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 | })
|