Skip to content

Commit c20fcb6

Browse files
Mark Pundsackjdx
authored andcommitted
Merge pull request #1 from heroku/promote
Promote with releases API
1 parent 5828797 commit c20fcb6

File tree

2 files changed

+59
-30
lines changed

2 files changed

+59
-30
lines changed

packages/heroku-pipelines/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ example-staging ahead by 1 commit:
9393
#### Promote an app in a pipeline
9494

9595
```bash
96-
$ heroku pipelines:promote -r staging # Not implemented yet
96+
$ heroku pipelines:promote -r staging
9797
Promoting example-staging to example (production)... done, v23
9898
Promoting example-staging to example-admin (production)... done, v54
9999
```
Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
'use strict';
22

33
let cli = require('heroku-cli-util');
4-
let co = require('co');
4+
5+
const PROMOTION_ORDER = ["development", "staging", "production"];
6+
const V3_HEADER = 'application/vnd.heroku+json; version=3';
7+
const PIPELINES_HEADER = V3_HEADER + '.pipelines';
58

69
module.exports = {
710
topic: 'pipelines',
@@ -10,40 +13,66 @@ module.exports = {
1013
help: "promote an app's slug down the pipeline",
1114
needsApp: true,
1215
needsAuth: true,
13-
flags: [
14-
{name: 'stage', char: 's', description: 'stage of first app in pipeline', hasValue: true}
15-
],
1616
run: cli.command(function* (context, heroku) {
17-
var stage;
17+
const app = context.app;
1818

19-
stage = context.flags.stage;
20-
let promise = heroku.request({
19+
const coupling = yield cli.action(`Fetching app info`, heroku.request({
2120
method: 'GET',
22-
path: `/apps/${context.app}/pipeline-couplings`,
23-
headers: { 'Accept': 'application/vnd.heroku+json; version=3.pipelines' }
21+
path: `/apps/${app}/pipeline-couplings`,
22+
headers: { 'Accept': PIPELINES_HEADER }
23+
}));
24+
25+
const allApps = yield cli.action(`Fetching apps from ${coupling.pipeline.name}`,
26+
heroku.request({
27+
method: 'GET',
28+
path: `/pipelines/${coupling.pipeline.id}/apps`,
29+
headers: { 'Accept': PIPELINES_HEADER }
30+
}));
31+
32+
const sourceStage = coupling.stage;
33+
const targetStage = PROMOTION_ORDER[PROMOTION_ORDER.indexOf(sourceStage) + 1];
34+
35+
if (targetStage == null || PROMOTION_ORDER.indexOf(sourceStage) < 0) {
36+
throw new Error(`Cannot promote ${app} from '${sourceStage}' stage`);
37+
}
38+
39+
const targetApps = allApps.filter(function(app) {
40+
return app.coupling.stage === targetStage;
2441
});
25-
let coupling = yield cli.action(`Fetching app info`, promise);
2642

27-
promise = heroku.request({
28-
method: 'GET',
29-
path: `/pipelines/${coupling.pipeline.id}/apps`,
30-
headers: { 'Accept': 'application/vnd.heroku+json; version=3.pipelines' }
31-
}); // heroku.pipelines(pipeline_id).apps;
32-
let apps = yield cli.action(`Fetching apps from ${coupling.pipeline.id}`, promise);
33-
34-
//let order = ["review", "development", "test", "qa", "staging", "production"];
35-
36-
let next_apps = [];
37-
for (var app in apps) {
38-
if (apps.hasOwnProperty(app)) {
39-
if (apps[app].coupling.stage == stage) next_apps.push(app);
40-
}
43+
if (targetApps.length < 1) {
44+
throw new Error(`Cannot promote from ${app} as there are no downstream apps in $(targetStage) stage`);
4145
}
42-
cli.debug(next_apps);
43-
for (var app in next_apps) {
44-
if (keys.hasOwnProperty(app)) {
45-
cli.log(`Promoting ${context.app} to ${app.name} (${stage})... done, v2`);
46-
}
46+
47+
const releases = yield cli.action(`Fetching latest release from ${app}`,
48+
heroku.request({
49+
method: 'GET',
50+
path: `/apps/${app}/releases`,
51+
headers: { 'Accept': V3_HEADER, 'Range': 'version ..; order=desc,max=1;' }
52+
}));
53+
cli.hush(releases);
54+
55+
const sourceRelease = releases[0];
56+
57+
if (sourceRelease == null) {
58+
throw new Error(`Cannot promote from ${app} as it has no builds yet`);
4759
}
60+
61+
const sourceSlug = sourceRelease.slug.id;
62+
63+
yield targetApps.map(function(targetApp) {
64+
const promotion = heroku.request({
65+
method: 'POST',
66+
path: `/apps/${targetApp.id}/releases`,
67+
headers: {
68+
'Accept': V3_HEADER,
69+
'Heroku-Deploy-Type': 'pipeline-promote',
70+
'Heroku-Deploy-Source': app
71+
},
72+
body: { slug: sourceSlug }
73+
});
74+
75+
return cli.action(`Promoting to ${targetApp.name}`, promotion);
76+
});
4877
})
4978
};

0 commit comments

Comments
 (0)