diff --git a/.yarnclean b/.yarnclean new file mode 100644 index 00000000..25bdd148 --- /dev/null +++ b/.yarnclean @@ -0,0 +1,42 @@ +# test directories +__tests__ +test +tests +powered-test + +# asset directories +docs +doc +website +images +assets + +# examples +example +examples + +# code coverage directories +coverage +.nyc_output + +# build scripts +Makefile +Gulpfile.js +Gruntfile.js + +# configs +.tern-project +.gitattributes +.editorconfig +.*ignore +.eslintrc +.jshintrc +.flowconfig +.documentup.json +.yarn-metadata.json +.*.yml +*.yml + +# misc +*.gz +*.md diff --git a/packages/cp-gql-mock-server/package.json b/packages/cp-gql-mock-server/package.json index eaf650db..a20586eb 100644 --- a/packages/cp-gql-mock-server/package.json +++ b/packages/cp-gql-mock-server/package.json @@ -15,9 +15,7 @@ "good": "^7.2.0", "good-console": "^6.4.0", "good-squeeze": "^5.0.2", - "graphql": "^0.9.6", - "graphql-server-hapi": "^0.7.2", - "graphql-tools": "^0.11.0", + "graphi": "^2.0.0", "hapi": "^16.1.1", "joi": "^10.5.0", "joyent-cp-gql-schema": "^1.0.4" diff --git a/packages/cp-gql-mock-server/src/index.js b/packages/cp-gql-mock-server/src/index.js index 17d4d031..805a4483 100644 --- a/packages/cp-gql-mock-server/src/index.js +++ b/packages/cp-gql-mock-server/src/index.js @@ -1,6 +1,5 @@ const schema = require('joyent-cp-gql-schema'); -const { graphqlHapi, graphiqlHapi } = require('graphql-server-hapi'); -const { makeExecutableSchema } = require('graphql-tools'); +const graphi = require('graphi'); const Good = require('good'); const Hapi = require('hapi'); const resolvers = require('./resolvers'); @@ -45,27 +44,12 @@ server.register( } }, { - register: graphiqlHapi, + register: graphi, options: { - path: '/graphiql', - graphiqlOptions: { - endpointURL: '/graphql' - } - } - }, - { - register: graphqlHapi, - options: { - path: '/graphql', - graphqlOptions: { - schema: makeExecutableSchema({ - typeDefs: schema.sync(), - resolvers - }) - }, - route: { - cors: true - } + graphqlPath: '/graphql', + graphiqlPath: '/graphiql', + schema, + resolvers } } ], diff --git a/packages/cp-gql-mock-server/yarn.lock b/packages/cp-gql-mock-server/yarn.lock index 0cb778dc..4efd2570 100644 --- a/packages/cp-gql-mock-server/yarn.lock +++ b/packages/cp-gql-mock-server/yarn.lock @@ -2,60 +2,10 @@ # yarn lockfile v1 -"@types/boom@*": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@types/boom/-/boom-4.3.2.tgz#8f40fb142322958ff1bb520c2d8d20cf1dd3d468" - -"@types/catbox@*": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@types/catbox/-/catbox-7.1.0.tgz#adb0962f1cd9ba14d15efc0de91503364845e5f6" - dependencies: - "@types/boom" "*" - "@types/graphql@^0.9.0": version "0.9.1" resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.1.tgz#b04ebe84bc997cc60dbea2ed4d0d4342c737f99d" -"@types/hapi@^16.0.0": - version "16.1.2" - resolved "https://registry.yarnpkg.com/@types/hapi/-/hapi-16.1.2.tgz#e782b399a11ef8934c3de3ab07aae9fb53cb0968" - dependencies: - "@types/boom" "*" - "@types/catbox" "*" - "@types/joi" "*" - "@types/mimos" "*" - "@types/node" "*" - "@types/podium" "*" - "@types/shot" "*" - -"@types/joi@*": - version "10.3.2" - resolved "https://registry.yarnpkg.com/@types/joi/-/joi-10.3.2.tgz#bc4ce6577c294710663d347ebe59087f375075a5" - -"@types/mime-db@*": - version "1.27.0" - resolved "https://registry.yarnpkg.com/@types/mime-db/-/mime-db-1.27.0.tgz#9bc014a1fd1fdf47649c1a54c6dd7966b8284792" - -"@types/mimos@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/mimos/-/mimos-3.0.0.tgz#20356e7381e9529c2a7e764c798be5a48cdeff96" - dependencies: - "@types/mime-db" "*" - -"@types/node@*": - version "7.0.22" - resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.22.tgz#4593f4d828bdd612929478ea40c67b4f403ca255" - -"@types/podium@*": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/podium/-/podium-1.0.0.tgz#bfaa2151be2b1d6109cc69f7faa9dac2cba3bb20" - -"@types/shot@*": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@types/shot/-/shot-3.4.0.tgz#459477c5187d3ebd303660ab099e7e9e0f3b656f" - dependencies: - "@types/node" "*" - abbrev@1: version "1.1.0" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" @@ -229,7 +179,7 @@ boom@2.x.x: dependencies: hoek "2.x.x" -boom@4.x.x, boom@^4.3.1: +boom@4.x.x: version "4.3.1" resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" dependencies: @@ -439,10 +389,6 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" -deprecated-decorator@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37" - doctrine@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" @@ -886,38 +832,26 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" -graphql-server-core@^0.7.0: +graphi@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/graphi/-/graphi-2.2.1.tgz#234a4752e09accd182e5b6cb93d59c75535702d8" + dependencies: + boom "4.x.x" + graphql "0.9.x" + graphql-server-core "0.7.x" + graphql-server-module-graphiql "0.7.x" + +graphql-server-core@0.7.x: version "0.7.0" resolved "https://registry.yarnpkg.com/graphql-server-core/-/graphql-server-core-0.7.0.tgz#a658b3b0704f8a19b24254b64bef6b3d0ddce8ba" optionalDependencies: "@types/graphql" "^0.9.0" -graphql-server-hapi@^0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/graphql-server-hapi/-/graphql-server-hapi-0.7.2.tgz#2fa55b8a000952f125af038404563e72d0ba05ab" - dependencies: - boom "^4.3.1" - graphql-server-core "^0.7.0" - graphql-server-module-graphiql "^0.7.2" - optionalDependencies: - "@types/graphql" "^0.9.0" - "@types/hapi" "^16.0.0" - -graphql-server-module-graphiql@^0.7.2: +graphql-server-module-graphiql@0.7.x: version "0.7.2" resolved "https://registry.yarnpkg.com/graphql-server-module-graphiql/-/graphql-server-module-graphiql-0.7.2.tgz#aa1f2a26eadbf7127c1b077e633d5086da52b330" -graphql-tools@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-0.11.0.tgz#14c372f6ddad7e63a757094d541a937d6b31b7da" - dependencies: - deprecated-decorator "^0.1.6" - lodash "^4.3.0" - uuid "^3.0.1" - optionalDependencies: - "@types/graphql" "^0.9.0" - -graphql@^0.9.6: +graphql@0.9.x: version "0.9.6" resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.9.6.tgz#514421e9d225c29dfc8fd305459abae58815ef2c" dependencies: @@ -2149,7 +2083,7 @@ uuid@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" -uuid@^3.0.0, uuid@^3.0.1: +uuid@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" diff --git a/packages/cp-gql-schema/index.js b/packages/cp-gql-schema/index.js index 2a724415..0bd76809 100644 --- a/packages/cp-gql-schema/index.js +++ b/packages/cp-gql-schema/index.js @@ -1,8 +1,6 @@ const path = require('path'); -const { readFile } = require('mz/fs'); const { readFileSync } = require('fs'); const file = path.join(__dirname, 'schema.gql'); -module.exports = () => readFile(file, 'utf-8'); -module.exports.sync = () => readFileSync(file, 'utf-8'); +module.exports = readFileSync(file, 'utf-8'); diff --git a/packages/cp-gql-schema/package.json b/packages/cp-gql-schema/package.json index 3ae10644..ece6c630 100644 --- a/packages/cp-gql-schema/package.json +++ b/packages/cp-gql-schema/package.json @@ -9,9 +9,7 @@ "lint": "eslint . --fix --format=tap", "test": "exit 0" }, - "dependencies": { - "mz": "^2.6.0" - }, + "dependencies": {}, "devDependencies": { "eslint": "^3.19.0", "eslint-config-joyent-portal": "1.0.0" diff --git a/packages/cp-gql-schema/schema.gql b/packages/cp-gql-schema/schema.gql index 82f28374..1575c55b 100644 --- a/packages/cp-gql-schema/schema.gql +++ b/packages/cp-gql-schema/schema.gql @@ -9,7 +9,7 @@ } type User { - uuid: ID! + id: ID! firstName: String! lastName: String! email: String! @@ -17,7 +17,7 @@ } type DeploymentGroup { - uuid: ID! + id: ID! name: String! slug: String! services(slug: String): [Service]! @@ -26,7 +26,7 @@ } type ServiceScale { - uuid: ID! + id: ID! serviceName: String! replicas: Int! } @@ -39,14 +39,14 @@ } type ConvergenceAction { - uuid: String! + id: String! type: ConvergenceActionType! service: String! # service name machines: [String]! # instance machine ids } type StateConvergencePlan { - uuid: String! + id: String! running: Boolean! actions: [ConvergenceAction]! } @@ -69,7 +69,7 @@ } type Manifest { - uuid: String! + id: String! created: Date! type: ManifestType! format: ManifestFormat! @@ -79,21 +79,21 @@ # immutable type Service { - uuid: String! # unique id for db row + id: String! # unique id for db row hash: String! # unique id for version of service name: String! # human readable name slug: String! instances: [Instance]! # metrics: [MetricType]! currentMetrics: [CurrentMetric]! - connections: [String!] # list of serviceUuids - parent: ID # parent service uuid + connections: [String!] # list of serviceIds + parent: ID # parent service id package: Package! # we don't have this in current mock data } # for metrics max / min (I guess) type Package { - uuid: ID! + id: ID! name: String! type: String! memory: Float! @@ -115,7 +115,7 @@ } type Instance { - uuid: String! + id: String! name: String! machineId: String! status: InstanceStatus! @@ -123,8 +123,9 @@ } type Datacenter { - uuid: String! - # name: String! # Do we have 'official' human readable names? + id: String! + url: String! + name: String! region: String! } @@ -140,7 +141,7 @@ } type MetricType { - uuid: String! + id: String! name: String! id: String! } @@ -154,13 +155,13 @@ type Query { portal: Portal deploymentGroups: [DeploymentGroup] - deploymentGroup(uuid: String, slug: String): DeploymentGroup - services(deploymentGroupUuid: String, deploymentGroupSlug: String): [Service] - service(uuid: String, slug: String): Service - instances(serviceUuid: String, serviceSlug: String): [Instance] - instance(uuid: String, machineId: String): Instance + deploymentGroup(id: String, slug: String): DeploymentGroup + services(deploymentGroupId: String, deploymentGroupSlug: String): [Service] + service(id: String, slug: String): Service + instances(serviceId: String, serviceSlug: String): [Instance] + instance(id: String, machineId: String): Instance metricTypes: [MetricType] - metricData(instanceUuid: String!, metricType: String!, from: Date!, to: Date!): [InstanceMetric]! + metricData(instanceId: String!, metricType: String!, from: Date!, to: Date!): [InstanceMetric]! package: Package datacenters: [Datacenter] # tmp test @@ -173,41 +174,42 @@ portal: Portal user: User deploymentGroups(name: String, slug: String): [DeploymentGroup] - deploymentGroup(uuid: ID, name: String, slug: String): DeploymentGroup - serviceScales(serviceName: String, versionUuid: ID): [ServiceScale] - serviceScale(uuid: ID!): ServiceScale - convergenceActions(type: ConvergenceActionType, service: String, versionUuid: ID): [ConvergenceAction] - convergenceAction(uuid: ID!): ConvergenceAction - stateConvergencePlans(running: Boolean, versionUuid: ID): [StateConvergencePlan] - stateConvergencePlan(uuid: ID!): StateConvergencePlan - versions(manifestUuid: ID, deploymentGroupUuid: ID): [Version] - version(uuid: ID, manifestUuid: ID): Version - manifests(type: String, deploymentGroupUuid: ID): [Manifest] - manifest(uuid: ID!): Manifest - services(name: String, slug: String, parentUuid: ID, deploymentGroupUuid: ID, deploymentGroupSlug: String): [Service] - service(uuid: ID, hash: ID): Service + deploymentGroup(id: ID, name: String, slug: String): DeploymentGroup + serviceScales(serviceName: String, versionId: ID): [ServiceScale] + serviceScale(id: ID!): ServiceScale + convergenceActions(type: ConvergenceActionType, service: String, versionId: ID): [ConvergenceAction] + convergenceAction(id: ID!): ConvergenceAction + stateConvergencePlans(running: Boolean, versionId: ID): [StateConvergencePlan] + stateConvergencePlan(id: ID!): StateConvergencePlan + versions(manifestId: ID, deploymentGroupId: ID): [Version] + version(id: ID, manifestId: ID): Version + manifests(type: String, deploymentGroupId: ID): [Manifest] + manifest(id: ID!): Manifest + services(name: String, slug: String, parentId: ID, deploymentGroupId: ID, deploymentGroupSlug: String): [Service] + service(id: ID, hash: ID): Service packages(name: String, type: String, memory: Int, disk: Int, swap: Int, lwps: Int, vcpus: Int, version: String, group: String): [Package] - package(uuid: ID!): Package - instances(name: String!, machineId: ID, status: InstanceStatus, serviceUuid: ID, serviceSlug: String, deploymentGroupUuid: ID, deploymentGroupSlug: String): [Instance] - instance(uuid: ID!): Instance - datacenter(uuid: ID, region: String): Datacenter + package(id: ID!): Package + instances(name: String!, machineId: ID, status: InstanceStatus, serviceId: ID, serviceSlug: String, deploymentGroupId: ID, deploymentGroupSlug: String): [Instance] + instance(id: ID!): Instance + datacenter(id: ID, region: String): Datacenter + datacenters: [Datacenter] } type Mutation { createDeploymentGroup(name: String!) : DeploymentGroup - updateDeploymentGroup(uuid: ID!, name: String!) : DeploymentGroup + updateDeploymentGroup(id: ID!, name: String!) : DeploymentGroup - provisionManifest(deploymentGroupUuid: ID!, type: ManifestType!, format: ManifestFormat!, raw: String!) : Version + provisionManifest(deploymentGroupId: ID!, type: ManifestType!, format: ManifestFormat!, raw: String!) : Version scale(service: ID!, replicas: Int!) : Version - stopServices(uuids: [ID]!) : [Service] - startServices(uuids: [ID]!) : [Service] - restartServices(uuids: [ID]!) : [Service] - deleteServices(uuids: [ID]!) : [Service] + stopServices(ids: [ID]!) : [Service] + startServices(ids: [ID]!) : [Service] + restartServices(ids: [ID]!) : [Service] + deleteServices(ids: [ID]!) : [Service] - stopInstances(uuids: [ID]!) : [Instance] - startInstances(uuids: [ID]!) : [Instance] - restartInstances(uuids: [ID]!) : [Instance] + stopInstances(ids: [ID]!) : [Instance] + startInstances(ids: [ID]!) : [Instance] + restartInstances(ids: [ID]!) : [Instance] # reprovision() ??? } diff --git a/packages/portal-api/lib/handlers.js b/packages/portal-api/lib/handlers.js deleted file mode 100644 index 5e9fe593..00000000 --- a/packages/portal-api/lib/handlers.js +++ /dev/null @@ -1,82 +0,0 @@ -'use strict'; - -// Deployments - -exports.deploymentCreate = function (request, reply) { - const deploymentRoute = request.server.lookup('deploymentGet'); - - this.createDeployment(request.payload).then((deployment) => { - reply(deployment).created(deploymentRoute.path.replace('{deployment}', deployment.id)); - }).catch((error) => { - reply(error); - }); -}; - -exports.deploymentGet = function (request, reply) { - reply(this.getDeployment(request.params.deploymentId)); -}; - -exports.deploymentUpdate = function (request, reply) { - const payload = request.payload; - payload.id = request.params.deploymentId; - - reply(this.updateDeployment(payload)); -}; - -exports.deploymentDelete = function (request, reply) { - reply(this.deleteDeployment(request.params.deploymentId)); -}; - -exports.deploymentsGet = function (request, reply) { - reply(this.getDeployments()); -}; - - -// Datacenters - -exports.datacentersGet = function (request, reply) { - reply(this.getDatacenters()); -}; - - -// Manifests - -exports.manifestCreate = function (request, reply) { - const manifestRoute = request.server.lookup('manifestGet'); - const deploymentId = request.params.deploymentId; - - this.createManifest(deploymentId, request.payload).then((manifest) => { - reply(manifest).created(manifestRoute.path.replace('{deployment}', deploymentId).replace('{manifestId}', manifest.id)); - }).catch((error) => { - reply(error); - }); -}; - -exports.manifestGet = function (request, reply) { - reply(this.getManifest(request.params.manifestId)); -}; - - -// Activities and Metrics - -exports.activitiesGet = function (request, reply) { - reply(this.getActivities(request.params.deploymentId)); -}; - -exports.metricsGet = function (request, reply) { - reply(this.getMetrics(request.params.deploymentId)); -}; - - -// Services - -exports.servicesGet = function (request, reply) { - reply(this.getServices(request.params.deploymentId)); -}; - -exports.serviceUpdate = function (request, reply) { - const service = request.payload; - service.name = request.params.name; - - reply(this.updateService(request.params.deploymentId, service)); -}; diff --git a/packages/portal-api/lib/index.js b/packages/portal-api/lib/index.js index ec5602fa..9c536d40 100644 --- a/packages/portal-api/lib/index.js +++ b/packages/portal-api/lib/index.js @@ -1,10 +1,10 @@ 'use strict'; +const Schema = require('joyent-cp-gql-schema'); const Graphi = require('graphi'); const PortalData = require('portal-data'); -const Graphql = require('./models/graphql'); const Pack = require('../package.json'); -const Routes = require('./routes'); +const Resolvers = require('./resolvers'); module.exports = function (server, options, next) { @@ -19,12 +19,13 @@ module.exports = function (server, options, next) { server.register([ { register: Graphi, - options: Graphql.options(data) + options: { + schema: Schema, + resolvers: Resolvers(data) + } } ]); - server.route(Routes); - next(); }); }; diff --git a/packages/portal-api/lib/models/examples.js b/packages/portal-api/lib/models/examples.js deleted file mode 100644 index c81e4e99..00000000 --- a/packages/portal-api/lib/models/examples.js +++ /dev/null @@ -1,121 +0,0 @@ -'use strict'; - - -exports.activities = [ - { - date: Date.now(), - type: 'start', - meta: { - user: 'Tom' - } - }, - { - date: Date.now(), - type: 'stop', - meta: { - user: 'Dave' - } - } -]; - - -exports.datacenters = [ - { name: 'us-sw-1', url: 'https://us-sw-1.api.joyentcloud.com' }, - { name: 'us-west-1', url: 'https://us-west-1.api.joyentcloud.com' } -]; - - -exports.services = [ - { - name: 'consul', - count: 3 - }, - { - name: 'prometheus', - count: 1 - } -]; - -exports.service = exports.services[0]; - - -exports.deployments = [{ - id: 'd1f6c3af-1180-46cc-8d3f-1e7e90e5795d', - name: 'User Services', - datacenter: 'us-sw-1', - state: { - current: 'started' - }, - services: exports.services -}]; - -exports.deployment = exports.deployments[0]; - - -exports.manifest = { - id: 'd1f6c3af-1180-46cc-8d3f-1e7e90e5795d', - created: Date.now(), - deploymentId: exports.deployment.id, - type: 'docker-compose', - format: 'yml', - raw: `consul: - image: autopilotpattern/consul:0.7.2-r0.8 - restart: always - dns: - - 127.0.0.1 - labels: - - triton.cns.services=consul - ports: - - "8500:8500" - command: > - /usr/local/bin/containerpilot - /bin/consul agent -server - -config-dir=/etc/consul - -log-level=err - -bootstrap-expect 1 - -ui-dir /ui - prometheus: - image: autopilotpattern/prometheus:1.3.0r1.0 - mem_limit: 128m - restart: always - ports: - - "9090:9090"`, - obj: { - consul: { - image: 'autopilotpattern/consul:0.7.2-r0.8', - restart: 'always', - dns: ['127.0.0.1'], - labels: ['triton.cns.services=consul'], - ports: ['8500:8500'], - command: `> - /usr/local/bin/containerpilot - /bin/consul agent -server - -config-dir=/etc/consul - -log-level=err - -bootstrap-expect 1 - -ui-dir /ui` - }, - prometheus: { - image: 'autopilotpattern/prometheus:1.3.0r1.0', - mem_limit: '128m', - restart: 'always', - ports: ['9090:9090'] - } - } -}; - - -exports.metrics = [ - { - service: 'consul', - cpu: 1.2, - memory: 23344523, - network: 5024 - }, - { - service: 'prometheus', - cpu: 24.2, - memory: 514234453, - network: 10024 - } -]; diff --git a/packages/portal-api/lib/models/graphql.js b/packages/portal-api/lib/models/graphql.js deleted file mode 100644 index 626e716a..00000000 --- a/packages/portal-api/lib/models/graphql.js +++ /dev/null @@ -1,89 +0,0 @@ -'use strict'; - -const Fs = require('fs'); -const Path = require('path'); - - -const internals = { - schema: Fs.readFileSync(Path.join(__dirname, 'schema.gql')).toString() -}; - - -exports.options = (data) => { - const queryWrap = function (name) { - return function (args, request) { - return data[name](args); - }; - }; - - const queries = [ - 'portal', - 'deploymentGroups', - 'deploymentGroup', - 'services', - 'service', - 'instances', - 'instance', - 'metricTypes', - 'metricData', - 'package', - 'datacenters', - 'instanceMetric' - ]; - - const resolvers = { - createDeploymentGroup: (args, request) => { - return data.createDeploymentGroup(args.name); - }, - - updateDeploymentGroup: (args, request) => { - return data.updateDeploymentGroup(args.uuid, args.name); - }, - - provisionManifest: (args, request) => { - return data.provisionManifest(args.deploymentGroupUuid, args.type, args.format, args.raw); - }, - - scale: (args, request) => { - return data.scale(args.service, args.replicas); - }, - - stopServices: (args, request) => { - return data.stopServices(args.uuids); - }, - - startServices: (args, request) => { - return data.startServices(args.uuids); - }, - - restartServices: (args, request) => { - return data.restartServices(args.uuids); - }, - - deleteServices: (args, request) => { - return data.deleteServices(args.uuids); - }, - - stopInstances: (args, request) => { - return data.stopInstances(args.uuids); - }, - - startInstances: (args, request) => { - return data.startInstances(args.uuids); - }, - - restartInstances: (args, request) => { - return data.restartInstances(args.uuids); - } - }; - - queries.forEach((query) => { - const functionName = 'get' + query[0].toUpperCase() + query.slice(1); - resolvers[query] = queryWrap(functionName); - }); - - return { - schema: internals.schema, - resolvers - }; -}; diff --git a/packages/portal-api/lib/models/index.js b/packages/portal-api/lib/models/index.js deleted file mode 100644 index 8b7ace70..00000000 --- a/packages/portal-api/lib/models/index.js +++ /dev/null @@ -1,116 +0,0 @@ -'use strict'; - -const Joi = require('joi'); -const Examples = require('./examples'); - - -// Shared schema between schema sections - -const internals = { - serviceName: Joi.string().required().description('Unique name to identify the service') -}; - - -// Activity - -exports.activity = Joi.object({ - date: Joi.date().required().description('Date/time when the activity occurred'), - type: Joi.string().required().description('The type of activity that occurred'), - meta: Joi.object().optional().description('Any metadata related to the activity') -}).example(Examples.activities[0]); - -exports.activities = Joi.array().items(exports.activity).example(Examples.activities); - - -// Datacenters - -exports.datacenter = Joi.object({ - name: Joi.string().required().description('Name of datacenter'), - url: Joi.string().required().description('URL of datacenter') -}).example(Examples.datacenters[0]); - -exports.datacenters = Joi.array().items(exports.datacenter).example(Examples.datacenters); - - -// Services - -exports.serviceName = internals.serviceName; - -exports.serviceCount = Joi.number().default(1).description('Number of instances of the service'); - -exports.service = Joi.object({ - name: internals.serviceName, - count: exports.serviceCount -}).example(Examples.services[0]); - -exports.services = Joi.array().items(exports.service).example(Examples.services); - -exports.serviceUpdate = Joi.object({ - count: exports.serviceCount.required() -}); - - -// State - -exports.stateAction = Joi.object({ - action: Joi.string().required().valid(['start', 'stop', 'restart']) - .description('Action being performed on the deployment group') -}); - -exports.state = Joi.object({ - current: Joi.string().required().valid(['started', 'stopped']).default('stopped') - .description('The current state of the deployment group') -}); - - -// Deployments - -exports.deploymentId = Joi.string().required().description('ID of deployment group'); - -exports.deploymentCreate = Joi.object({ - name: Joi.string().required().description('Name of deployment group'), - datacenter: Joi.string().required().description('Datacenter the deployment group belongs to') -}); - -exports.deploymentUpdate = Joi.object({ - name: Joi.string().optional().description('Name of deployment group'), - datacenter: Joi.string().optional().description('Datacenter the deployment group belongs to') -}).or('name', 'datacenter'); - -exports.deployment = exports.deploymentCreate.keys({ - id: exports.deploymentId, - state: exports.state, - services: exports.services -}).example(Examples.deployments[0]); - -exports.deployments = Joi.array().items(exports.deployment); - - -// Manifests - -exports.manifestId = Joi.string().required().description('ID of manifest').example(Examples.manifest.id); - -exports.manifestCreate = Joi.object({ - format: Joi.string().default('yml').valid(['yml', 'json']).description('File format of raw data').example(Examples.manifest.format), - type: Joi.string().default('docker-compose').valid(['docker-compose']).description('Type of manifest, e.g. docker-compose').example(Examples.manifest.type), - raw: Joi.string().required().description('Original manifest file in a string form').example(Examples.manifest.raw), - obj: Joi.object().required().description('Manifest file represented as JSON').example(Examples.manifest.obj) -}); - -exports.manifest = exports.manifestCreate.keys({ - id: exports.manifestId, - created: Joi.date().required().description('Date/time when the manifest was created').example(Examples.manifest.created), - deploymentId: exports.deploymentId -}).example(Examples.manifest); - - -// Metrics - -exports.metric = Joi.object({ - service: internals.serviceName, - cpu: Joi.number().required().description('CPU usage percentage'), - memory: Joi.number().required().description('Total memory usage in bytes'), - network: Joi.number().required().description('Total bytes per second transferred by the NIC') -}).example(Examples.metrics[0]); - -exports.metrics = Joi.array().items(exports.metric).example(Examples.metrics); diff --git a/packages/portal-api/lib/models/schema.gql b/packages/portal-api/lib/models/schema.gql deleted file mode 100644 index 1ff50db8..00000000 --- a/packages/portal-api/lib/models/schema.gql +++ /dev/null @@ -1,204 +0,0 @@ - -scalar Date -scalar Object - -type Portal { - username: String! - datacenter: Datacenter! - deploymentGroups: [DeploymentGroup]! -} - -type DeploymentGroup { - uuid: ID! - name: String! - slug: String! - services: [Service]! - version: Version! - history: [Version]! -} - -type ServiceScale { - uuid: ID! - serviceName: String! - replicas: Int! -} - -enum ConvergenceActionType { - NOOP - CREATE - RECREATE - START - } - -type ConvergenceAction { - uuid: String! - type: ConvergenceActionType! - service: String! # service name - machines: [String]! # instance machine ids -} - -type StateConvergencePlan { - uuid: String! - running: Boolean! - actions: [ConvergenceAction]! -} - -type Version { - created: Date! # Either Int or define scalar - manifest: Manifest! - scale: [ServiceScale]! - plan: StateConvergencePlan -} - -enum ManifestType { - COMPOSE - MARIPOSA -} - -enum ManifestFormat { - JSON - YAML -} - -type Manifest { - uuid: String! - created: Date! - type: ManifestType! - format: ManifestFormat! - raw: String! - obj: Object! -} - -# immutable -type Service { - uuid: String! # unique id for db row - hash: String! # unique id for version of service - name: String! # human readable name - slug: String! - instances: [Instance]! - # metrics: [MetricType]! - currentMetrics: [CurrentMetric]! - connections: [String!] # list of serviceUuids - parent: ID # parent service uuid - package: Package! # we don't have this in current mock data -} - -# for metrics max / min (I guess) -type Package { - uuid: ID! - name: String! - type: String! - memory: Float! - disk: Float! - swap: Float! - lwps: Int! - vcpus: Int! - version: String! - group: String! -} - -enum InstanceStatus { - CREATED - RESTARTING - RUNNING - PAUSED - EXITED - DELETED -} - -type Instance { - uuid: String! - name: String! - machineId: String! - status: InstanceStatus! - # metrics: [InstanceMetric]! -} - -type Datacenter { - uuid: String! - # name: String! # Do we have 'official' human readable names? - region: String! -} - -type InstanceMetric { - type: MetricType! - data: [MetricData]! -} - -type CurrentMetric { - name: String! - value: Float! - measurement: String! -} - -type MetricType { - uuid: String! - name: String! - id: String! -} - -type MetricData { - timestamp: Int! - value: Float! -} - -# Need to review queries -type Query { - portal: Portal - deploymentGroups: [DeploymentGroup] - deploymentGroup(uuid: String, slug: String): DeploymentGroup - services(deploymentGroupUuid: String, deploymentGroupSlug: String): [Service] - service(uuid: String, slug: String): Service - instances(serviceUuid: String, serviceSlug: String): [Instance] - instance(uuid: String, machineId: String): Instance - metricTypes: [MetricType] - metricData(instanceUuid: String!, metricType: String!, from: Date!, to: Date!): [InstanceMetric]! - package: Package - datacenters: [Datacenter] - # tmp test - instanceMetric: InstanceMetric! -} - -# we probably wont use some of these queries or arguments -# but this way we expose the entire db through gql -type Query { - portal: Portal - deploymentGroups(name: String, slug: String): [DeploymentGroup] - deploymentGroup(uuid: ID, name: String, slug: String): DeploymentGroup - serviceScales(serviceName: String, versionUuid: ID): [ServiceScale] - serviceScale(uuid: ID!): ServiceScale - convergenceActions(type: ConvergenceActionType, service: String, versionUuid: ID): [ConvergenceAction] - convergenceAction(uuid: ID!): ConvergenceAction - stateConvergencePlans(running: Boolean, versionUuid: ID): [StateConvergencePlan] - stateConvergencePlan(uuid: ID!): StateConvergencePlan - versions(manifestUuid: ID, deploymentGroupUuid: ID): [Version] - version(uuid: ID, manifestUuid: ID): Version - manifests(type: String, deploymentGroupUuid: ID): [Manifest] - manifest(uuid: ID!): Manifest - services(name: String, slug: String, parentUuid: ID, deploymentGroupUuid: ID, deploymentGroupSlug: String): [Service] - service(uuid: ID, hash: ID): Service - packages(name: String, type: String, memory: Int, disk: Int, swap: Int, lwps: Int, vcpus: Int, version: String, group: String): [Package] - package(uuid: ID!): Package - instances(name: String!, machineId: ID, status: InstanceStatus, serviceUuid: ID, serviceSlug: String, deploymentGroupUuid: ID, deploymentGroupSlug: String): [Instance] - instance(uuid: ID!): Instance - datacenter(uuid: ID, region: String): Datacenter -} - -type Mutation { - createDeploymentGroup(name: String!) : DeploymentGroup - updateDeploymentGroup(uuid: ID!, name: String!) : DeploymentGroup - - provisionManifest(deploymentGroupUuid: ID!, type: ManifestType!, format: ManifestFormat!, raw: String!) : Version - scale(service: ID!, replicas: Int!) : Version - - stopServices(uuids: [ID]!) : [Service] - startServices(uuids: [ID]!) : [Service] - restartServices(uuids: [ID]!) : [Service] - deleteServices(uuids: [ID]!) : [Service] - - stopInstances(uuids: [ID]!) : [Instance] - startInstances(uuids: [ID]!) : [Instance] - restartInstances(uuids: [ID]!) : [Instance] - - # reprovision() ??? -} diff --git a/packages/portal-api/lib/resolvers.js b/packages/portal-api/lib/resolvers.js new file mode 100644 index 00000000..bbb54584 --- /dev/null +++ b/packages/portal-api/lib/resolvers.js @@ -0,0 +1,57 @@ +'use strict'; + +module.exports = (data) => { + const queryWrap = function (name) { + return function (options, request, cb) { + return data[name](options, cb); + }; + }; + + const mutationWrap = function (name) { + return function (options, request, cb) { + return data[name](options, cb); + }; + }; + + const queries = [ + 'portal', + 'deploymentGroups', + 'deploymentGroup', + 'services', + 'service', + 'instances', + 'instance', + 'metricTypes', + 'metricData', + 'package', + 'datacenters', + 'instanceMetric' + ]; + + const mutations = [ + 'createDeploymentGroup', + 'updateDeploymentGroup', + 'provisionManifest', + 'scale', + 'stopServices', + 'startServices', + 'restartServices', + 'deleteServices', + 'stopInstances', + 'startInstances', + 'restartInstances' + ]; + + const resolvers = {}; + + queries.forEach((query) => { + const functionName = 'get' + query[0].toUpperCase() + query.slice(1); + resolvers[query] = queryWrap(functionName); + }); + + mutations.forEach((mutation) => { + resolvers[mutation] = mutationWrap(mutation); + }); + + return resolvers; +}; diff --git a/packages/portal-api/lib/routes.js b/packages/portal-api/lib/routes.js deleted file mode 100644 index 4540801e..00000000 --- a/packages/portal-api/lib/routes.js +++ /dev/null @@ -1,224 +0,0 @@ -'use strict'; - -const Handlers = require('./handlers'); -const Models = require('./models'); - - -module.exports = [ - { - path: '/deployment', - method: 'post', - config: { - tags: ['api', 'deployment'], - description: 'Create new deployment group', - validate: { - payload: Models.deploymentCreate - }, - response: { - schema: Models.deployment - }, - handler: Handlers.deploymentCreate, - plugins: { - 'hapi-swagger': { - responses: { - '201': { - description: 'Deployment group created', - schema: Models.deployment - } - } - } - } - } - }, - { - path: '/deployment/{deploymentId}', - method: 'get', - config: { - id: 'deploymentGet', - tags: ['api', 'deployment'], - description: 'Retrieve a deployment group', - validate: { - params: { - deploymentId: Models.deploymentId - } - }, - response: { - schema: Models.deployment - }, - handler: Handlers.deploymentGet - } - }, - { - path: '/deployment/{deploymentId}', - method: 'put', - config: { - tags: ['api', 'deployment'], - description: 'Update a deployment group', - validate: { - params: { - deploymentId: Models.deploymentId - }, - payload: Models.deploymentUpdate - }, - response: { - schema: Models.deployment - }, - handler: Handlers.deploymentUpdate - } - }, - { - path: '/deployment/{deploymentId}', - method: 'delete', - config: { - tags: ['api', 'deployment'], - description: 'Delete a deployment group', - validate: { - params: { - deploymentId: Models.deploymentId - } - }, - handler: Handlers.deploymentDelete - } - }, - { - path: '/deployments', - method: 'get', - config: { - tags: ['api', 'deployment'], - description: 'Retrieve a list of deployment groups', - response: { - schema: Models.deployments - }, - handler: Handlers.deploymentsGet - } - }, - { - path: '/datacenters', - method: 'get', - config: { - tags: ['api', 'datacenter'], - description: 'Retrieve a list of available datacenters', - response: { - schema: Models.datacenters - }, - handler: Handlers.datacentersGet - } - }, - { - path: '/deployment/{deploymentId}/manifest', - method: 'post', - config: { - tags: ['api', 'deployment', 'manifest'], - description: 'Create a new manifest revision for a deployment group', - validate: { - params: { - deploymentId: Models.deploymentId - }, - payload: Models.manifestCreate - }, - response: { - schema: Models.manifest - }, - handler: Handlers.manifestCreate, - plugins: { - 'hapi-swagger': { - responses: { - '201': { - description: 'Manifest revision created', - schema: Models.manifest - } - } - } - } - } - }, - { - path: '/deployment/{deploymentId}/manifest/{manifestId}', - method: 'get', - config: { - id: 'manifestGet', - tags: ['api', 'deployment', 'manifest'], - description: 'Retrieve a manifest revision for a deployment group', - validate: { - params: { - deploymentId: Models.deploymentId, - manifestId: Models.manifestId - } - }, - response: { - schema: Models.manifest - }, - handler: Handlers.manifestGet - } - }, - { - path: '/deployment/{deploymentId}/activities', - method: 'get', - config: { - tags: ['api', 'deployment', 'activity'], - description: 'Retrieve the recent activities for the deployment group', - validate: { - params: { - deploymentId: Models.deploymentId - } - }, - response: { - schema: Models.activities - }, - handler: Handlers.activitiesGet - } - }, - { - path: '/deployment/{deploymentId}/metrics', - method: 'get', - config: { - tags: ['api', 'deployment', 'metric'], - description: 'Retrieve metrics for the deployment group', - validate: { - params: { - deploymentId: Models.deploymentId - } - }, - response: { - schema: Models.metrics - }, - handler: Handlers.metricsGet - } - }, - { - path: '/deployment/{deploymentId}/services', - method: 'get', - config: { - tags: ['api', 'deployment', 'service'], - description: 'Retrieve the services for a deployment group', - validate: { - params: { - deploymentId: Models.deploymentId - } - }, - response: { - schema: Models.services - }, - handler: Handlers.servicesGet - } - }, - { - path: '/deployment/{deploymentId}/service/{name}', - method: 'put', - config: { - tags: ['api', 'deployment', 'service'], - description: 'Perform an action on the named service', - validate: { - params: { - deploymentId: Models.deploymentId, - name: Models.serviceName - }, - payload: Models.serviceUpdate - }, - response: { - schema: Models.service - }, - handler: Handlers.serviceUpdate - } - } -]; diff --git a/packages/portal-api/package.json b/packages/portal-api/package.json index 348be46a..53c14f07 100644 --- a/packages/portal-api/package.json +++ b/packages/portal-api/package.json @@ -27,6 +27,7 @@ "graphi": "^2.0.0", "hoek": "^4.1.1", "joi": "^10.4.1", + "joyent-cp-gql-schema": "^1.0.4", "portal-data": "^1.0.0" } } diff --git a/packages/portal-api/test/index.js b/packages/portal-api/test/index.js index d09eaf3e..bd61647e 100644 --- a/packages/portal-api/test/index.js +++ b/packages/portal-api/test/index.js @@ -32,315 +32,6 @@ describe('portal-api plugin', () => { }); -describe('deployments', () => { - it('can be created', (done) => { - const server = new Hapi.Server(); - server.connection(); - server.register(internals.register, (err) => { - expect(err).to.not.exist(); - const payload = { - name: 'User Services', - datacenter: 'us-sw-1' - }; - - server.inject({ method: 'POST', url: '/deployment', payload }, (res) => { - expect(res.statusCode).to.equal(201); - expect(res.headers.location).to.exist(); - done(); - }); - }); - }); - - it('can be updated', (done) => { - const server = new Hapi.Server(); - server.connection(); - server.register(internals.register, (err) => { - expect(err).to.not.exist(); - const payload = { - name: 'User Services', - datacenter: 'us-sw-1' - }; - - server.inject({ method: 'POST', url: '/deployment', payload }, (res) => { - expect(res.statusCode).to.equal(201); - expect(res.result.name).to.equal('User Services'); - payload.name = 'Customer Services'; - - server.inject({ method: 'PUT', url: `/deployment/${res.result.id}`, payload }, (res) => { - expect(res.statusCode).to.equal(200); - expect(res.result.name).to.equal('Customer Services'); - done(); - }); - }); - }); - }); - - it('can be retrieved', (done) => { - const server = new Hapi.Server(); - server.connection(); - server.register(internals.register, (err) => { - expect(err).to.not.exist(); - - const payload = { - name: 'User Services', - datacenter: 'us-sw-1' - }; - - server.inject({ method: 'POST', url: '/deployment', payload }, (res) => { - expect(res.statusCode).to.equal(201); - expect(res.result.name).to.equal('User Services'); - - server.inject({ method: 'GET', url: `/deployment/${res.result.id}` }, (res) => { - expect(res.statusCode).to.equal(200); - expect(res.result.name).to.equal('User Services'); - done(); - }); - }); - }); - }); - - it('can be deleted', (done) => { - const server = new Hapi.Server(); - server.connection(); - server.register(internals.register, (err) => { - expect(err).to.not.exist(); - - const payload = { - name: 'User Services', - datacenter: 'us-sw-1' - }; - - server.inject({ method: 'POST', url: '/deployment', payload }, (res) => { - expect(res.statusCode).to.equal(201); - expect(res.result.name).to.equal('User Services'); - - server.inject({ method: 'DELETE', url: `/deployment/${res.result.id}` }, (res) => { - expect(res.statusCode).to.equal(200); - done(); - }); - }); - }); - }); - - it('can all be retrieved', (done) => { - const server = new Hapi.Server(); - server.connection(); - server.register(internals.register, (err) => { - expect(err).to.not.exist(); - - const deployment1 = { - name: 'User Services', - datacenter: 'us-sw-1' - }; - - const deployment2 = { - name: 'Customer Services', - datacenter: 'us-sw-1' - }; - - server.inject({ method: 'POST', url: '/deployment', payload: deployment1 }, (res) => { - expect(res.statusCode).to.equal(201); - expect(res.result.name).to.equal(deployment1.name); - - server.inject({ method: 'POST', url: '/deployment', payload: deployment2 }, (res) => { - expect(res.statusCode).to.equal(201); - expect(res.result.name).to.equal(deployment2.name); - - server.inject({ method: 'GET', url: '/deployments' }, (res) => { - expect(res.statusCode).to.equal(200); - expect(res.result.length >= 2).to.be.true(); - done(); - }); - }); - }); - }); - }); -}); - - -describe('datacenters', () => { - it('can be retrieved', (done) => { - const server = new Hapi.Server(); - server.connection(); - server.register(internals.register, (err) => { - expect(err).to.not.exist(); - - server.inject({ method: 'GET', url: '/datacenters' }, (res) => { - expect(res.statusCode).to.equal(200); - done(); - }); - }); - }); -}); - - -describe('manifests', () => { - it('can be created', (done) => { - const server = new Hapi.Server(); - server.connection(); - server.register(internals.register, (err) => { - expect(err).to.not.exist(); - const payload = { - raw: 'blah', - obj: {} - }; - - const deployment = { - name: 'User Services', - datacenter: 'us-sw-1' - }; - - server.inject({ method: 'POST', url: '/deployment', payload: deployment }, (res) => { - expect(res.statusCode).to.equal(201); - - server.inject({ method: 'POST', url: `/deployment/${res.result.id}/manifest`, payload }, (res) => { - expect(res.statusCode).to.equal(201); - expect(res.headers.location).to.exist(); - done(); - }); - }); - }); - }); - - it('can be retrieved', (done) => { - const server = new Hapi.Server(); - server.connection(); - server.register(internals.register, (err) => { - expect(err).to.not.exist(); - const payload = { - raw: 'blah', - obj: {} - }; - - const deployment = { - name: 'User Services', - datacenter: 'us-sw-1' - }; - - server.inject({ method: 'POST', url: '/deployment', payload: deployment }, (res) => { - expect(res.statusCode).to.equal(201); - - server.inject({ method: 'POST', url: `/deployment/${res.result.id}/manifest`, payload }, (res) => { - expect(res.statusCode).to.equal(201); - - server.inject(res.headers.location, (res) => { - expect(res.statusCode).to.equal(200); - expect(res.result.raw).to.equal(payload.raw); - done(); - }); - }); - }); - }); - }); -}); - - -describe('activities', () => { - it('can be retrieved', (done) => { - const server = new Hapi.Server(); - server.connection(); - server.register(internals.register, (err) => { - expect(err).to.not.exist(); - - const deployment = { - name: 'User Services', - datacenter: 'us-sw-1' - }; - - server.inject({ method: 'POST', url: '/deployment', payload: deployment }, (res) => { - expect(res.statusCode).to.equal(201); - - server.inject({ method: 'GET', url: `/deployment/${res.result.id}/activities` }, (res) => { - expect(res.statusCode).to.equal(200); - done(); - }); - }); - }); - }); -}); - - -describe('metrics', () => { - it.skip('can be retrieved', (done) => { - const server = new Hapi.Server(); - server.connection(); - server.register(internals.register, (err) => { - expect(err).to.not.exist(); - - const deployment = { - name: 'User Services', - datacenter: 'us-sw-1' - }; - - server.inject({ method: 'POST', url: '/deployment', payload: deployment }, (res) => { - expect(res.statusCode).to.equal(201); - - server.inject({ method: 'GET', url: `/deployment/${res.result.id}/metrics` }, (res) => { - expect(res.statusCode).to.equal(200); - done(); - }); - }); - }); - }); -}); - - -describe('services', () => { - it('can be retrieved', (done) => { - const server = new Hapi.Server(); - server.connection(); - server.register(internals.register, (err) => { - expect(err).to.not.exist(); - - const deployment = { - name: 'User Services', - datacenter: 'us-sw-1' - }; - - server.inject({ method: 'POST', url: '/deployment', payload: deployment }, (res) => { - expect(res.statusCode).to.equal(201); - - server.inject({ method: 'GET', url: `/deployment/${res.result.id}/services` }, (res) => { - expect(res.statusCode).to.equal(200); - done(); - }); - }); - }); - }); - - it('can be updated', (done) => { - const server = new Hapi.Server(); - server.connection(); - server.register(internals.register, (err) => { - expect(err).to.not.exist(); - - const deployment = { - name: 'User Services', - datacenter: 'us-sw-1' - }; - - server.inject({ method: 'POST', url: '/deployment', payload: deployment }, (res) => { - expect(res.statusCode).to.equal(201); - const deploymentId = res.result.id; - - server.inject({ method: 'GET', url: `/deployment/${deploymentId}/services` }, (res) => { - expect(res.statusCode).to.equal(200); - - const service = { - count: 2 - }; - - server.inject({ method: 'PUT', url: `/deployment/${deploymentId}/service/consul`, payload: service }, (res) => { - expect(res.statusCode).to.equal(200); - done(); - }); - }); - }); - }); - }); -}); - - describe('graphql', () => { it('route exists', (done) => { const server = new Hapi.Server(); diff --git a/packages/portal-data/.yarnclean b/packages/portal-data/.yarnclean new file mode 100644 index 00000000..25bdd148 --- /dev/null +++ b/packages/portal-data/.yarnclean @@ -0,0 +1,42 @@ +# test directories +__tests__ +test +tests +powered-test + +# asset directories +docs +doc +website +images +assets + +# examples +example +examples + +# code coverage directories +coverage +.nyc_output + +# build scripts +Makefile +Gulpfile.js +Gruntfile.js + +# configs +.tern-project +.gitattributes +.editorconfig +.*ignore +.eslintrc +.jshintrc +.flowconfig +.documentup.json +.yarn-metadata.json +.*.yml +*.yml + +# misc +*.gz +*.md diff --git a/packages/portal-data/lib/index.js b/packages/portal-data/lib/index.js index df37e0aa..84f0aa6e 100644 --- a/packages/portal-data/lib/index.js +++ b/packages/portal-data/lib/index.js @@ -3,7 +3,9 @@ const Hoek = require('hoek'); const Penseur = require('penseur'); const DCClient = require('docker-compose-client'); -const Awaitify = require('apr-awaitify'); +const VAsync = require('vasync'); +const Transform = require('./transform'); + const internals = { defaults: { @@ -13,13 +15,16 @@ const internals = { }, dockerHost: 'tcp://0.0.0.0:4242' }, - tables: [ - 'activities', - 'datacenters', - 'deployments', - 'manifests', - 'metrics' - ] + tables: { + 'portals': { id: { type: 'uuid' } }, + 'datacenters': { id: { type: 'uuid' } }, + 'deployment_groups': { id: { type: 'uuid' } }, + 'versions': { id: { type: 'uuid' } }, + 'manifests': { id: { type: 'uuid' } }, + 'services': { id: { type: 'uuid' } }, + 'packages': { id: { type: 'uuid' } }, + 'instances': { id: { type: 'uuid' } } + } }; module.exports = class Data { @@ -31,38 +36,83 @@ module.exports = class Data { this._docker = new DCClient(settings.dockerHost); } - connect () { - return new Promise((resolve, reject) => { - this._db.establish(internals.tables, (err) => { + connect (cb) { + this._db.establish(internals.tables, cb); + } + + + // portals + + createPortal (clientPortal, cb) { + const portal = Transform.toPortal(clientPortal); + this._db.portals.insert(portal, (err, key) => { + if (err) { + return cb(err); + } + + portal.id = key; + cb(null, Transform.fromPortal({ portal })); + }); + } + + getPortal (cb) { + this._db.portals.all((err, portals) => { + if (err) { + return cb(err); + } + + const portal = portals[0]; + VAsync.parallel({ + funcs: [ + (next) => { + this.getDatacenter({ id: portal.datacenter_id }, next); + }, + (next) => { + this.getDeploymentGroups(portal.deployment_group_ids, next); + } + ] + }, (err, results) => { if (err) { - return reject(err); + return cb(err); } - // promisify Penseur - internals.tables.forEach((tableName) => { - return ['insert', 'get', 'update', 'remove', 'all'].forEach((methodName) => { - this._db[tableName][methodName] = Awaitify( - this._db[tableName][methodName] - ); - }); - }); - - resolve(); + cb(null, Transform.fromPortal({ portal, datacenter: results.successes[0], deploymentGroups: results.successes[1] })); }); }); } - /* - * DeploymentGroupUuid - * Manifest - * id: UUID, - * created: Date.now(), - * type: 'docker-compose', - * format: 'yml', - * raw: 'original yml file content', - * obj: { } - */ - createDeployment ({ deploymentGroupUuid, manifest, deployment }) { + + // datacenters + + createDatacenter (datacenter, cb) { + this._db.datacenters.insert(datacenter, (err, key) => { + if (err) { + return cb(err); + } + + datacenter.id = key; + cb(null, datacenter); + }); + } + + getDatacenters (cb) { + this._db.datacenters.all(cb); + } + + getDatacenter ({ id, region }, cb) { + Hoek.assert(id || region, 'id or region are required to retrieve a datacenter'); + + if (region) { + return this._db.datacenters.single({ region }, cb); + } + + this._db.datacenters.single({ id }, cb); + } + + + // deployment_groups + + createDeploymentGroup ({ name }, cb) { // trigger deployment // create deployment queue (we should think about what is a deployment queue) // create the ConvergencePlans @@ -70,111 +120,68 @@ module.exports = class Data { // create a Version // update the DeploymentGroup - // TODO - const updateDb = (plan) => { - // deployment.services = []; - // deployment.state = { current: 'stopped' }; - - this._db.deployments - .insert({ - name: deployment.name - }) - .then((key) => { - deployment.id = key; - return deployment; - }); - }; - - const provision = ({ name }) => { - return this._docker - .provision({ - projectName: name, - manifest: manifest.raw - }) - .then(updateDb); - }; - - this.getDeployment(deploymentGroupUuid).then(provision); - } - - getDeployment (id) { - return this._db.deployments.get(id); - } - - updateDeployment (deployment) { - return this._db.deployments.update(deployment.id, deployment); - } - - deleteDeployment (id) { - return this._db.deployments.remove(id); - } - - getDeployments () { - return this._db.deployments.all(); - } - - getDatacenters () { - return this._db.datacenters.all(); - } - - createManifest (deploymentId, manifest) { - manifest.deploymentId = deploymentId; - manifest.created = Date.now(); - - return this._db.manifests.insert().then((id) => { - manifest.id = id; - return manifest; - }); - } - getManifest (id) { - return this._db.manifests.get(); - } - - getActivities (deploymentId) { - return this._db.activities.query({ deploymentId }); - } - - getMetrics (containerId) { - return this._db.metrics.get(containerId); - } - - insertMetrics (containerId, metrics) { - return this._db.metrics.get(containerId).then((existing) => { - if (existing) { - return this._db.metrics.update(containerId, { - metrics: this._db.append(metrics) - }); + this._db.deployment_groups.insert({ name }, (err, key) => { + if (err) { + return cb(err); } - const entry = { id: containerId, metrics }; - return this._db.metrics.insert(entry, { merge: true }); + cb(null, Transform.fromDeploymentGroup({ id: key, name })); }); } - getServices (deploymentId) { - this._db.deployments.get(deploymentId, { filter: 'services' }); - } - - updateService (deploymentId, service) { - this._db.deployments.get(deploymentId, { filter: 'services' }).then((deployment) => { - const serviceToUpdate = deployment.services.find((currentService) => { - return currentService.name === service.name; - }); - - if (!serviceToUpdate) { - deployment.services.push(service); - } else { - serviceToUpdate.count = service.count; - serviceToUpdate.containers = service.containers; + updateDeploymentGroup ({ id, name }, cb) { + this._db.deployment_groups.update(id, { name }, (err) => { + if (err) { + return cb(err); } - return this._db.deployments.update(deploymentId, { - services: deployment.services - }); + cb(null, Transform.fromDeploymentGroup({ id, name })); }); } - deploymentChanges (handler) { - return this._db.deployments.changes('*', { reconnect: true, handler }); + getDeploymentGroups (ids, cb) { + this._db.deployment_groups.get(ids, (err, deploymentGroups) => { + if (err) { + return cb(err); + } + + deploymentGroups = deploymentGroups || []; + cb(null, deploymentGroups.map(Transform.fromDeploymentGroup)); + }); + } + + getDeploymentGroup (id, cb) { + this._db.deployment_groups.single({ id }, (err, deploymentGroup) => { + if (err) { + return cb(err); + } + + cb(null, Transform.fromDeploymentGroup(deploymentGroup || {})); + }); + } + + + // versions + + createVersion (clientVersion, cb) { + const version = Transform.toVersion(clientVersion); + this._db.versions.insert(version, (err, key) => { + if (err) { + return cb(err); + } + + version.id = key; + cb(null, Transform.fromVersion(version)); + }); + } + + getVersion (id, cb) { + this._db.versions.single({ id }, (err, version) => { + if (err) { + return cb(err); + } + + cb(null, Transform.fromVersion(version)); + }); } }; diff --git a/packages/portal-data/lib/transform.js b/packages/portal-data/lib/transform.js new file mode 100644 index 00000000..37b43b43 --- /dev/null +++ b/packages/portal-data/lib/transform.js @@ -0,0 +1,94 @@ +'use strict'; + + +exports.fromPortal = function ({ portal, datacenter, deploymentGroups }) { + deploymentGroups = Array.isArray(deploymentGroups) ? deploymentGroups : []; + + return { + id: portal.id, + username: portal.username, + datacenter, + deploymentGroups: deploymentGroups.map(exports.fromDeploymentGroup) + }; +}; + +exports.toPortal = function (clientPortal) { + return { + username: clientPortal.username, + datacenter_id: clientPortal.datacenter ? clientPortal.datacenter.id : '', + deployment_group_ids: clientPortal.deploymentGroups ? clientPortal.deploymentGroups.map((deploymentGroup) => { + return deploymentGroup.id; + }) : [] + }; +}; + +exports.fromDeploymentGroup = function (deploymentGroup, services) { + if (!Array.isArray(services)) { + services = []; + } + + return { + id: deploymentGroup.id, + name: deploymentGroup.name, + slug: deploymentGroup.slug, + services: services.map(exports.fromService), + version: deploymentGroup.version_id, + history: deploymentGroup.history_version_ids || [] + }; +}; + + +exports.fromService = function (service) { + +}; + + +exports.toVersion = function (clientVersion) { + return { + id: clientVersion.id, + created: clientVersion.created || Date.now(), + manifest_id: clientVersion.manifestId, + service_scales: clientVersion.scales ? clientVersion.scales.map(exports.toScale) : [], + plan: exports.toPlan(clientVersion.plan || {}) + }; +}; + +exports.fromVersion = function (version) { + return { + id: version.id, + created: version.created, + manifestId: version.manifest_id, + scales: version.service_scales ? version.service_scales.map(exports.fromScale) : [], + plan: exports.fromPlan(version.plan || {}) + }; +}; + + +exports.toScale = function (clientScale) { + return { + service_name: clientScale.serviceName, + replicas: clientScale.replicas + }; +}; + +exports.fromScale = function (scale) { + return { + serviceName: scale.service_name, + replicas: scale.replicas + }; +}; + + +exports.toPlan = function (clientPlan) { + return { + running: clientPlan.running, + actions: clientPlan.actions + }; +}; + +exports.fromPlan = function (plan) { + return { + running: plan.running, + actions: plan.actions + }; +}; diff --git a/packages/portal-data/package.json b/packages/portal-data/package.json index 89df5b57..0231a7b7 100644 --- a/packages/portal-data/package.json +++ b/packages/portal-data/package.json @@ -6,7 +6,6 @@ "scripts": { "bootstrap": "node ./bootstrap-data", "lint": "belly-button", - "fmt": "prettier --write --single-quote {lib,test}/**/*.js", "rethinkdb-up": "docker run -d -p 8080:8080 -p 28015:28015 -p 29015:29015 --name rethinkdb rethinkdb", "rethinkdb-down": "docker rm -f rethinkdb", "test": "exit 0 # npm run lint && lab -t 40" @@ -15,10 +14,10 @@ "author": "wyatt", "license": "MPL-2.0", "dependencies": { - "apr-awaitify": "^1.0.4", "docker-compose-client": "^1.0.3", "hoek": "^4.1.1", - "penseur": "^7.8.1" + "penseur": "^7.8.1", + "vasync": "^1.6.4" }, "devDependencies": { "belly-button": "^3.1.0", diff --git a/packages/portal-data/test/index.js b/packages/portal-data/test/index.js index 40afc59b..5b7016c4 100644 --- a/packages/portal-data/test/index.js +++ b/packages/portal-data/test/index.js @@ -12,208 +12,252 @@ const internals = { options: { name: 'test', db: { test: true } } }; -describe('connect()', function () { - it('connects to the database', () => { +describe('connect()', () => { + it('connects to the database', (done) => { const data = new PortalData(internals.options); - - return data.connect(); + data.connect(done); }); }); -describe.skip('createDeployment()', () => { - it('creates a deployment record in the deployment table', (done) => { - const data = new PortalData(internals.options); - const deployment = { - name: 'User Services', - datacenter: 'us-sw-1' - }; - - data - .connect() - .then(() => { - data - .createDeployment({ - deployment - }) - .then((deployment) => { - expect(deployment.id).to.exist(); - done(); - }); - }) - .catch((err) => { +describe('portals', () => { + describe('createPortal()', () => { + it('creates a new portal', (done) => { + const data = new PortalData(internals.options); + data.connect((err) => { expect(err).to.not.exist(); - }); - }); -}); -describe.skip('getDeployment()', () => { - it('will retrieve an existing deployment', (done) => { - const data = new PortalData(internals.options); - data.connect().then(() => { - const deployment = { - name: 'User Services', - datacenter: 'us-sw-1' - }; + const portal = { + username: 'tom' + }; - data.createDeployment(deployment).then((deployment) => { - expect(deployment.id).to.exist(); - data.getDeployment(deployment.id).then((retrievedDeployment) => { - expect(deployment).to.equal(retrievedDeployment); + data.createPortal(portal, (err, result) => { + expect(err).to.not.exist(); + expect(result.id).to.exist(); + expect(result.username).to.equal(portal.username); done(); }); }); }); }); -}); -describe.skip('updateService()', () => { - it('will update the services for an existing deployment', (done) => { - const data = new PortalData(internals.options); - data.connect().then(() => { - const deployment = { - name: 'User Services', - datacenter: 'us-sw-1' - }; - const service = { - name: 'consul', - containers: [ - { - server_id: '423e7432-b760-11e2-bf6c-002590c3f1a0', - alias: 'nodejsexample_consul_1', - image_id: '91b757b5-bd29-2126-5ff9-ae9235011ff5', - owner_id: '30f62ec2-24a2-6f8e-8fad-d46b04c8a0b9', - id: '81205d4a-92f4-c4d9-da8a-aafd689eeabb' - } - ], - count: 1 - }; + describe('getPortal()', () => { + it('retrieves a single portal record', (done) => { + const data = new PortalData(internals.options); + data.connect((err) => { + expect(err).to.not.exist(); + const datacenter = { + region: 'us-sw-1' + }; - data.createDeployment(deployment).then((deployment) => { - expect(deployment.id).to.exist(); - data.updateService(deployment.id, service).then((updatedService) => { - expect(updatedService).to.equal(service); - done(); - }); - }); - }); - }); -}); + data.createDatacenter(datacenter, (err, createdDatacenter) => { + expect(err).to.not.exist(); + const portal = { + username: 'tom', + datacenter: { + id: createdDatacenter.id + } + }; -describe.skip('deploymentChanges()', () => { - it('will execute the handler when a deployment service changes', (done) => { - const data = new PortalData(internals.options); - data.connect().then(() => { - const deployment = { - name: 'User Services', - datacenter: 'us-sw-1' - }; - const service1 = { - name: 'consul', - containers: [ - { - server_id: '423e7432-b760-11e2-bf6c-002590c3f1a0', - alias: 'nodejsexample_consul_1', - image_id: '91b757b5-bd29-2126-5ff9-ae9235011ff5', - owner_id: '30f62ec2-24a2-6f8e-8fad-d46b04c8a0b9', - id: '81205d4a-92f4-c4d9-da8a-aafd689eeabb' - } - ], - count: 1 - }; - - const service2 = { - name: 'consul', - containers: [ - { - server_id: '423e7432-b760-11e2-bf6c-002590c3f1a0', - alias: 'nodejsexample_consul_1', - image_id: '91b757b5-bd29-2126-5ff9-ae9235011ff5', - owner_id: '30f62ec2-24a2-6f8e-8fad-d46b04c8a0b9', - id: '81205d4a-92f4-c4d9-da8a-aafd689eeabb' - }, - { - server_id: '423e7432-b760-11e2-bf6c-002590c3f1a0', - alias: 'nodejsexample_consul_2', - image_id: '91b757b5-bd29-2126-5ff9-ae9235011ff5', - owner_id: '30f62ec2-24a2-6f8e-8fad-d46b04c8a0b9', - id: '81205d4a-92f4-c4d9-da8a-aafd689eeabb' - }, - { - server_id: '423e7432-b760-11e2-bf6c-002590c3f1a0', - alias: 'nodejsexample_consul_3', - image_id: '91b757b5-bd29-2126-5ff9-ae9235011ff5', - owner_id: '30f62ec2-24a2-6f8e-8fad-d46b04c8a0b9', - id: '81205d4a-92f4-c4d9-da8a-aafd689eeabb' - } - ], - count: 3 - }; - - data.createDeployment(deployment).then((deployment) => { - expect(deployment.id).to.exist(); - data.updateService(deployment.id, service1).then((updatedService1) => { - expect(updatedService1).to.equal(service1); - - let executed = false; - data - .deploymentChanges((err, changes) => { + data.createPortal(portal, (err, createdPortal) => { + expect(err).to.not.exist(); + expect(createdPortal.id).to.exist(); + data.getPortal((err, retrievedPortal) => { expect(err).to.not.exist(); - if (executed) { - return; - } - - expect(changes.before).to.exist(); - expect(changes.after).to.exist(); + expect(retrievedPortal.id).to.exist(); + expect(retrievedPortal.username).to.equal(portal.username); done(); - executed = true; - }) - .then(() => { - data - .updateService(deployment.id, service2) - .then((updatedService2) => { - expect(updatedService2).to.equal(service2); - }); }); - }); - }); - }); - }); -}); - -describe.skip('insertMetrics()', () => { - it("will add new metrics to a service and won't overwrite existing ones", (done) => { - const data = new PortalData(internals.options); - data.connect().then(() => { - const containerId = '81205d4a-92f4-c4d9-da8a-aafd689eeabb'; - const metrics1 = [ - { - timestamp: 1494360995851, - cpu: 1.2, - memory: 23344523, - network: 5024 - } - ]; - - const metrics2 = [ - { - timestamp: 1495360995851, - cpu: 1.3, - memory: 23344523, - network: 4024 - } - ]; - - data.insertMetrics(containerId, metrics1).then((result1) => { - expect(result1.id).to.equal(containerId); - expect(result1.metrics).to.equal(metrics1); - data.insertMetrics(containerId, metrics2).then((result2) => { - expect(result2.id).to.equal(containerId); - data.getMetrics(containerId).then((results) => { - expect(results.metrics.length).to.equal(2); - done(); }); }); }); }); }); }); + +describe('deployment groups', () => { + describe('createDeploymentGroup()', () => { + it('creates a deployment group record in the deployment_groups table', (done) => { + const data = new PortalData(internals.options); + const name = 'User Services'; + + data.connect((err) => { + expect(err).to.not.exist(); + data.createDeploymentGroup({ name }, (err, deploymentGroup) => { + expect(err).to.not.exist(); + expect(deploymentGroup.id).to.exist(); + done(); + }); + }); + }); + }); + + describe('getDeploymentGroup()', () => { + it('gets a deployment group record from the deployment_groups table', (done) => { + const data = new PortalData(internals.options); + const name = 'User Services'; + + data.connect((err) => { + expect(err).to.not.exist(); + data.createDeploymentGroup({ name }, (err, createdDeploymentGroup) => { + expect(err).to.not.exist(); + expect(createdDeploymentGroup.id).to.exist(); + data.getDeploymentGroup(createdDeploymentGroup.id, (err, deploymentGroup) => { + expect(err).to.not.exist(); + expect(deploymentGroup).to.equal(createdDeploymentGroup); + done(); + }); + }); + }); + }); + }); + + describe('getDeploymentGroups()', () => { + it('gets a list of deployment group records from the deployment_groups table', (done) => { + const data = new PortalData(internals.options); + const name = 'User Services'; + + data.connect((err) => { + expect(err).to.not.exist(); + data.createDeploymentGroup({ name }, (err, createdDeploymentGroup1) => { + expect(err).to.not.exist(); + expect(createdDeploymentGroup1.id).to.exist(); + data.createDeploymentGroup({ name }, (err, createdDeploymentGroup2) => { + expect(err).to.not.exist(); + expect(createdDeploymentGroup1.id).to.exist(); + + data.getDeploymentGroups([createdDeploymentGroup1.id, createdDeploymentGroup2.id], (err, deploymentGroups) => { + expect(err).to.not.exist(); + expect(deploymentGroups.length).to.equal(2); + done(); + }); + }); + }); + }); + }); + }); +}); + +describe('datacenters', () => { + describe('createDatacenter()', () => { + it('creates a new datacenter record', (done) => { + const data = new PortalData(internals.options); + data.connect((err) => { + expect(err).to.not.exist(); + const datacenter = { + region: 'us-sw-1' + }; + + data.createDatacenter(datacenter, (err, result) => { + expect(err).to.not.exist(); + expect(result.id).to.exist(); + expect(result.region).to.equal(datacenter.region); + done(); + }); + }); + }); + }); + + describe('getDatacenter()', () => { + it('retrieves a datacenter record from an id', (done) => { + const data = new PortalData(internals.options); + data.connect((err) => { + expect(err).to.not.exist(); + const datacenter = { + region: 'us-sw-1' + }; + + data.createDatacenter(datacenter, (err, createdDatacenter) => { + expect(err).to.not.exist(); + expect(createdDatacenter.id).to.exist(); + data.getDatacenter({ id: createdDatacenter.id }, (err, retrievedDatacenter) => { + expect(err).to.not.exist(); + expect(retrievedDatacenter.region).to.equal(datacenter.region); + done(); + }); + }); + }); + }); + + it('retrieves a datacenter record from a region', (done) => { + const data = new PortalData(internals.options); + data.connect((err) => { + expect(err).to.not.exist(); + const datacenter = { + region: 'us-sw-1' + }; + + data.createDatacenter(datacenter, (err, createdDatacenter) => { + expect(err).to.not.exist(); + expect(createdDatacenter.id).to.exist(); + data.getDatacenter({ region: createdDatacenter.region }, (err, retrievedDatacenter) => { + expect(err).to.not.exist(); + expect(retrievedDatacenter.region).to.equal(datacenter.region); + done(); + }); + }); + }); + }); + }); + + describe('getDatacenters()', () => { + it('retrieves all datacenter records', (done) => { + const data = new PortalData(internals.options); + data.connect((err) => { + expect(err).to.not.exist(); + const datacenter1 = { + region: 'us-sw-1' + }; + + const datacenter2 = { + region: 'us-west-1' + }; + + data.createDatacenter(datacenter1, (err, createdDatacenter1) => { + expect(err).to.not.exist(); + data.createDatacenter(datacenter2, (err, createdDatacenter2) => { + expect(err).to.not.exist(); + data.getDatacenters((err, datacenters) => { + expect(err).to.not.exist(); + expect(datacenters.length).to.equal(2); + done(); + }); + }); + }); + }); + }); + }); +}); + + +describe('versions', () => { + describe('createVersion()', () => { + it('creates a new version record in the versions table', (done) => { + const data = new PortalData(internals.options); + data.connect((err) => { + expect(err).to.not.exist(); + const clientVersion = { + manifestId: 'something', + scales: [{ + serviceName: 'consul', + replicas: 3 + }], + plan: { + running: true, + actions: [{ + type: 'start', + service: 'consul', + machines: ['vmid', 'vmid'] + }] + } + }; + + data.createVersion(clientVersion, (err, result) => { + expect(err).to.not.exist(); + expect(result.id).to.exist(); + expect(result.scales).to.equal(clientVersion.scales); + done(); + }); + }); + }); + }); +}); diff --git a/packages/portal-data/yarn.lock b/packages/portal-data/yarn.lock index f666e00e..50f37c3a 100644 --- a/packages/portal-data/yarn.lock +++ b/packages/portal-data/yarn.lock @@ -276,6 +276,13 @@ diff@3.x.x: version "3.2.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" +docker-compose-client@^1.0.3: + version "1.0.7" + resolved "https://registry.yarnpkg.com/docker-compose-client/-/docker-compose-client-1.0.7.tgz#a2f351aff998fd5323b9b6bb27d4400fff95e43c" + dependencies: + apr-awaitify "^1.0.4" + zerorpc "^0.9.7" + doctrine@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" @@ -448,6 +455,10 @@ exit-hook@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" +extsprintf@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.2.0.tgz#5ad946c22f5b32ba7f8cd7426711c6e8a3fc2529" + fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -1183,6 +1194,18 @@ uuid@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" +vasync@^1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/vasync/-/vasync-1.6.4.tgz#dfe93616ad0e7ae801b332a9d88bfc5cdc8e1d1f" + dependencies: + verror "1.6.0" + +verror@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.6.0.tgz#7d13b27b1facc2e2da90405eb5ea6e5bdd252ea5" + dependencies: + extsprintf "1.2.0" + window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"