@doop/deploy
Version:
All purpose Doop server deployment script
227 lines (183 loc) • 11 kB
Markdown
/Deploy
============
All purpose Doop server deployment script.
This project exposes an executable command line script `doop-deploy` which is used to deploy [Doop](https://github.com/MomsFriendlyDevCo/Doop) projects based on their configuration from `app.config.deploy.profiles`.
**Features**:
* Simple pull from git + build + restart PM2 deployment process
* Peer deployments of other profiles if required
* Special branch expressions to query + deploy to complex branches
* File date deltas to skip unnecessary package reinstall, build, restart steps
* Automatic handling of PM2 processes and custom names
* Optional branch Semver + version bumping on successful deploy
NOTE: For verbose debugging set the `DEBUG=deploy` environment variable as per the [Debug](https://github.com/debug-js/debug) config.
System-wide config
------------------
/Deploy generally reads from a Doop projects main config object but if the script is installed system-wide it will also check for a `~/.doop-deploy` INI file first.
Any keys within this file are merged into the main ENV setup before processing anything else.
An example of using `~/.doop-deploy` to specify a directory to change to before trying to read config:
```
# Change to site directory to read config
DOOP_DEPLOY_BASE=/sites/acme.com
```
Environment Variables
---------------------
Environment variables are read from the current shell and the above `~/.doop-deploy` file before execution.
| Variable | Type | Description |
|-----------------------|----------|-------------------------------------------------------------------|
| `DOOP_DEPLOY_BASE` | `String` | Set the working direcotry before attempting to read in the config |
| `DOOP_DEPLOY_PROFILE` | `String` | Profile to deploy if no CLI profile is selected |
Configuration
-------------
Configuration is read per-profile from the `app.config.deploy.profiles` object. Each key is the local ID of the profile with the object following the specification below.
| Config key | Type | Default | Description |
|-------------------|----------------------|------------------------------------------|-------------------------------------------------------------------------------------------------------------|
| `id` | `String` | (key) | The key of the object, provided here for the template engine |
| `enabled` | `Boolean` | `true` | Whether the profile is directly deployable, if disabled the profile can only be deployed via a `peerDeploy` |
| `path` | `String` | (current path) | Change to this root directory before deploying a profile |
| `script` | `Array` / `String` | | Script or other executable which will be run instead of any of the below processes |
| `repo` | `String` | `--repo=<REPO>` or `"origin"` | Which source repository to use when deploying, `--repo` overrides the setting if present |
| `branch` | `String` | `--branch=<BRANCH>` or `"master"` | The branch to use when deploying, can be a complex expression - see Branch Expressions |
| `title` | `String` | (key via _.startCase) | The human readable name of the deployment profile |
| `sort` | `Number` | `10` | Sort position if deploying multiple profiles (low-to-high) |
| `peerDeploy` | `Array` / `String` | `""` | Additional deployments implied if this profile is deployed |
| `peerDeny` | `Array` / `String` | `""` | Deployments that are not allowed alongside this profile - an error is raised if both are attempted at once |
| `processes` | `Number` | `1` | The number of PM2 processes to manage during a deployment |
| `env` | `Object` | `{}` | Environment variables to set when building (usually `NODE_ENV` needs to be set |
| `semver` | `Boolean` / `String` | `false` | Whether to commit a new version completion, ENUM: 'patch' / `true`, 'minor', 'major' |
| `semverPackage` | `Boolean` | `true` | Whether to also bump the new version in `package.json` |
| `pm2Name` | `String` | `"${profile.id}-${process.alpha}"` | Templatable name of the PM2 processes |
| `pm2Names` | `Array<String>` | (Computed from pm2Name if not specified) | Computed templatable names or manual overrides if needed |
| `pm2Args` | `Object<Array>` | `{}` | Specific PM2 arguments per computed instance name |
| `pm2Args.default` | `Array` | `['-e', (key)]` | Universal defaults for PM2 process arguments if no other key matches |
**Notes:**
* Profiles are always deployed according to `sort` order, even if specified by `peerDeploy`
* Setting `semver` requires write access as it will try to commit the new version based on the result of the deployment (cherry-picks and all)
* If `script` is specified it is deferred to instead of any other deploy process - no fetching, building or restarts are performed - only the script is run
* When using `script` only the `path` and `env` settings are processed - all other settings are ignored
Branch expressions
------------------
The `branch` property can be set to either:
* A branch name (e.g. 'master', 'dev')
* A tag (e.g. 'v1.2.3')
* A patch hash (e.g. '36d050e', 'f9748544f569e38646a10e8aeecdd0fa47bba0ac')
* One of the special expressions below
The following branch expressions are also supported, they take the form `TYPE ARG1=VAL1,ARG2=VAL2,ARG3=VAL3` (e.g. `branch: tag semver=v1.x.x,sort=desc`)
| Branch type | Arguments | Default | Description |
|-------------|-----------|----------|---------------------------------------------------------------------------|
| `tag` | `semver` | `"*"` | A semver expression to filter tags by, the first valid semver tag is used |
| | `sort` | `"desc"` | Whether to sort tags asending, decending before filtering |
String Templates
----------------
Some config options can be templated using [ES6 template syntax](https://github.com/MomsFriendlyDevCo/template).
The following variables are available when templating:
| Variable | Type | Description |
|------------------|----------|---------------------------------------------------------|
| `_` | `Object` | Lodash instance |
| `semver` | `Object` | Semver instance |
| `profile` | `Object` | Profile configuration object (see above for details) |
| `process` | `Object` | Details about the current proceses iteration |
| `process.offset` | `Number` | Offset (starting at zero) of the current iteration |
| `process.alpha` | `String` | Alphabetical offset of the process (e.g. 'a', 'b' etc.) |
| `process.name` | `String` | Computed process name |
Example config
==============
Project config
--------------
The following is a recommended setup in `package.json`:
```json
{
"name": "acme-project",
"version": "1.0.0",
"description": "Project that does stuff",
"main": "server/index.js",
"scripts": {
"build": "gulp build",
"dev": "gulp serve",
"deploy": "doop-deploy",
"deploy:pre": "gulp preDeploy",
"deploy:post": "gulp postDeploy",
"start": "node server"
}
}
```
Basic config
------------
The following is a standard deployment setup with a "Dev" and "Live" profile located in two different paths.
```javascript
# Within in a Doop `config/index.js` file:
var config = {
deploy: {
profiles: {
dev: {
title: 'Dev',
path: '/sites/dev.acme.com',
sort: 1,
processes: 2,
env: {'NODE_ENV': 'dev'},
pm2Name: 'dev-${process.alpha}',
pm2Args: {
default: [
'-e', 'dev',
'-o', 'port=${10000 + process.offset + 1}',
'-o', 'papertrail-program=${process.name}',
],
'dev-a': [
'-e', 'dev',
'-o', 'port=${10000 + process.offset + 1}',
'-o', 'papertrail-program=${process.name}',
'-o', 'cache.cleanAuto=true',
'-o', 'mongo.migration=true',
],
},
},
live: {
title: 'Live',
path: '/sites/acme.com',
sort: 2,
processes: 8,
env: {'NODE_ENV': 'production'},
pm2Name: 'live-${process.alpha}',
pm2Args: {
default: [
'-e', 'production',
'-o', 'port=${10100 + process.offset + 1}',
'-o', 'papertrail-program=${process.name}',
],
'live-a': [
'-e', 'production',
'-o', 'port=${10000 + process.offset + 1}',
'-o', 'papertrail-program=${process.name}',
'-o', 'cache.cleanAuto=true',
'-o', 'mongo.migration=true',
],
},
},
},
},
}
```
Chain deployments
-----------------
The following is an example where:
* Dev becomes the latest version (on `npm run deploy --dev`)
* Live becomes the Dev version on deploy (on `npm run deploy --live`)
* Fallback is always the previously deployed live version
* Any deployment to Dev is incremented as a patch on semver, if live is deployed it uses the latest semver
The config below should be spliced into the existing config profile:
```javascript
var config = {
deploy: {
profiles: {
dev: {
semver: 'patch', // Increment semver when deploying Dev
},
live: {
peerDeploy: ['dev', 'fallback'], // Imply other profiles must be updated first
},
fallback: {
enabled: false, // Disable direct deployment
},
},
},
};
```