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 #528 from Financial-Times/rationalise
Browse files Browse the repository at this point in the history
[WIP DO NOT MERGE] Simplify nht for the proposed new Heroku build and deployment process
  • Loading branch information
apaleslimghost authored Jan 24, 2019
2 parents 850d0a8 + ef01e00 commit 0ada2df
Show file tree
Hide file tree
Showing 40 changed files with 1,415 additions and 1,668 deletions.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ node_modules/@financial-times/n-gage/index.mk:
-include node_modules/@financial-times/n-gage/index.mk

unit-test:
export PORT=5134; mocha -r loadvars.js
jest

unit-test-watch:
jest --watch

minus-eslint: ci-n-ui-check _verify_lintspaces _verify_pa11y_testable

Expand Down
48 changes: 18 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
# n-heroku-tools

<a href="https://docs.google.com/forms/d/e/1FAIpQLSf5InA7UJK9yNBCzidFKI_WNkfbl6of1eRlIACRspGXUcBx8A/viewform?usp=pp_url&entry.78759464=n-heroku-tools" target="_blank"><img src="https://i.imgur.com/UmScdZ4.png" alt="Yak button" border="0" align="right" width="150" title="Report a yak shaving incident for this repository"></a>

This library is a command line tool that orchestrates [Heroku](https://www.heroku.com/) and [Amazon S3](https://aws.amazon.com/s3/) deployments for [Next](https://github.com/Financial-Times/next/wiki), based on configuration in the [Next service registry](https://next-registry.ft.com/v2/) and [Vault](https://www.vaultproject.io/).

<br clear="right">

### Installation

In order to use this tool, run

```
npm install @financial-times/n-heroku-tools --save-dev
```
Expand All @@ -17,31 +14,22 @@ npm install @financial-times/n-heroku-tools --save-dev

In order to use `n-heroku-tools` the following commands are available in your command line:

Usage: n-heroku-tools [options] [command]


Commands:

deploy [options] [app] runs haikro deployment scripts with sensible defaults for Next projects
configure [options] [source] [target] gets environment variables from Vault and uploads them to the current app
scale [options] [source] [target] downloads process information from next-service-registry and scales/sizes the application servers
provision [options] [app] provisions a new instance of an application server
review-app [options] [app] create a heroku review app and print out the app name created
destroy [options] [app] deletes the app from heroku
deploy-hashed-assets [options] deploys hashed asset files to S3 (if AWS keys set correctly)
deploy-static [options] <source> [otherSources...] Deploys static <source> to S3. Requires AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY env vars
run [options] Runs the local app through the router
rebuild [options] [apps...] Trigger a rebuild of the latest master on Circle
test-urls [options] [app] Tests that a given set of urls for an app respond as expected. Expects the config file ./test/smoke.js to exist
ship [options] Ships code. Deploys using pipelines, also running the configure and scale steps automatically
float [options] Deploys code to a test app and checks it doesn't die
drydock [options] [name] Creates a new pipeline with a staging and EU production app
smoke [options] [app] [DEPRECATED - Use n-test directly]. Tests that a given set of urls for an app respond as expected. Expects the config file ./test/smoke.js to exist
*

Options:

-h, --help output usage information
-V, --version output the version number
```
Usage: n-heroku-tools [options] [command]
Options:
-V, --version output the version number
-h, --help output usage information
Commands:
configure [options] [source] [target] gets environment variables from Vault and uploads them to the current app
deploy-hashed-assets [options] deploys hashed asset files to S3 (if AWS keys set correctly)
deploy-static [options] <source> [otherSources...] Deploys static <source> to S3. Requires AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY env vars
run [options] Runs the local app through the router
rebuild [options] [apps...] Trigger a rebuild of the latest master on Circle
gtg [app] Runs gtg checks for an app
review-app [options] [appName] Create or find an existing heroku review app and print out the app name. [appName] is the package.json name (which is also the value of VAULT_NAME). On the first build of a branch, Heroku will create a review app with a build. On subsequent builds, Heroku will automatically generate a new build, which this task looks for. See https://devcenter.heroku.com/articles/review-apps-beta for more details of the internals
*
```

*Note*: The README.md is automatically generated. Run `make docs` to update it.
5 changes: 0 additions & 5 deletions ascii/canoe.ascii

This file was deleted.

18 changes: 0 additions & 18 deletions ascii/ship-in-bottle.ascii

This file was deleted.

18 changes: 0 additions & 18 deletions ascii/yacht.ascii

This file was deleted.

15 changes: 1 addition & 14 deletions bin/n-heroku-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,25 @@
require('isomorphic-fetch');

let program = require('commander');
let logger = require('../lib/logger');

const utils = {
list: val => {
return val.split(',');
},

exit: err => {
logger.error(err);
if (err.stack) {
logger.error(err.stack);
}
console.error(err); // eslint-disable-line no-console
process.exit(1);
}
};

program.version(require('../package.json').version);

require('../tasks/deploy')(program, utils);
require('../tasks/configure')(program, utils);
require('../tasks/scale')(program, utils);
require('../tasks/provision')(program, utils);
require('../tasks/destroy')(program, utils);
require('../tasks/deploy-hashed-assets')(program, utils);
require('../tasks/deploy-static')(program, utils);
require('../tasks/run')(program, utils);
require('../tasks/rebuild')(program, utils);
require('../tasks/test-urls')(program, utils);
require('../tasks/ship')(program, utils);
require('../tasks/float')(program, utils);
require('../tasks/drydock')(program, utils);
require('../tasks/smoke')(program, utils);
require('../tasks/gtg')(program, utils);
require('../tasks/review-app')(program, utils);

Expand Down
5 changes: 5 additions & 0 deletions lib/__mocks__/github-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
getGithubArchiveRedirectUrl: jest.fn(() => {
return Promise.resolve('https://github.com/some-tarball-link');
})
};
2 changes: 2 additions & 0 deletions lib/__mocks__/shellpromise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const HEROKU_AUTH_TOKEN = 'herokuToken123';
module.exports = () => Promise.resolve(HEROKU_AUTH_TOKEN);
10 changes: 0 additions & 10 deletions lib/commit.js

This file was deleted.

27 changes: 27 additions & 0 deletions lib/github-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const getGithubArchiveUrl = ({ repoName, branch }) => `https://api.github.com/repos/Financial-Times/${repoName}/tarball/${branch}`;

const getGithubArchiveRedirectUrl = ({ repoName, branch, githubToken }) => {
const url = getGithubArchiveUrl({ repoName, branch });

return fetch(url, {
headers: {
Authorization: `token ${githubToken}`
},
redirect: 'manual' // Don't follow redirect, just want the URL
}).then(async res => {
const { status } = res;
if (status !== 302) {
const error = await res.json();
throw new Error(`Unexpected response for ${url} (${status}): ${JSON.stringify(error)}`);
}

const { headers: { _headers: { location } } } = res;
const [ redirectUrl ] = location || [];

return redirectUrl;
});
};

module.exports = {
getGithubArchiveRedirectUrl
};
28 changes: 18 additions & 10 deletions lib/heroku-api.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
function herokuApi ({ endpoint, authToken }) {
return fetch(
`https://api.heroku.com${endpoint}`,
{
headers: {
Accept: 'Accept: application/vnd.heroku+json; version=3',
'Content-Type': 'application/json',
Authorization: `Bearer ${authToken}`
}
const merge = require('lodash.merge');

function herokuApi ({ endpoint, authToken, options = {} }) {

const defaultFetchOptions = {
headers: {
Accept: 'application/vnd.heroku+json; version=3',
'Content-Type': 'application/json',
Authorization: `Bearer ${authToken}`
}
};

const fetchOptions = merge(defaultFetchOptions, options);
const url = `https://api.heroku.com${endpoint}`;

return fetch(
url,
fetchOptions
).then(response => {
const { ok, status, statusText } = response;
if (!ok) {
let err = new Error(`BadResponse: ${status} ${statusText}`);
let err = new Error(`BadResponse: ${url} - ${status} ${statusText}`);
err.name = 'BAD_RESPONSE';
err.status = status;
throw err;
Expand Down
57 changes: 57 additions & 0 deletions lib/heroku-api.unit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
require('isomorphic-fetch');
const nock = require('nock');
const herokuApi = require('./heroku-api');

describe('heroku-api', () => {
let nockScope;
beforeAll(() => {
nockScope = nock('https://api.heroku.com');
});

afterEach(() => {
nock.cleanAll();
});

it('calls the endpoint', async () => {
const endpoint = '/something';
nockScope.get(endpoint)
.reply(200, {
something: 'yes'
});
const output = await herokuApi({ endpoint });

expect(output).toEqual({
something: 'yes'
});
});

it('uses the auth token', async () => {
const endpoint = '/';
const authToken = 'some123';
nockScope.get(endpoint)
.reply(200, function () {
const { headers } = this.req;
return {
headers
};
});
const { headers: { authorization } } = await herokuApi({ endpoint, authToken });
const [ bearerToken ] = authorization;
expect(bearerToken).toEqual(`Bearer ${authToken}`);
});

it('throws an error on failure', async () => {
const endpoint = '/';
const authToken = 'some123';
nockScope.get(endpoint)
.reply(400, {});

try {
await herokuApi({ endpoint, authToken });
} catch (error) {
const { name, status } = error;
expect(name).toEqual('BAD_RESPONSE');
expect(status).toEqual(400);
};
});
});
38 changes: 26 additions & 12 deletions lib/heroku-auth-token.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
'use strict';

let shellpromise = require('shellpromise');
let authToken;

module.exports = function () {
const getAuthFromCli = () => {
return shellpromise('heroku auth:whoami 2>/dev/null')
.then(function () {
return shellpromise('heroku auth:token 2>/dev/null');
})
.then(function (token) {
return token.trim();
})
.catch(function (err) {
console.error(err); // eslint-disable-line no-console
throw new Error('Please make sure the Heroku CLI is authenticated by running `heroku auth:token`');
});
};

module.exports = async function () {
if (process.env.HEROKU_AUTH_TOKEN) {

return Promise.resolve(process.env.HEROKU_AUTH_TOKEN);

} else {
return shellpromise('heroku auth:whoami 2>/dev/null')
.then(function () {
return shellpromise('heroku auth:token 2>/dev/null');
})
.then(function (token) {
return token.trim();
})
.catch(function (err) {
console.error(err); // eslint-disable-line no-console
throw new Error('Please make sure the Heroku CLI is authenticated by running `heroku auth:token`');
});

if (authToken) {
return authToken;
} else {
authToken = await getAuthFromCli();
return authToken;
}

}
};
Loading

0 comments on commit 0ada2df

Please sign in to comment.