-
Notifications
You must be signed in to change notification settings - Fork 228
/
Copy pathrelease.ts
115 lines (100 loc) · 3.71 KB
/
release.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import color from '@heroku-cli/color'
import {Command, flags} from '@heroku-cli/command'
import {ux} from '@oclif/core'
import * as Heroku from '@heroku-cli/schema'
import {debug} from '../../lib/container/debug'
import {streamer} from '../../lib/container/streamer'
import {ensureContainerStack} from '../../lib/container/helpers'
type ImageResponse = {
schemaVersion: number,
history: [{
v1Compatibility: string,
}
],
config: {
digest: string
}
}
export default class ContainerRelease extends Command {
static topic = 'container'
static description = 'Releases previously pushed Docker images to your Heroku app'
static usage = 'container:release'
static example = `
${color.cmd('heroku container:release web')} # Releases the previously pushed web process type
${color.cmd('heroku container:release web worker')} # Releases the previously pushed web and worker process types`
static strict = false
static flags = {
app: flags.app({required: true}),
remote: flags.remote(),
verbose: flags.boolean({char: 'v'}),
}
async run() {
const {flags, argv} = await this.parse(ContainerRelease)
const {app, verbose} = flags
if (argv.length === 0) {
this.error(`Error: Requires one or more process types\n ${ContainerRelease.example}`)
}
if (verbose) {
debug.enabled = true
}
const {body: appBody} = await this.heroku.get<Heroku.App>(`/apps/${app}`)
ensureContainerStack(appBody, 'release')
const herokuHost: string = process.env.HEROKU_HOST || 'heroku.com'
const updateData: any[] = []
for (const process of argv) {
const image = `${app}/${process}`
const tag = 'latest'
const {body: imageResp} = await this.heroku.get<ImageResponse>(
`/v2/${image}/manifests/${tag}`,
{
hostname: `registry.${herokuHost}`,
headers: {
Accept: 'application/vnd.docker.distribution.manifest.v2+json',
Authorization: `Basic ${Buffer.from(`:${this.heroku.auth}`).toString('base64')}`,
}},
)
let imageID
let v1Comp
switch (imageResp.schemaVersion) {
case 1:
v1Comp = JSON.parse(imageResp.history[0].v1Compatibility)
imageID = v1Comp.id
break
case 2:
imageID = imageResp.config.digest
break
}
updateData.push({
type: process, docker_image: imageID,
})
}
const {body: oldReleases} = await this.heroku.get<Heroku.Release[]>(`/apps/${app}/releases`, {
partial: true, headers: {Range: 'version ..; max=1, order=desc'},
})
const oldRelease = oldReleases[0]
ux.action.start(`Releasing images ${argv.join(',')} to ${app}`)
await this.heroku.patch(`/apps/${app}/formation`, {
body: {updates: updateData}, headers: {
Accept: 'application/vnd.heroku+json; version=3.docker-releases',
},
})
ux.action.stop()
const {body: updatedReleases} = await this.heroku.get<Heroku.Release[]>(`/apps/${app}/releases`, {
partial: true, headers: {Range: 'version ..; max=1, order=desc'},
})
const release = updatedReleases[0]
if ((!oldRelease && !release) || (oldRelease && (oldRelease.id === release.id))) {
return
}
if (release.status === 'failed') {
ux.error('Error: release command failed', {exit: 1})
} else if ((release.status === 'pending') && release.output_stream_url) {
ux.log('Running release command...')
await streamer(release.output_stream_url, process.stdout)
const {body: finishedRelease} = await this.heroku.request<Heroku.Release>(`/apps/${app}/releases/${release.id}`)
if (finishedRelease.status === 'failed') {
ux.error('Error: release command failed', {exit: 1})
}
}
}
}