Skip to content
This repository has been archived by the owner on Feb 12, 2022. It is now read-only.

Commit

Permalink
Merge pull request #21 from heroku/pipeline-couplings-endpoints
Browse files Browse the repository at this point in the history
Use new pipeline-couplings endpoints
  • Loading branch information
gudmundur committed Feb 1, 2016
2 parents ed8ac79 + 6b4757d commit fd22a9e
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 94 deletions.
16 changes: 8 additions & 8 deletions commands/pipelines/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ let disambiguate = require('../../lib/disambiguate');
let prompt = require('../../lib/prompt');
let stageNames = require('../../lib/stages').names;

const createCoupling = require('../../lib/api').createCoupling;

module.exports = {
topic: 'pipelines',
command: 'add',
Expand All @@ -20,6 +22,8 @@ module.exports = {
{name: 'stage', char: 's', description: 'stage of first app in pipeline', hasValue: true}
],
run: cli.command(function* (context, heroku) {
const app = context.app;

var stage;
let guesses = infer(context.app);
let questions = [];
Expand All @@ -32,19 +36,15 @@ module.exports = {
questions.push({
type: "list",
name: "stage",
message: `Stage of ${context.app}`,
message: `Stage of ${app}`,
choices: stageNames,
default: guesses[1]
});
}
let answers = yield prompt(questions);
if (answers.stage) stage = answers.stage;
let promise = heroku.request({
method: 'POST',
path: `/apps/${context.app}/pipeline-couplings`,
body: {pipeline: {id: pipeline.id}, stage: stage},
headers: { 'Accept': 'application/vnd.heroku+json; version=3.pipelines' }
}); // heroku.apps(app_id).pipline_couplings().create(body);
yield cli.action(`Adding ${context.app} to ${pipeline.name} pipeline as ${stage}`, promise);

yield cli.action(`Adding ${app} to ${pipeline.name} pipeline as ${stage}`,
createCoupling(heroku, pipeline, app, stage));
})
};
16 changes: 8 additions & 8 deletions commands/pipelines/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ let infer = require('../../lib/infer');
let prompt = require('../../lib/prompt');
let stages = require('../../lib/stages').names;

const createCoupling = require('../../lib/api').createCoupling;

function* run(context, heroku) {
var name, stage;
let guesses = infer(context.app);
let questions = [];

const app = context.app;

if (context.args.name) {
name = context.args.name;
} else {
Expand All @@ -27,7 +31,7 @@ function* run(context, heroku) {
questions.push({
type: "list",
name: "stage",
message: `Stage of ${context.app}`,
message: `Stage of ${app}`,
choices: stages,
default: guesses[1]
});
Expand All @@ -42,13 +46,9 @@ function* run(context, heroku) {
headers: { 'Accept': 'application/vnd.heroku+json; version=3.pipelines' }
}); // heroku.pipelines().create({name: name});
let pipeline = yield cli.action(`Creating ${name} pipeline`, promise);
promise = heroku.request({
method: 'POST',
path: `/apps/${context.app}/pipeline-couplings`,
body: {pipeline: {id: pipeline.id}, stage: stage},
headers: { 'Accept': 'application/vnd.heroku+json; version=3.pipelines' }
}); // heroku.apps(app_id).pipline_couplings().create(body);
yield cli.action(`Adding ${context.app} to ${pipeline.name} pipeline as ${stage}`, promise);

yield cli.action(`Adding ${app} to ${pipeline.name} pipeline as ${stage}`,
createCoupling(heroku, pipeline, app, stage));
}

module.exports = {
Expand Down
125 changes: 64 additions & 61 deletions commands/pipelines/diff.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,71 +108,74 @@ function* diff(targetApp, downstreamApp, githubToken, herokuUserAgent) {
}
}

function* run(context, heroku) {
// jshint maxstatements:65
const targetAppName = context.app;
let coupling;
try {
coupling = yield heroku.request({
method: 'GET',
path: `/apps/${targetAppName}/pipeline-couplings`,
headers: { 'Accept': PIPELINES_HEADER }
});
} catch (err) {
return cli.error(`This app (${targetAppName}) does not seem to be a part of any pipeline`);
}
const targetAppId = coupling.app.id;

const allApps = yield cli.action(`Fetching apps from pipeline`,
heroku.request({
method: 'GET',
path: `/pipelines/${coupling.pipeline.id}/apps`,
headers: { 'Accept': PIPELINES_HEADER }
}));

const sourceStage = coupling.stage;
const downstreamStage = PROMOTION_ORDER[PROMOTION_ORDER.indexOf(sourceStage) + 1];
if (downstreamStage === null || PROMOTION_ORDER.indexOf(sourceStage) < 0) {
return cli.error(`Unable to diff ${targetAppName}`);
}
const downstreamApps = allApps.filter(function(app) {
return app.coupling.stage === downstreamStage;
});

if (downstreamApps.length < 1) {
return cli.error(`Cannot diff ${targetAppName} as there are no downstream apps configured`);
}

// Fetch GitHub repo/latest release hash for [target, downstream[0], .., downstream[n]] apps
const wrappedGetAppInfo = co.wrap(getAppInfo);
const appInfoPromises = [wrappedGetAppInfo(heroku, targetAppName, targetAppId)];
downstreamApps.forEach(function (app) {
appInfoPromises.push(wrappedGetAppInfo(heroku, app.name, app.id));
});
const appInfo = yield cli.action(`Fetching release info for all apps`,
bluebird.all(appInfoPromises));

// Verify the target app
let targetAppInfo = appInfo[0];
if (targetAppInfo.repo === null) {
return cli.error(`${targetAppName} does not seem to be connected to GitHub!`);
} else if (targetAppInfo.hash === null) {
return cli.error(`No release was found for ${targetAppName}, unable to diff`);
}

// Fetch GitHub token for the user
const githubAccount = yield kolkrabbiRequest(`/account/github/token`, heroku.options.token);

// Diff [{target, downstream[0]}, {target, downstream[1]}, .., {target, downstream[n]}]
const downstreamAppsInfo = appInfo.slice(1);
for (let downstreamAppInfo of downstreamAppsInfo) {
yield diff(
targetAppInfo, downstreamAppInfo, githubAccount.github.token, heroku.options.userAgent);
}
}

module.exports = {
topic: 'pipelines',
command: 'diff',
description: 'compares the latest release of this app its downstream app(s)',
needsAuth: true,
needsApp: true,
run: cli.command(function* (context, heroku) {
const targetAppName = context.app;
let coupling;
try {
coupling = yield heroku.request({
method: 'GET',
path: `/apps/${targetAppName}/pipeline-couplings`,
headers: { 'Accept': PIPELINES_HEADER }
});
} catch (err) {
return cli.error(`This app (${targetAppName}) does not seem to be a part of any pipeline`);
}
const targetAppId = coupling.app.id;

const allApps = yield cli.action(`Fetching apps from pipeline`,
heroku.request({
method: 'GET',
path: `/pipelines/${coupling.pipeline.id}/apps`,
headers: { 'Accept': PIPELINES_HEADER }
}));

const sourceStage = coupling.stage;
const downstreamStage = PROMOTION_ORDER[PROMOTION_ORDER.indexOf(sourceStage) + 1];
if (downstreamStage === null || PROMOTION_ORDER.indexOf(sourceStage) < 0) {
return cli.error(`Unable to diff ${targetAppName}`);
}
const downstreamApps = allApps.filter(function(app) {
return app.coupling.stage === downstreamStage;
});

if (downstreamApps.length < 1) {
return cli.error(`Cannot diff ${targetAppName} as there are no downstream apps configured`);
}

// Fetch GitHub repo/latest release hash for [target, downstream[0], .., downstream[n]] apps
const wrappedGetAppInfo = co.wrap(getAppInfo);
const appInfoPromises = [wrappedGetAppInfo(heroku, targetAppName, targetAppId)];
downstreamApps.forEach(function (app) {
appInfoPromises.push(wrappedGetAppInfo(heroku, app.name, app.id));
});
const appInfo = yield cli.action(`Fetching release info for all apps`,
bluebird.all(appInfoPromises));

// Verify the target app
let targetAppInfo = appInfo[0];
if (targetAppInfo.repo === null) {
return cli.error(`${targetAppName} does not seem to be connected to GitHub!`);
} else if (targetAppInfo.hash === null) {
return cli.error(`No release was found for ${targetAppName}, unable to diff`);
}

// Fetch GitHub token for the user
const githubAccount = yield kolkrabbiRequest(`/account/github/token`, heroku.options.token);

// Diff [{target, downstream[0]}, {target, downstream[1]}, .., {target, downstream[n]}]
const downstreamAppsInfo = appInfo.slice(1);
for (let downstreamAppInfo of downstreamAppsInfo) {
yield diff(
targetAppInfo, downstreamAppInfo, githubAccount.github.token, heroku.options.userAgent);
}
})
run: cli.command(co.wrap(run))
};
12 changes: 5 additions & 7 deletions commands/pipelines/remove.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

let cli = require('heroku-cli-util');

const removeCoupling = require('../../lib/api').removeCoupling;

module.exports = {
topic: 'pipelines',
command: 'remove',
Expand All @@ -10,12 +12,8 @@ module.exports = {
needsApp: true,
needsAuth: true,
run: cli.command(function* (context, heroku) {
let promise = heroku.request({
method: 'DELETE',
path: `/apps/${context.app}/pipeline-couplings`,
headers: { 'Accept': 'application/vnd.heroku+json; version=3.pipelines' }
}); // heroku.apps(app).pipeline-couplings().destroy();
let pipeline = yield cli.action(`Removing ${context.app}`, promise);
cli.hush(pipeline);
const app = context.app;

yield cli.action(`Removing ${app}`, removeCoupling(heroku, app));
})
};
15 changes: 7 additions & 8 deletions commands/pipelines/update.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

let cli = require('heroku-cli-util');
const updateCoupling = require('../../lib/api').updateCoupling;

module.exports = {
topic: 'pipelines',
Expand All @@ -17,13 +18,11 @@ module.exports = {
cli.error('Stage must be specified with -s');
process.exit(1);
}
let promise = heroku.request({
method: 'PATCH',
path: `/apps/${context.app}/pipeline-couplings`,
body: {stage: context.flags.stage},
headers: { 'Accept': 'application/vnd.heroku+json; version=3.pipelines' }
}); // heroku.apps(app).pipeline-couplings().update(body);
let pipeline = yield cli.action(`Changing ${context.app} to ${context.flags.stage}`, promise);
cli.hush(pipeline);

const app = context.app;
const stage = context.flags.stage;

yield cli.action(`Changing ${app} to ${stage}`,
updateCoupling(heroku, app, stage));
})
};
59 changes: 59 additions & 0 deletions lib/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const V3_HEADER = 'application/vnd.heroku+json; version=3';
const PIPELINES_HEADER = V3_HEADER + '.pipelines';

function getCoupling(heroku, app) {
return heroku.request({
method: 'GET',
path: `/apps/${app}/pipeline-couplings`,
headers: { 'Accept': PIPELINES_HEADER }
});
}

function postCoupling(heroku, pipeline, app, stage) {
return heroku.request({
method: 'POST',
path: '/pipeline-couplings',
body: {app: app, pipeline: pipeline, stage: stage},
headers: { 'Accept': PIPELINES_HEADER }
});
}

function patchCoupling(heroku, id, stage) {
return heroku.request({
method: 'PATCH',
path: `/pipeline-couplings/${id}`,
body: {stage: stage},
headers: { 'Accept': PIPELINES_HEADER }
});
}

function deleteCoupling(heroku, id) {
return heroku.request({
method: 'DELETE',
path: `/pipeline-couplings/${id}`,
headers: { 'Accept': PIPELINES_HEADER }
});
}

function createCoupling(heroku, pipeline, app, stage) {
return postCoupling(heroku, pipeline.id, app, stage);
}

function updateCoupling(heroku, app, stage) {
return getCoupling(heroku, app)
.then(coupling => patchCoupling(heroku, coupling.id, stage));
}

function removeCoupling(heroku, app) {
return getCoupling(heroku, app)
.then(coupling => deleteCoupling(heroku, coupling.id));
}

exports.getCoupling = getCoupling;
exports.postCoupling = postCoupling;
exports.patchCoupling = patchCoupling;
exports.deleteCoupling = deleteCoupling;

exports.createCoupling = createCoupling;
exports.updateCoupling = updateCoupling;
exports.removeCoupling = removeCoupling;
2 changes: 1 addition & 1 deletion test/commands/pipelines/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe('pipelines:add', function () {
.reply(200, pipelines);

nock('https://api.heroku.com')
.post('/apps/example/pipeline-couplings')
.post('/pipeline-couplings')
.reply(201, pipeline_coupling);

return cmd.run({app: 'example', args: {pipeline: 'example'}, flags: {stage: 'production'}})
Expand Down
2 changes: 1 addition & 1 deletion test/commands/pipelines/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('pipelines:create', function () {
let heroku = nock('https://api.heroku.com')
.post('/pipelines')
.reply(201, pipeline)
.post('/apps/example/pipeline-couplings')
.post('/pipeline-couplings')
.reply(201, pipeline_coupling);

return cmd.run({app: 'example', args: {name: 'example'}, flags: {stage: 'production'}})
Expand Down
27 changes: 27 additions & 0 deletions test/commands/pipelines/remove.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';

const cli = require('heroku-cli-util');
const nock = require('nock');
const cmd = require('../../../commands/pipelines/remove');

describe('pipelines:remove', () => {
beforeEach(() => cli.mockConsole());

it('displays the right messages', () => {
const app = 'example';
const id = '0123';

const pipeline_coupling = { id, stage: 'production' };

nock('https://api.heroku.com')
.get(`/apps/${app}/pipeline-couplings`)
.reply(200, pipeline_coupling);

nock('https://api.heroku.com')
.delete(`/pipeline-couplings/${id}`)
.reply(200, pipeline_coupling);

return cmd.run({ app })
.then(() => cli.stderr.should.contain(`Removing ${app}... done`));
});
});
Loading

0 comments on commit fd22a9e

Please sign in to comment.