feat: detect service connections and instance health

This commit is contained in:
Sérgio Ramos 2017-07-11 17:59:25 +01:00 committed by Judit Greskovits
parent 47e9982d7f
commit be7bb5f871
11 changed files with 577 additions and 302 deletions

View File

@ -152,12 +152,22 @@ enum InstanceStatus {
UNKNOWN
}
enum HealthyStatus {
HEALTHY
UNHEALTHY
MAINTENANCE
UNKNOWN
UNAVAILABLE
}
type Instance {
id: ID!
name: String!
machineId: ID!
status: InstanceStatus!
healthy: Boolean
healthy: HealthyStatus
watchers: [String]
jobs: [String]
}
type Datacenter {

View File

@ -3,7 +3,6 @@
exports[`renders <A /> correctly 1`] = `
.jleQxG {
background-color: transparent;
-webkit-text-decoration-skip: objects;
text-decoration-skip: objects;
}
@ -72,7 +71,6 @@ exports[`renders <Button /> correctly 1`] = `
line-height: 1.15;
margin: 0;
overflow: visible;
-webkit-text-transform: none;
text-transform: none;
-webkit-appearance: button;
-moz-appearance: button;
@ -374,7 +372,6 @@ exports[`renders <Select /> correctly 1`] = `
font-size: 100%;
line-height: 1.15;
margin: 0;
-webkit-text-transform: none;
text-transform: none;
}

View File

@ -23,7 +23,7 @@ const VAsync = require('vasync');
// local modules
const Transform = require('./transform');
const { DEPLOYMENT_GROUP, SERVICE, HASH } = require('../watch');
const { DEPLOYMENT_GROUP, SERVICE, HASH } = require('../watch/machines');
const NON_IMPORTABLE_STATES = [
@ -77,7 +77,7 @@ class Data extends EventEmitter {
this._db = new Penseur.Db(settings.name, settings.db);
this._dockerCompose = new DockerClient(settings.dockerComposeHost);
this._docker = new Dockerode(settings.docker);
this._watcher = null;
this._machines = null;
this._triton = null;
Triton.createClient({
@ -96,8 +96,8 @@ class Data extends EventEmitter {
});
}
setWatcher (watcher) {
this._watcher = watcher;
setMachinesWatcher (machinesWatcher) {
this._machines = machinesWatcher;
}
connect (cb) {
@ -1854,10 +1854,6 @@ class Data extends EventEmitter {
updateInstance (clientInstance, cb) {
const instance = Transform.toInstance(clientInstance);
if (typeof instance.healthy !== 'boolean') {
instance.healthy = null;
}
this._db.instances.update([instance], (err) => {
if (err) {
return cb(err);
@ -2073,11 +2069,11 @@ class Data extends EventEmitter {
}
getImportableDeploymentGroups (args, cb) {
if (!this._watcher) {
if (!this._machines) {
return cb(null, []);
}
const machines = this._watcher.getContainers();
const machines = this._machines.getContainers();
if (!Array.isArray(machines)) {
return cb(null, []);
@ -2123,12 +2119,12 @@ class Data extends EventEmitter {
importDeploymentGroup ({ deploymentGroupSlug }, cb) {
console.log(`-> import requested for ${deploymentGroupSlug}`);
if (!this._watcher) {
if (!this._machines) {
console.log('-> watcher not yet defined');
return cb(null, null);
}
const machines = this._watcher.getContainers();
const machines = this._machines.getContainers();
if (!Array.isArray(machines)) {
console.log('-> no machines found');

View File

@ -165,19 +165,22 @@ exports.fromInstance = function (instance) {
name: instance.name,
machineId: instance.machine_id,
status: instance.status,
ips: instance.ips,
healthy: instance.healthy
healthy: instance.healthy,
watchers: instance.watchers,
jobs: instance.jobs
};
};
exports.toInstance = function (clientInstance) {
return clean({
id: clientInstance.id,
name: clientInstance.name,
machine_id: clientInstance.machineId,
status: clientInstance.status,
ips: clientInstance.ips,
healthy: clientInstance.healthy
healthy: clientInstance.healthy,
watchers: clientInstance.watchers,
jobs: clientInstance.jobs
});
};

View File

@ -6,8 +6,8 @@ const Piloted = require('piloted');
const Data = require('./data');
const Pack = require('../package.json');
const Resolvers = require('./resolvers');
const Watch = require('./watch');
const WatchHealth = require('./watch/health');
const ContainerPilotWatcher = require('./watch/container-pilot');
const MachinesWatcher = require('./watch/machines');
const internals = {};
@ -24,7 +24,8 @@ module.exports = function (server, options, next) {
}
const data = new Data(options.data);
const watcher = new Watch(Object.assign(options.watch, {
const cpWatcher = new ContainerPilotWatcher(Object.assign(options.watch, { data }));
const machinesWatcher = new MachinesWatcher(Object.assign(options.watch, {
data
}));
@ -32,10 +33,9 @@ module.exports = function (server, options, next) {
// portal depends on watcher and vice-versa
// I'm sure there is a better way to organize this domains
// but this works for now
data.setWatcher(watcher);
data.setMachinesWatcher(machinesWatcher);
const healthWatcher = new WatchHealth(Object.assign(options.health, { data }));
healthWatcher.on('error', (err) => {
cpWatcher.on('error', (err) => {
server.log(['error'], err);
});
@ -51,8 +51,9 @@ module.exports = function (server, options, next) {
server.bind(data);
Piloted.on('refresh', internals.refresh(data));
watcher.poll();
healthWatcher.poll();
machinesWatcher.poll();
cpWatcher.poll();
server.register([
{

View File

@ -0,0 +1,429 @@
'use strict';
const Events = require('events');
const Flatten = require('lodash.flatten');
const Triton = require('triton');
const VAsync = require('vasync');
const Wreck = require('wreck');
const CIDRMatcher = require('cidr-matcher');
const ForceArray = require('force-array');
const Get = require('lodash.get');
const Uniq = require('lodash.uniq');
const Queue = require('./queue');
module.exports = class ContainerPilotWatcher extends Events {
constructor (options) {
super();
options = options || {};
// todo assert options
this._data = options.data;
this._frequency = options.frequency || 1000;
Triton.createClient({
profile: {
url: options.url || process.env.SDC_URL,
account: options.account || process.env.SDC_ACCOUNT,
keyId: options.keyId || process.env.SDC_KEY_ID
}
}, (err, client) => {
if (err) {
this.emit('error', err);
return;
}
this._triton = client.cloudapi;
});
}
poll () {
if (this._timeoutId) {
return;
}
this._timeoutId = setTimeout(() => {
this.check((err) => {
if (err) {
this.emit('error', err);
}
delete this._timeoutId;
this.poll();
});
}, this._frequency);
}
_getDeploymentGroups (cb) {
const getInstances = (service, next) => {
service.instances({}, (err, instances) => {
if (err) {
return next(err);
}
next(null, Object.assign({}, service, {
instances
}));
});
};
const getServices = (deploymentGroup, next) => {
deploymentGroup.services({}, (err, services) => {
if (err) {
return next(err);
}
VAsync.forEachParallel({
inputs: services,
func: getInstances
}, (err, result) => {
if (err) {
return next(err);
}
next(null, Object.assign({}, deploymentGroup, {
services: result.successes
}));
});
});
};
const handleDeploymentGroups = (err, deploymentGroups) => {
if (err) {
return cb(err);
}
VAsync.forEachParallel({
inputs: deploymentGroups,
func: getServices
}, (err, result) => {
cb(err, result.successes);
});
};
const getDeploymentGroups = (err, portal) => {
if (err) {
return cb(err);
}
portal.deploymentGroups({}, handleDeploymentGroups);
};
this._data.getPortal({}, getDeploymentGroups);
}
_getNetworks (networkIds = [], cb) {
VAsync.forEachParallel({
inputs: networkIds,
func: (id, next) => {
this._triton.getNetwork(id, next);
}
}, (err, results) => {
cb(err, ForceArray((results || {}).successes));
});
}
_getPublicIps (machine, cb) {
this._getNetworks(machine.networks, (err, networks) => {
if (err) {
return cb(err);
}
const privateNetworkSubnets = networks
.filter((network) => {
return !network['public'];
})
.map((network) => {
return network.subnet;
})
.filter(Boolean);
const cidr = new CIDRMatcher(privateNetworkSubnets);
const nonPrivateIps = machine.ips.filter((ip) => {
return !cidr.contains(ip);
});
cb(null, nonPrivateIps);
});
}
_fetchInstanceStatus (instance, cb) {
const { machineId } = instance;
const handleStatuses = (err, results) => {
if (err) {
this.emit('error', err);
return cb();
}
const statuses = ForceArray((results || {}).successes);
const status = statuses.filter(Boolean).shift();
if (!status) {
return cb(null, instance);
}
const services = ForceArray(status.Services).filter(({ Name }) => {
return Name !== 'containerpilot';
});
cb(null, Object.assign({}, instance, {
cp: Object.assign({}, status, {
Services: services
})
}));
};
const fetchStatus = (ip, next) => {
Wreck.get(`http://${ip}:9090/status`, {
timeout: 1000 // 1s
}, (err, res, payload) => {
if (err) {
this.emit('error', err);
return next();
}
if (Buffer.isBuffer(payload)) {
payload = payload.toString();
}
try {
const status = JSON.parse(payload);
next(null, status);
} catch (err) {
next();
}
});
};
const handlePublicIps = (err, ips) => {
if (err) {
this.emit('error', err);
return cb();
}
VAsync.forEachParallel({
inputs: ips,
func: fetchStatus
}, handleStatuses);
};
this._triton.getMachine(machineId, (err, machine) => {
if (err) {
this.emit('error', err);
return cb();
}
this._getPublicIps(machine, handlePublicIps);
});
}
_fetchServiceStatus (service, cb) {
VAsync.forEachParallel({
inputs: service.instances,
func: (instance, next) => {
this._fetchInstanceStatus(instance, next);
}
}, (err, results) => {
if (err) {
this.emit('error', err);
}
cb(null, Object.assign({}, service, {
instances: ForceArray((results || {}).successes)
}));
});
}
_fetchDeploymentGroupStatus (dg, cb) {
VAsync.forEachParallel({
inputs: dg.services,
func: (service, next) => {
this._fetchServiceStatus(service, next);
}
}, (err, results) => {
if (err) {
this.emit('error', err);
}
cb(null, Object.assign({}, dg, {
services: ForceArray((results || {}).successes)
}));
});
}
_saveInstance ({ id, healthy, watchers, jobs }, cb) {
if (!id) {
return cb();
}
this._data.updateInstance({
id,
healthy,
watchers,
jobs
}, cb);
}
_saveService ({ id, instances, connections }, cb) {
if (!id) {
return cb();
}
VAsync.forEachParallel({
inputs: instances,
func: (instance, next) => {
this._saveInstance(instance, next);
}
}, (err) => {
if (err) {
return cb(err);
}
this._data.updateService({
id,
connections
}, cb);
});
}
_saveDeploymentGroup (dg, cb) {
VAsync.forEachParallel({
inputs: dg.services,
func: (service, next) => {
this._saveService(service, next);
}
}, cb);
}
check (cb) {
if (!this._triton) {
return cb();
}
const resolveServiceConnections = ({ services, service }) => {
const watches = Uniq(
Flatten(ForceArray(service.instances).map(({ watches }) => { return watches; }))
);
return watches
.map((jobName) => {
return services.reduce((serviceId, service) => {
if (serviceId) {
return serviceId;
}
const thisServiceJobs = Uniq(
Flatten(ForceArray(service.instances).map(({ jobs }) => { return jobs; }))
);
if (thisServiceJobs.indexOf(jobName) >= 0) {
return service.id;
}
return serviceId;
}, null);
})
.filter(Boolean);
};
const resolveInstanceHealth = ({ name }, instance) => {
if (!instance) {
return;
}
if (!instance.cp) {
return 'UNAVAILABLE';
}
const jobNames = Get(instance, 'cp.Services');
const serviceJobs = jobNames.filter(({ Name }) => { return Name === name; });
if (serviceJobs.length) {
return serviceJobs.shift().Status.toUpperCase();
}
const almostJobNameRegexp = new RegExp(`${name}-.*`);
const almostServiceJobs = jobNames.filter((n) => {
return almostJobNameRegexp.test(n);
});
if (almostServiceJobs.length) {
return almostServiceJobs.shift().Status.toUpperCase();
}
return 'UNKNOWN';
};
const handleStatuses = (err, results) => {
if (err) {
this.emit('error', err);
}
const dgs = ForceArray((results || {}).successes)
.map((dg) => {
return Object.assign({}, dg, {
services: ForceArray(dg.services).map((service) => {
return Object.assign({}, service, {
instances: ForceArray(service.instances).map((instance) => {
return Object.assign({}, instance, {
healthy: resolveInstanceHealth(service, instance),
jobs: Get(instance, 'cp.Services', []).map(
({ Name }) => { return Name; }
),
watches: Get(instance, 'cp.Watches', [])
});
})
});
})
});
})
.map((dg) => {
return Object.assign({}, dg, {
services: ForceArray(dg.services).map((service) => {
return Object.assign({}, service, {
connections: resolveServiceConnections({
services: dg.services,
service
})
});
})
});
});
VAsync.forEachParallel({
inputs: dgs,
func: (dg, next) => {
Queue(dg.id, () => {
return new Promise((resolve) => {
this._saveDeploymentGroup(dg, (err) => {
resolve();
next(err);
});
});
});
}
}, cb);
};
const fetchStatuses = (err, dgs) => {
if (err) {
this.emit('error', err);
return cb();
}
VAsync.forEachParallel({
inputs: dgs,
func: (dg, next) => {
this._fetchDeploymentGroupStatus(dg, next);
}
}, handleStatuses);
};
this._getDeploymentGroups(fetchStatuses);
}
};

View File

@ -1,159 +0,0 @@
'use strict';
const Events = require('events');
const Consulite = require('consulite');
const VAsync = require('vasync');
module.exports = class Health extends Events {
constructor (options) {
super();
options = options || {};
// todo assert options
this._data = options.data;
this._frequency = options.frequency || 2000;
}
poll () {
if (this._timeoutId) {
return;
}
this._timeoutId = setTimeout(() => {
this.check((err) => {
if (err) {
this.emit('error', err);
}
delete this._timeoutId;
this.poll();
});
}, this._frequency);
}
// check() follows these steps:
// 1. grab all services from the db
// 2. filter to only the unique consul hosts
// 3. grab all instances from the db
// 4. query each consul host for service names
// 5. query the respective consul host for service health
// 6. match node to instance using the address and update `healthy` if the status changes
check (cb) {
// 1. grab all services from the db
this._data.services.all((err, services) => {
if (err) {
return cb(err);
}
if (!services) {
return cb();
}
// 2. filter to only the unique consul hosts
const consulHosts = [];
services.forEach((service) => {
if (!service.consul) {
return;
}
if (consulHosts.indexOf(service.consul) === -1) {
consulHosts.push(service.consul);
}
});
// 3. grab all instances from the db
this._data.instances.all((err, instances) => {
if (err) {
return cb(err);
}
// we match consul nodes using the IP address, remove those that won't match
instances = instances.filter((instance) => {
return instance.ips && instance.ips.length;
});
// include consul host on each instance
// helps to identify the correct instance when comparing addresses
instances.forEach((instance) => {
const service = services.find((service) => {
return (service.instance_ids.indexOf(instance.id) !== -1);
});
if (service) {
instance.consul = service.consul;
}
});
VAsync.forEachParallel({
inputs: consulHosts,
// 4. query each consul host for service names
// 5. query the respective consul host for service health
func: this._checkServicesHealth
}, (err, results) => {
if (err) {
return cb(err);
}
// 6. match node to instance using the address and update `healthy` if the status changes
this._findAndUpdateInstances(instances, results.successes, cb);
});
});
});
}
_checkServicesHealth (consul, cb) {
const consulite = new Consulite({ consul });
consulite.getServiceNames((err, consulServices) => {
if (err) {
return cb(err);
}
// filter out telemetry services
consulServices = consulServices.filter((consulService) => {
return consulService !== 'containerpilot';
});
VAsync.forEachParallel({
inputs: consulServices,
func: (consulService, next) => {
consulite.getServiceStatus(consulService, (err, nodes) => {
if (err) {
return next(err);
}
nodes = nodes.map((node) => {
node.consul = consul;
return node;
});
next(null, nodes);
});
}
}, cb);
});
}
_findAndUpdateInstances (instances, nodes, cb) {
VAsync.forEachPipeline({
inputs: nodes,
func: (node, next) => {
const healthy = (node.status === 'passing');
const instance = instances.find((instance) => {
return (instance.ips.indexOf(node.address) !== -1) &&
(instance.consul === node.consul) &&
(instance.healthy !== healthy);
});
if (!instance) {
return next();
}
this._data.updateInstance({ id: instance.id, healthy }, next);
}
}, cb);
}
};

View File

@ -1,13 +1,13 @@
'use strict';
// const Assert = require('assert');
const Throat = require('throat');
const TritonWatch = require('triton-watch');
const Get = require('lodash.get');
const Find = require('lodash.find');
const Util = require('util');
const ForceArray = require('force-array');
const VAsync = require('vasync');
const Queue = require('./queue');
const DEPLOYMENT_GROUP = 'docker:label:com.docker.compose.project';
@ -52,7 +52,7 @@ const SERVICE_DELETING_STATUSES = [
'UNKNOWN'
];
module.exports = class Watcher {
module.exports = class MachineWatcher {
constructor (options) {
options = options || {};
@ -71,7 +71,6 @@ module.exports = class Watcher {
}
});
this._queues = {};
this._waitingForPlan = [];
this._isTritonWatchPolling = false;
@ -112,16 +111,6 @@ module.exports = class Watcher {
return this._tritonWatch.getContainers();
}
pushToQueue (deploymentGroupId, cb) {
if (this._queues[deploymentGroupId]) {
this._queues[deploymentGroupId](cb);
return;
}
this._queues[deploymentGroupId] = Throat(1);
this._queues[deploymentGroupId](cb);
}
getDeploymentGroup (name, cb) {
this._data.getDeploymentGroup({ name }, (err, deploymentGroup) => {
if (err) {
@ -178,7 +167,6 @@ module.exports = class Watcher {
const instance = {
name: machine.name,
status,
ips: machine.ips,
machineId: machine.id
};
@ -203,7 +191,6 @@ module.exports = class Watcher {
const updatedInstance = {
id: instance.id,
ips: machine.ips,
status: (machine.state || '').toUpperCase()
};
@ -630,7 +617,7 @@ module.exports = class Watcher {
return;
}
this.pushToQueue(deploymentGroup.id, () => {
Queue(deploymentGroup.id, () => {
return new Promise((resolve) => {
assertService(deploymentGroup, (err) => {
if (err) {

View File

@ -0,0 +1,18 @@
'use strict';
const Throat = require('throat');
module.exports = (() => {
const _queues = {};
// pushToQueue
return (id, cb) => {
if (_queues[id]) {
_queues[id](cb);
return;
}
_queues[id] = Throat(1);
_queues[id](cb);
};
})();

View File

@ -27,11 +27,12 @@
"hapi-swagger": "^7.7.0",
"inert": "^4.2.0",
"lab": "^14.0.1",
"vision": "^4.1.1"
"vision": "^4.1.1",
"wreck": "^12.2.2"
},
"dependencies": {
"boom": "^5.1.0",
"consulite": "^2.0.0",
"cidr-matcher": "^1.0.5",
"docker-compose-client": "^1.0.8",
"dockerode": "^2.5.0",
"graphi": "^2.2.1",
@ -40,6 +41,7 @@
"lodash.find": "^4.6.0",
"lodash.flatten": "^4.4.0",
"lodash.get": "^4.4.2",
"lodash.uniq": "^4.5.0",
"lodash.uniqby": "^4.7.0",
"param-case": "^2.1.1",
"penseur": "^7.12.3",

185
yarn.lock
View File

@ -198,7 +198,7 @@ ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
ansi-styles@^3.0.0:
ansi-styles@^3.0.0, ansi-styles@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.1.0.tgz#09c202d5c917ec23188caa5c9cb9179cd9547750"
dependencies:
@ -220,8 +220,8 @@ anymatch@^1.3.0:
micromatch "^2.1.5"
apollo-client@^1.4.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-1.7.0.tgz#3d6fdf6ead0a07d3e02d32d9191f26cbcfb6e4f6"
version "1.8.0"
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-1.8.0.tgz#3b5d1976a06a0f82b2fc66fe71754868193dadb9"
dependencies:
graphql "^0.10.0"
graphql-anywhere "^3.0.1"
@ -456,8 +456,8 @@ arrify@^1.0.0, arrify@^1.0.1:
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
asap@^2.0.0, asap@~2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f"
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
asn1.js@^4.0.0:
version "4.9.1"
@ -497,10 +497,6 @@ ast-types@0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.0.tgz#c8721c8747ae4d5b29b929e99c5317b4e8745623"
ast-types@0.9.6:
version "0.9.6"
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9"
async-each@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
@ -543,14 +539,14 @@ autoprefixer@^6.0.0:
postcss-value-parser "^3.2.3"
ava-init@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/ava-init/-/ava-init-0.2.0.tgz#9304c8b4c357d66e3dfdae1fbff47b1199d5c55d"
version "0.2.1"
resolved "https://registry.yarnpkg.com/ava-init/-/ava-init-0.2.1.tgz#75ac4c8553326290d2866e63b62fa7035684bd58"
dependencies:
arr-exclude "^1.0.0"
execa "^0.5.0"
execa "^0.7.0"
has-yarn "^1.0.0"
read-pkg-up "^2.0.0"
write-pkg "^2.0.0"
write-pkg "^3.1.0"
ava@0.19.1:
version "0.19.1"
@ -1385,8 +1381,8 @@ backoff@2.4.1, backoff@^2.4.1:
precond "0.2"
bail@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.1.tgz#912579de8b391aadf3c5fdf4cd2a0fc225df3bc2"
version "1.0.2"
resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.2.tgz#f7d6c1731630a9f9f0d4d35ed1f962e2074a1764"
balanced-match@^0.4.0:
version "0.4.2"
@ -1766,12 +1762,12 @@ camelcase@^4.0.0, camelcase@^4.1.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
caniuse-db@^1.0.30000187, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
version "1.0.30000698"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000698.tgz#623a2de3458ceca379846a8f170e7b1771c7c3a3"
version "1.0.30000699"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000699.tgz#5af491ab1c777561a32b43fe253d6a7071ccf979"
caniuse-lite@^1.0.30000684:
version "1.0.30000698"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000698.tgz#8102e8978b1f36962f2a102432e4bf4eac7b6cbe"
version "1.0.30000699"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000699.tgz#2a187b737edaa9ebedbbb56edcb53e994eceda0c"
capture-stack-trace@^1.0.0:
version "1.0.0"
@ -1824,6 +1820,14 @@ chalk@^0.4.0:
has-color "~0.1.0"
strip-ansi "~0.1.0"
chalk@^2.0.0, chalk@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.0.1.tgz#dbec49436d2ae15f536114e76d14656cdbc0f44d"
dependencies:
ansi-styles "^3.1.0"
escape-string-regexp "^1.0.5"
supports-color "^4.0.0"
chalk@~0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174"
@ -1835,16 +1839,16 @@ chalk@~0.5.1:
supports-color "^0.2.0"
character-entities-html4@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.0.tgz#1ab08551d3ce1fa1df08d00fb9ca1defb147a06c"
version "1.1.1"
resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.1.tgz#359a2a4a0f7e29d3dc2ac99bdbe21ee39438ea50"
character-entities-legacy@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.0.tgz#b18aad98f6b7bcc646c1e4c81f9f1956376a561a"
character-entities@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.0.tgz#a683e2cf75dbe8b171963531364e58e18a1b155f"
version "1.2.1"
resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.1.tgz#f76871be5ef66ddb7f8f8e3478ecc374c27d6dca"
character-reference-invalid@^1.0.0:
version "1.1.0"
@ -1875,6 +1879,12 @@ ci-info@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.0.0.tgz#dc5285f2b4e251821683681c381c3388f46ec534"
cidr-matcher@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/cidr-matcher/-/cidr-matcher-1.0.5.tgz#1805e49751be8fc8279a73355574e31c9f49666b"
dependencies:
ip "^1.0.2"
cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
@ -2172,13 +2182,6 @@ consulite@1.6.x:
or-promise "1.x.x"
wreck "10.x.x"
consulite@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/consulite/-/consulite-2.0.0.tgz#4d35228d24f70a8fb01fdb8ed921d4e54d8bbb16"
dependencies:
or-promise "1.x.x"
wreck "12.x.x"
contains-path@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
@ -2518,8 +2521,8 @@ cryptiles@3.x.x:
boom "5.x.x"
crypto-browserify@^3.11.0:
version "3.11.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.0.tgz#3652a0906ab9b2a7e0c3ce66a408e957a2485522"
version "3.11.1"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.1.tgz#948945efc6757a400d6e5e5af47194d10064279f"
dependencies:
browserify-cipher "^1.0.0"
browserify-sign "^4.0.0"
@ -2819,8 +2822,8 @@ dale-chall-formula@^1.0.0:
resolved "https://registry.yarnpkg.com/dale-chall-formula/-/dale-chall-formula-1.0.1.tgz#de3a82d485c7ab7b64c2c9c046f7f3cb75e177fd"
dale-chall@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dale-chall/-/dale-chall-1.0.0.tgz#7f6e7ca357bdb3552c1ddb3c750bd9300cb895b7"
version "1.0.1"
resolved "https://registry.yarnpkg.com/dale-chall/-/dale-chall-1.0.1.tgz#e2dc01ba31784261520dc94f3e24367b437a677d"
damerau-levenshtein@^1.0.0:
version "1.0.4"
@ -3231,8 +3234,8 @@ es-to-primitive@^1.1.1:
is-symbol "^1.0.1"
es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14:
version "0.10.23"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.23.tgz#7578b51be974207a5487821b56538c224e4e7b38"
version "0.10.24"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.24.tgz#a55877c9924bc0c8d9bd3c2cbe17495ac1709b14"
dependencies:
es6-iterator "2"
es6-symbol "~3.1"
@ -3346,8 +3349,8 @@ eslint-module-utils@^2.1.1:
pkg-dir "^1.0.0"
eslint-plugin-flowtype@^2.34.0:
version "2.34.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.34.1.tgz#ea109175645b05d37baeac53b9b65066d79b9446"
version "2.35.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.35.0.tgz#d17494f0ae8b727c632d8b9d4b4a848e7e0c04af"
dependencies:
lodash "^4.15.0"
@ -3507,9 +3510,9 @@ esprima@^2.6.0, esprima@~2.7.1:
version "2.7.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
esprima@^3.1.1, esprima@~3.1.0:
version "3.1.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
esprima@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
espurify@^1.6.0:
version "1.7.0"
@ -3717,11 +3720,11 @@ extsprintf@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550"
extsprintf@1.2.0, extsprintf@^1.2.0:
extsprintf@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.2.0.tgz#5ad946c22f5b32ba7f8cd7426711c6e8a3fc2529"
extsprintf@1.3.0:
extsprintf@1.3.0, extsprintf@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
@ -3793,8 +3796,8 @@ fill-range@^2.1.0:
repeat-string "^1.5.2"
fillers@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fillers/-/fillers-1.1.0.tgz#0fa81fb2af369a793f2fafc08b67e60a299fc9f9"
version "1.1.1"
resolved "https://registry.yarnpkg.com/fillers/-/fillers-1.1.1.tgz#9d1a8f0150d47f78a898de4cd43cf079d417148e"
finalhandler@~1.0.3:
version "1.0.3"
@ -4358,15 +4361,15 @@ has-own-prop@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-own-prop/-/has-own-prop-1.0.0.tgz#7b5e04505ee55896ba32e5018098b481a2f8a0e5"
has-symbol-support-x@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.3.0.tgz#588bd6927eaa0e296afae24160659167fc2be4f8"
has-symbol-support-x@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.0.tgz#442d89b1d0ac6cf5ff2f7b916ee539869b93a256"
has-to-string-tag-x@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.3.0.tgz#78e3d98c3c0ec9413e970eb8d766249a1e13058f"
version "1.4.0"
resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.0.tgz#49d7bcde85c2409be38ac327e3e119a451657c7b"
dependencies:
has-symbol-support-x "^1.3.0"
has-symbol-support-x "^1.4.0"
has-unicode@^2.0.0:
version "2.0.1"
@ -4629,11 +4632,11 @@ inquirer@^0.12.0:
through "^2.3.6"
inquirer@^3.0.2, inquirer@^3.0.6, inquirer@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.1.1.tgz#87621c4fba4072f48a8dd71c9f9df6f100b2d534"
version "3.2.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.2.0.tgz#45b44c2160c729d7578c54060b3eed94487bb42b"
dependencies:
ansi-escapes "^2.0.0"
chalk "^1.0.0"
chalk "^2.0.0"
cli-cursor "^2.1.0"
cli-width "^2.0.0"
external-editor "^2.0.4"
@ -4643,8 +4646,8 @@ inquirer@^3.0.2, inquirer@^3.0.6, inquirer@^3.1.0:
run-async "^2.2.0"
rx-lite "^4.0.8"
rx-lite-aggregates "^4.0.8"
string-width "^2.0.0"
strip-ansi "^3.0.0"
string-width "^2.1.0"
strip-ansi "^4.0.0"
through "^2.3.6"
insync@2.x.x:
@ -4665,6 +4668,10 @@ invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
ip@^1.0.2:
version "1.1.5"
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
ipaddr.js@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec"
@ -5206,8 +5213,8 @@ jest-snapshot@^20.0.3:
pretty-format "^20.0.3"
jest-styled-components@^3.0.2:
version "3.3.1"
resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-3.3.1.tgz#8803b9577105d11ea8681397a06f154824c8f782"
version "3.3.2"
resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-3.3.2.tgz#cf22c7489d417cbb19d878cdc606cec83538f4f7"
dependencies:
css "^2.2.1"
@ -5315,11 +5322,11 @@ js-tokens@^3.0.0:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
js-yaml@^3.2.7, js-yaml@^3.4.3, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.6.0, js-yaml@^3.8.2, js-yaml@^3.8.4:
version "3.8.4"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.4.tgz#520b4564f86573ba96662af85a8cafa7b4b5a6f6"
version "3.9.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.0.tgz#4ffbbf25c2ac963b8299dc74da7e3740de1c18ce"
dependencies:
argparse "^1.0.7"
esprima "^3.1.1"
esprima "^4.0.0"
js-yaml@~3.7.0:
version "3.7.0"
@ -6067,8 +6074,8 @@ miller-rabin@^4.0.0:
brorand "^1.0.1"
mime-db@1.x.x:
version "1.28.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.28.0.tgz#fedd349be06d2865b7fc57d837c6de4f17d7ac3c"
version "1.29.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.29.0.tgz#48d26d235589651704ac5916ca06001914266878"
mime-db@~1.27.0:
version "1.27.0"
@ -7056,8 +7063,8 @@ pretty-ms@^2.0.0:
plur "^1.0.0"
primer-support@*:
version "4.0.6"
resolved "https://registry.yarnpkg.com/primer-support/-/primer-support-4.0.6.tgz#80f3bf1ceb63fda1cf2fdbcea09b632c812e1d54"
version "4.0.7"
resolved "https://registry.yarnpkg.com/primer-support/-/primer-support-4.0.7.tgz#c9c433c5a597a78226870ea3d18c4bf0e96eeab4"
primer-utilities@^3.0.0:
version "3.5.0"
@ -7552,7 +7559,7 @@ readline2@^1.0.1:
is-fullwidth-code-point "^1.0.0"
mute-stream "0.0.5"
recast@0.11.12:
recast@0.11.12, recast@^0.11.5:
version "0.11.12"
resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.12.tgz#a79e4d3f82d5d72a82ee177aeaa791e793bbe5d6"
dependencies:
@ -7561,15 +7568,6 @@ recast@0.11.12:
private "~0.1.5"
source-map "~0.5.0"
recast@^0.11.5:
version "0.11.23"
resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3"
dependencies:
ast-types "0.9.6"
esprima "~3.1.0"
private "~0.1.5"
source-map "~0.5.0"
rechoir@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
@ -8295,7 +8293,7 @@ sntp@1.x.x:
dependencies:
hoek "2.x.x"
sort-keys@^1.1.1, sort-keys@^1.1.2:
sort-keys@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
dependencies:
@ -8541,7 +8539,7 @@ string-width@^1.0.0, string-width@^1.0.1, string-width@^1.0.2:
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"
string-width@^2.0.0:
string-width@^2.0.0, string-width@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.0.tgz#030664561fc146c9423ec7d978fe2457437fe6d0"
dependencies:
@ -8718,12 +8716,12 @@ stylelint-selector-no-utility@^1.5.0:
stylelint "^7.0.0"
stylelint@^7.0.0, stylelint@^7.0.3, stylelint@^7.11.1:
version "7.12.0"
resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-7.12.0.tgz#bf302c265d7c2d6fe79b154a9fd873a80f8b4aa4"
version "7.13.0"
resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-7.13.0.tgz#111f97b6da72e775c80800d6bb6f5f869997785d"
dependencies:
autoprefixer "^6.0.0"
balanced-match "^0.4.0"
chalk "^1.1.1"
chalk "^2.0.1"
colorguard "^1.2.0"
cosmiconfig "^2.1.1"
debug "^2.6.0"
@ -8795,9 +8793,9 @@ supports-color@^3.1.0, supports-color@^3.1.2, supports-color@^3.2.3:
dependencies:
has-flag "^1.0.0"
supports-color@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.1.0.tgz#92cc14bb3dad8928ca5656c33e19a19f20af5c7a"
supports-color@^4.0.0, supports-color@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.0.tgz#ad986dc7eb2315d009b4d77c8169c2231a684037"
dependencies:
has-flag "^2.0.0"
@ -9217,8 +9215,8 @@ triton@5.2.x, triton@^5.2.0:
wordwrap "1.0.0"
trough@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.0.tgz#6bdedfe7f2aa49a6f3c432257687555957f342fd"
version "1.0.1"
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.1.tgz#a9fd8b0394b0ae8fff82e0633a0a36ccad5b5f86"
try-catch@^1.0.0, try-catch@~1.0.0:
version "1.0.0"
@ -9610,8 +9608,8 @@ wcwidth@^1.0.0:
defaults "^1.0.3"
weasels@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/weasels/-/weasels-1.1.0.tgz#f2335da6ad8630772bc515549d010bc0415192cb"
version "1.1.1"
resolved "https://registry.yarnpkg.com/weasels/-/weasels-1.1.1.tgz#3f3c72ac0a8ea19f5714a814c9101e0740857b25"
webpack-sources@^0.2.3:
version "0.2.3"
@ -9721,7 +9719,7 @@ wreck@10.x.x:
boom "4.x.x"
hoek "4.x.x"
wreck@12.x.x:
wreck@12.x.x, wreck@^12.2.2:
version "12.2.2"
resolved "https://registry.yarnpkg.com/wreck/-/wreck-12.2.2.tgz#e21823d34c36d672004eefa347ae8c4f6050e3db"
dependencies:
@ -9748,7 +9746,7 @@ write-file-stdout@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/write-file-stdout/-/write-file-stdout-0.0.2.tgz#c252d7c7c5b1b402897630e3453c7bfe690d9ca1"
write-json-file@^2.0.0, write-json-file@^2.1.0, write-json-file@^2.2.0:
write-json-file@^2.1.0, write-json-file@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.2.0.tgz#51862506bbb3b619eefab7859f1fd6c6d0530876"
dependencies:
@ -9759,14 +9757,7 @@ write-json-file@^2.0.0, write-json-file@^2.1.0, write-json-file@^2.2.0:
sort-keys "^1.1.1"
write-file-atomic "^2.0.0"
write-pkg@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-2.1.0.tgz#353aa44c39c48c21440f5c08ce6abd46141c9c08"
dependencies:
sort-keys "^1.1.2"
write-json-file "^2.0.0"
write-pkg@^3.0.1:
write-pkg@^3.0.1, write-pkg@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-3.1.0.tgz#030a9994cc9993d25b4e75a9f1a1923607291ce9"
dependencies: