Skip to content
This repository has been archived by the owner on Mar 22, 2024. It is now read-only.

Commit

Permalink
Merge pull request #536 from Financial-Times/chores/add-output-logs
Browse files Browse the repository at this point in the history
Fail the build if the review app errors and provide link to output
  • Loading branch information
taktran authored Nov 30, 2018
2 parents 10a4791 + f050d6b commit 437de31
Show file tree
Hide file tree
Showing 3 changed files with 493 additions and 57 deletions.
130 changes: 110 additions & 20 deletions lib/review-apps.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const pRetry = require('p-retry');

const { info: pipelineInfo } = require('../lib/pipelines');
const herokuAuthToken = require('../lib/heroku-auth-token');
const { getGithubArchiveRedirectUrl } = require('./github-api');

Expand All @@ -16,9 +17,13 @@ const REVIEW_APP_STATUSES = {
pending: 'pending',
deleted: 'deleted',
creating: 'creating',
created: 'created'
created: 'created',
errored: 'errored'
};
const BUILD_STATUSES = {
succeeded: 'succeeded',
failed: 'failed'
};
const BUILD_STATUS_SUCCEEDED = 'succeeded';

const getReviewAppUrl = reviewAppId => `https://api.heroku.com/review-apps/${reviewAppId}`;
const getPipelineReviewAppsUrl = pipelineId => `https://api.heroku.com/pipelines/${pipelineId}/review-apps`;
Expand Down Expand Up @@ -50,7 +55,7 @@ const throwIfNotOk = async res => {
return res;
};

const waitTillReviewAppCreated = ({ minTimeout = MIN_TIMEOUT } = {}) => reviewApp => {
const waitTillReviewAppCreated = ({ commit, minTimeout = MIN_TIMEOUT } = {}) => reviewApp => {
const { id } = reviewApp;
const checkForCreatedStatus = async () => {
const headers = await herokuHeaders({ useReviewAppApi: true });
Expand All @@ -59,20 +64,38 @@ const waitTillReviewAppCreated = ({ minTimeout = MIN_TIMEOUT } = {}) => reviewAp
})
.then(throwIfNotOk)
.then(res => res.json())
.then(data => {
const { status, message, app } = data;
.then(async data => {
const { status, message, app = {} } = data;
const appId = !!app ? app.id : undefined;
if (status === REVIEW_APP_STATUSES.deleted) {
throw new pRetry.AbortError(`Review app was deleted: ${message}`);
}

if ((status === REVIEW_APP_STATUSES.errored)) {
if (!appId) {
throw new pRetry.AbortError(`Review app errored: ${message}`);
}

try {
const {
output_stream_url
} = await getAppBuildWithCommit({ appId, commit });
console.error(`App (${appId}, commit: ${commit}) errored.\n\nFor Heroku output see:\n${output_stream_url}`); // eslint-disable-line no-console
} catch (e) {
console.error(`Could not get app build for app id ${appId}, commit: ${commit}, ${e}`); // eslint-disable-line no-console
}

throw new pRetry.AbortError(`Review app errored: (appId: ${appId}) ${message}`);
}

if (status !== REVIEW_APP_STATUSES.created) {
const appIdOutput = (status === REVIEW_APP_STATUSES.creating)
? `, appId: ${app.id}`
? `, appId: ${appId}`
: '';
throw new Error(`Review app not created yet. Current status: ${status}${appIdOutput}`);
};

return app.id;
return appId;
});
return result;
};
Expand Down Expand Up @@ -121,23 +144,35 @@ const getBuilds = async (appId) => {
.then(res => res.json());
};

const waitForReviewAppBuild = ({ commit, minTimeout = MIN_TIMEOUT }) => async (appId) => {
const checkForBuildAppId = () => getBuilds(appId)
const getAppBuildWithCommit = ({ appId, commit }) => {
return getBuilds(appId)
.then(builds => {
const build = builds.find(({ source_blob: { version } }) => version === commit);

if (!build) {
throw new Error(`No review app build found for app id '${appId}';, commit '${commit}'`);
}
return build;
});
};

const { status } = build;
if (status !== BUILD_STATUS_SUCCEEDED) {
throw new Error(`Review app build for app id '${appId}' (commit '${commit}') not done yet: ${status}`);
}
const waitForReviewAppBuild = ({ commit, minTimeout = MIN_TIMEOUT }) => async (appId) => {
const checkForBuildAppId = () =>
getAppBuildWithCommit({ commit, appId })
.then(async build => {
if (!build) {
throw new Error(`No review app build found for app id '${appId}';, commit '${commit}'`);
}

return build;
})
.then(({ app: { id } }) => id);
const { status, output_stream_url } = build;
if ((status === BUILD_STATUSES.failed)) {
throw new pRetry.AbortError(`Review app build failed, appId: ${appId}, commit: ${commit}.\n\nFor Heroku output see:\n${output_stream_url}`);
}

if (status !== BUILD_STATUSES.succeeded) {
throw new Error(`Review app build for app id '${appId}' (commit '${commit}') not done yet: ${status}`);
}

return build;
})
.then(({ app: { id } }) => id);

return pRetry(checkForBuildAppId, {
factor: RETRY_EXP_BACK_OFF_FACTOR,
Expand Down Expand Up @@ -167,11 +202,66 @@ const createReviewApp = async ({ pipelineId, repoName, commit, branch, githubTok
});
};

/**
* Get the review app name based on the review app build.
* Create a review app if it does not exist, otherwise
* use the existing review app and make a new build
*
* @param {string} appName Heroku application name
* @param {string} repoName GitHub repository name
* @param {string} branch GitHub branch name
* @param {string} commit git commit SHA-1 to find the build
* @param {string} githubToken GitHub token for getting source code
*/
const getReviewAppName = async ({
appName,
repoName,
branch,
commit,
githubToken
}) => {
const { id: pipelineId } = await pipelineInfo(appName);

return createReviewApp({
pipelineId,
repoName,
commit,
branch,
githubToken
})
.then(res => {
const { status } = res;
if (status === 409) {
console.error(`Review app already created for '${branch}' branch. Using existing review app for build.`); // eslint-disable-line no-console
return findCreatedReviewApp({
pipelineId,
branch
})
.then(reviewApp => {
if (!reviewApp) {
throw new Error(`No review app found for pipeline ${pipelineId}, branch ${branch}`);
}

return reviewApp;
})
.then(waitTillReviewAppCreated({ commit }))
.then(waitForReviewAppBuild({ commit }))
.then(getAppName);
}
return Promise.resolve(res)
.then(res => res.json())
.then(waitTillReviewAppCreated({ commit }))
.then(getAppName);
});
};

module.exports = {
createReviewApp,
findCreatedReviewApp,
getAppName,
getBuilds,
getAppBuildWithCommit,
waitForReviewAppBuild,
waitTillReviewAppCreated
waitTillReviewAppCreated,
getReviewAppName
};
Loading

0 comments on commit 437de31

Please sign in to comment.