diff --git a/packages/cp-frontend/.eslintrc b/packages/cp-frontend/.eslintrc
index 6f477689..a847796a 100644
--- a/packages/cp-frontend/.eslintrc
+++ b/packages/cp-frontend/.eslintrc
@@ -5,6 +5,7 @@
"new-cap": 0,
// temp
"no-undef": 1,
- "no-debugger": 1
+ "no-debugger": 1,
+ "no-negated-condition": 0
}
}
diff --git a/packages/cp-frontend/package.json b/packages/cp-frontend/package.json
index ffad4fdf..fd892a5f 100644
--- a/packages/cp-frontend/package.json
+++ b/packages/cp-frontend/package.json
@@ -53,7 +53,6 @@
"unitcalc": "^1.0.8"
},
"devDependencies": {
- "apr-find": "^1.0.5",
"apr-for-each": "^1.0.6",
"apr-main": "^1.0.7",
"babel-plugin-inline-react-svg": "^0.4.0",
diff --git a/packages/cp-frontend/public/index.html b/packages/cp-frontend/public/index.html
index 3c722c45..de211200 100644
--- a/packages/cp-frontend/public/index.html
+++ b/packages/cp-frontend/public/index.html
@@ -14,6 +14,15 @@
border: solid 1px #d8d8d8;
margin-bottom: 8px;
}
+
+ html, body, #root {
+ height: 100%;
+ }
+
+ #root {
+ display: flex;
+ flex-flow: column;
+ }
diff --git a/packages/cp-frontend/scripts/postinstall.js b/packages/cp-frontend/scripts/postinstall.js
index 010ecb23..c2e0f68f 100644
--- a/packages/cp-frontend/scripts/postinstall.js
+++ b/packages/cp-frontend/scripts/postinstall.js
@@ -1,7 +1,6 @@
const { readFile, writeFile, exists } = require('mz/fs');
const main = require('apr-main');
const forEach = require('apr-for-each');
-const find = require('apr-find');
const path = require('path');
const ROOT = path.join(__dirname, '../node_modules/react-scripts/config');
diff --git a/packages/cp-frontend/src/app.js b/packages/cp-frontend/src/app.js
index ad8b0ca7..1fcf0086 100644
--- a/packages/cp-frontend/src/app.js
+++ b/packages/cp-frontend/src/app.js
@@ -1,5 +1,4 @@
import React, { Component } from 'react';
-import { Article } from 'normalized-styled-components';
import { ThemeProvider, injectGlobal } from 'styled-components';
import { theme, global } from 'joyent-ui-toolkit';
import { ApolloProvider } from 'react-apollo';
@@ -19,9 +18,7 @@ class App extends Component {
return (
-
- {Router}
-
+ {Router}
);
diff --git a/packages/cp-frontend/src/components/deployment-groups/empty.js b/packages/cp-frontend/src/components/deployment-groups/empty.js
deleted file mode 100644
index 36f6cb9b..00000000
--- a/packages/cp-frontend/src/components/deployment-groups/empty.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react';
-
-import { Col, Row } from 'react-styled-flexboxgrid';
-
-export default () =>
-
-
- you don't have any deployment groups
-
-
;
diff --git a/packages/cp-frontend/src/components/deployment-groups/index.js b/packages/cp-frontend/src/components/deployment-groups/index.js
index d7d458ff..ad11539a 100644
--- a/packages/cp-frontend/src/components/deployment-groups/index.js
+++ b/packages/cp-frontend/src/components/deployment-groups/index.js
@@ -1 +1,2 @@
-export { default as EmptyDeployementGroups } from './empty';
+export { default as DeploymentGroupsLoading } from './loading';
+export { default as CreateDeploymentGroup } from './create';
diff --git a/packages/cp-frontend/src/components/deployment-groups/loading.js b/packages/cp-frontend/src/components/deployment-groups/loading.js
new file mode 100644
index 00000000..057a1528
--- /dev/null
+++ b/packages/cp-frontend/src/components/deployment-groups/loading.js
@@ -0,0 +1,14 @@
+import React from 'react';
+import { Col, Row } from 'react-styled-flexboxgrid';
+import { Dots2 } from 'styled-text-spinners';
+
+const LoadingRow = Row.extend`
+ flex: 1 1 auto;
+`;
+
+export default () =>
+
+
+
+
+ ;
diff --git a/packages/cp-frontend/src/components/layout/container.js b/packages/cp-frontend/src/components/layout/container.js
index 587b8762..0b15643b 100644
--- a/packages/cp-frontend/src/components/layout/container.js
+++ b/packages/cp-frontend/src/components/layout/container.js
@@ -1,3 +1,13 @@
import { Grid } from 'react-styled-flexboxgrid';
+import remcalc from 'remcalc';
+import { isNot } from 'styled-is';
-export default Grid;
+export default Grid.extend`
+ padding-top: ${remcalc(19)};
+
+ ${isNot('plain')`
+ flex: 1 1 auto;
+ display: flex;
+ flex-flow: column;
+ `};
+`;
diff --git a/packages/cp-frontend/src/components/navigation/breadcrumb.js b/packages/cp-frontend/src/components/navigation/breadcrumb.js
index e4fac875..86e8baa2 100644
--- a/packages/cp-frontend/src/components/navigation/breadcrumb.js
+++ b/packages/cp-frontend/src/components/navigation/breadcrumb.js
@@ -19,7 +19,6 @@ const BreadcrumbLink = styled(Link)`
const BreadcrumbContainer = styled.div`
border-bottom: solid ${remcalc(1)} ${props => props.theme.grey};
- margin-bottom: ${remcalc(19)};
`;
const getBreadcrumbItems = (...links) =>
diff --git a/packages/cp-frontend/src/components/navigation/menu.js b/packages/cp-frontend/src/components/navigation/menu.js
index e6ac4925..3b3e58b2 100644
--- a/packages/cp-frontend/src/components/navigation/menu.js
+++ b/packages/cp-frontend/src/components/navigation/menu.js
@@ -22,7 +22,7 @@ const getMenuItems = (...links) =>
);
const Menu = ({ links = [] }) =>
-
+
{getMenuItems(...links)}
diff --git a/packages/cp-frontend/src/containers/deployment-groups/create.js b/packages/cp-frontend/src/containers/deployment-groups/create.js
index 06084b1f..8cb38d1b 100644
--- a/packages/cp-frontend/src/containers/deployment-groups/create.js
+++ b/packages/cp-frontend/src/containers/deployment-groups/create.js
@@ -8,7 +8,7 @@ import paramCase from 'param-case';
import DeploymentGroupBySlug from '@graphql/DeploymentGroupBySlug.gql';
import DeploymentGroupCreateMutation from '@graphql/DeploymentGroupCreate.gql';
import DeploymentGroupProvisionMutation from '@graphql/DeploymentGroupProvision.gql';
-import DeploymentGroupConfigMutation from '@graphql/DeploymentGroupConfig.gql';
+import DeploymentGroupConfigQuery from '@graphql/DeploymentGroupConfig.gql';
import { client } from '@state/store';
import { LayoutContainer } from '@components/layout';
@@ -262,7 +262,7 @@ export default compose(
provisionManifest: variables => mutate({ variables })
})
}),
- graphql(DeploymentGroupConfigMutation, {
+ graphql(DeploymentGroupConfigQuery, {
props: ({ mutate }) => ({
config: variables => mutate({ variables })
})
diff --git a/packages/cp-frontend/src/containers/deployment-groups/import.js b/packages/cp-frontend/src/containers/deployment-groups/import.js
new file mode 100644
index 00000000..c36ec1a6
--- /dev/null
+++ b/packages/cp-frontend/src/containers/deployment-groups/import.js
@@ -0,0 +1,58 @@
+import React, { Component } from 'react';
+import { compose, graphql } from 'react-apollo';
+import intercept from 'apr-intercept';
+
+import DeploymentGroupImportMutation from '@graphql/DeploymentGroupImport.gql';
+
+import { LayoutContainer } from '@components/layout';
+import { DeploymentGroupsLoading } from '@components/deployment-groups';
+import { Dots2 } from 'styled-text-spinners';
+import { H2 } from 'joyent-ui-toolkit';
+
+class DeploymentGroupImport extends Component {
+ constructor() {
+ super();
+
+ this.state = {
+ loading: true,
+ error: false
+ };
+
+ setTimeout(this.importDeploymentGroup, 16);
+ }
+
+ importDeploymentGroup = async () => {
+ const { importDeploymentGroup, match, history } = this.props;
+ const { slug } = match.params;
+
+ const [error] = await intercept(
+ importDeploymentGroup({
+ slug
+ })
+ );
+
+ if (error) {
+ return this.setState({ loading: false, error });
+ }
+
+ history.push(`/deployment-groups/${slug}`);
+ };
+
+ render() {
+ const { loading, error } = this.state;
+
+ return (
+
+ Importing deployment group
+ {loading && }
+ {error && {error.toString()}}
+
+ );
+ }
+}
+
+export default graphql(DeploymentGroupImportMutation, {
+ props: ({ mutate }) => ({
+ importDeploymentGroup: variables => mutate({ variables })
+ })
+})(DeploymentGroupImport);
diff --git a/packages/cp-frontend/src/containers/deployment-groups/index.js b/packages/cp-frontend/src/containers/deployment-groups/index.js
index 6b7bbecd..6f8944a3 100644
--- a/packages/cp-frontend/src/containers/deployment-groups/index.js
+++ b/packages/cp-frontend/src/containers/deployment-groups/index.js
@@ -1,2 +1,3 @@
export { default as DeploymentGroupList } from './list';
export { default as DeploymentGroupCreate } from './create';
+export { default as DeploymentGroupImport } from './import';
diff --git a/packages/cp-frontend/src/containers/deployment-groups/list.js b/packages/cp-frontend/src/containers/deployment-groups/list.js
index e7c1ba8e..204ccedc 100644
--- a/packages/cp-frontend/src/containers/deployment-groups/list.js
+++ b/packages/cp-frontend/src/containers/deployment-groups/list.js
@@ -1,73 +1,133 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { graphql } from 'react-apollo';
+import { compose, graphql } from 'react-apollo';
import { Link } from 'react-router-dom';
-import DeploymentGroupsQuery from '@graphql/DeploymentGroups.gql';
+import styled from 'styled-components';
import { Col, Row } from 'react-styled-flexboxgrid';
+import forceArray from 'force-array';
+import remcalc from 'remcalc';
import { LayoutContainer } from '@components/layout';
import { Loader, ErrorMessage } from '@components/messaging';
-import { EmptyDeployementGroups } from '@components/deployment-groups';
-import { Button } from 'joyent-ui-toolkit';
+import { DeploymentGroupsLoading } from '@components/deployment-groups';
+import DeploymentGroupsQuery from '@graphql/DeploymentGroups.gql';
+import DeploymentGroupsImportableQuery from '@graphql/DeploymentGroupsImportable.gql';
+import { Button, H2, H3, Small } from 'joyent-ui-toolkit';
+
+const Title = H2.extend`
+ margin-top: ${remcalc(2)};
+`;
+
+const DGsRows = Row.extend`
+ margin-top: ${remcalc(-7)};
+`;
+
+const Box = Col.withComponent(Link).extend`
+ text-decoration: none;
+ color: ${props => props.theme.secondary};
+ background-color: ${props => props.theme.white};
+ box-shadow: 0 ${remcalc(2)} 0 0 rgba(0, 0, 0, 0.05);
+ border: solid ${remcalc(1)} ${props => props.theme.grey};
+ margin-top: ${remcalc(20)};
+ margin-bottom: 0;
+ padding: ${remcalc(18)};
+ min-height: ${remcalc(258)};
+ display: flex;
+ flex-direction: column;
+
+ &:last-child {
+ margin-bottom: ${remcalc(20)};
+ }
+`;
+
+const BoxCreate = Box.extend`
+ background-color: ${props => props.theme.disabled};
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ align-content: center;
+ display: flex;
+
+ &:hover {
+ background-color: ${props => props.theme.white};
+ }
+`;
+
+const Oval = styled.div`
+ border: solid ${remcalc(1)} ${props => props.theme.grey};
+ border-radius: 50%;
+
+ width: ${remcalc(48)};
+ height: ${remcalc(48)};
+ line-height: ${remcalc(48)};
+ font-size: ${remcalc(24)};
+ text-align: center;
+
+ margin-bottom: ${remcalc(20)};
+`;
+
+const CreateTitle = Small.extend`
+ font-weight: 600;
+ text-align: center;
+`;
+
+const ServiceTitle = H3.extend`
+ margin-top: ${remcalc(10)};
+ font-weight: 600;
+`;
const DeploymentGroupList = ({
location,
deploymentGroups,
+ importable,
loading,
- error
+ error,
+ match
}) => {
- if (loading) {
- return (
-
-
-
- );
- } else if (error) {
- return (
-
+ const _loading = !loading ? null : ;
+
+ // todo improve this error message style according to new designs
+ const _error = !error
+ ? null
+ :
-
- );
- }
+ ;
- let emptyDeployementGroups = null;
- let deploymentGroupList = null;
+ const groups = forceArray(deploymentGroups).map(({ slug, name }) =>
+
+
+ {name}
+
+
+ );
- if (deploymentGroups.length) {
- const list = deploymentGroups.map((deploymentGroup, index) => {
- return (
-
-
- {deploymentGroup.name}
-
-
- );
- });
-
- deploymentGroupList = (
-
-
-
-
-
- );
- } else {
- emptyDeployementGroups = ;
- }
+ const create = [
+
+
+ +
+ Create new deployment group
+
+
+ ].concat(
+ forceArray(importable).map(({ slug, name }) =>
+
+
+ ⤵
+ {name}
+
+
+ )
+ );
return (
- {emptyDeployementGroups}
-
-
-
-
-
- {deploymentGroupList}
+ Deployment groups
+ {_loading}
+ {_error}
+
+ {groups}
+ {create}
+
);
};
@@ -81,15 +141,20 @@ DeploymentGroupList.propTypes = {
)
};
-const DeploymentGroupListWithData = graphql(DeploymentGroupsQuery, {
- options: {
- pollInterval: 1000
- },
- props: ({ data: { deploymentGroups, loading, error } }) => ({
- deploymentGroups,
- loading,
- error
+export default compose(
+ graphql(DeploymentGroupsQuery, {
+ options: {
+ pollInterval: 1000
+ },
+ props: ({ data: { deploymentGroups, loading, error } }) => ({
+ deploymentGroups,
+ loading,
+ error
+ })
+ }),
+ graphql(DeploymentGroupsImportableQuery, {
+ props: ({ data: { importableDeploymentGroups } }) => ({
+ importable: importableDeploymentGroups
+ })
})
-})(DeploymentGroupList);
-
-export default DeploymentGroupListWithData;
+)(DeploymentGroupList);
diff --git a/packages/cp-frontend/src/containers/services/menu.js b/packages/cp-frontend/src/containers/services/menu.js
index b708b376..8054d098 100644
--- a/packages/cp-frontend/src/containers/services/menu.js
+++ b/packages/cp-frontend/src/containers/services/menu.js
@@ -65,7 +65,7 @@ const ServicesMenu = ({ location, history: { push } }) => {
};
return (
-
+
Services
diff --git a/packages/cp-frontend/src/graphql/DeploymentGroupConfig.gql b/packages/cp-frontend/src/graphql/DeploymentGroupConfig.gql
index 48ca2c80..14ea3794 100644
--- a/packages/cp-frontend/src/graphql/DeploymentGroupConfig.gql
+++ b/packages/cp-frontend/src/graphql/DeploymentGroupConfig.gql
@@ -1,6 +1,6 @@
#import "./ServiceInfo.gql"
-mutation config($deploymentGroupName: String!, $type: ManifestType!, $format: ManifestFormat!, $raw: String!) {
+query config($deploymentGroupName: String!, $type: ManifestType!, $format: ManifestFormat!, $raw: String!) {
config(deploymentGroupName: $deploymentGroupName, type: $type, format: $format, raw: $raw) {
image
...ServiceInfo
diff --git a/packages/cp-frontend/src/graphql/DeploymentGroupImport.gql b/packages/cp-frontend/src/graphql/DeploymentGroupImport.gql
new file mode 100644
index 00000000..677aaf67
--- /dev/null
+++ b/packages/cp-frontend/src/graphql/DeploymentGroupImport.gql
@@ -0,0 +1,7 @@
+#import "./DeploymentGroupInfo.gql"
+
+mutation importDeploymentGroup($slug: String!) {
+ importDeploymentGroup(deploymentGroupSlug: $slug) {
+ ...DeploymentGroupInfo
+ }
+}
diff --git a/packages/cp-frontend/src/graphql/DeploymentGroupsImportable.gql b/packages/cp-frontend/src/graphql/DeploymentGroupsImportable.gql
new file mode 100644
index 00000000..1a32e12c
--- /dev/null
+++ b/packages/cp-frontend/src/graphql/DeploymentGroupsImportable.gql
@@ -0,0 +1,7 @@
+#import "./DeploymentGroupInfo.gql"
+
+query DeploymentGroupsImportable {
+ importableDeploymentGroups {
+ ...DeploymentGroupInfo
+ }
+}
diff --git a/packages/cp-frontend/src/router.js b/packages/cp-frontend/src/router.js
index 8de4c156..57721826 100644
--- a/packages/cp-frontend/src/router.js
+++ b/packages/cp-frontend/src/router.js
@@ -1,11 +1,15 @@
import React from 'react';
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom';
+import styled from 'styled-components';
import { Header, Breadcrumb, Menu } from '@containers/navigation';
+import { ServiceScale, ServiceDelete } from '@containers/service';
+import { InstanceList } from '@containers/instances';
import {
DeploymentGroupList,
- DeploymentGroupCreate
+ DeploymentGroupCreate,
+ DeploymentGroupImport
} from '@containers/deployment-groups';
import {
@@ -14,9 +18,12 @@ import {
ServicesMenu
} from '@containers/services';
-import { ServiceScale, ServiceDelete } from '@containers/service';
-
-import { InstanceList } from '@containers/instances';
+const Container = styled.div`
+ display: flex;
+ flex: 1 1 auto;
+ position: relative;
+ flex-flow: column;
+`;
const rootRedirect = p => ;
@@ -33,7 +40,7 @@ const serviceRedirect = p =>
const Router = (
-
+
@@ -65,6 +72,11 @@ const Router = (
exact
component={DeploymentGroupCreate}
/>
+
-
+
);
diff --git a/packages/cp-gql-schema/schema.gql b/packages/cp-gql-schema/schema.gql
index 59cb1d47..806e7477 100644
--- a/packages/cp-gql-schema/schema.gql
+++ b/packages/cp-gql-schema/schema.gql
@@ -19,7 +19,7 @@ type User {
type DeploymentGroup {
id: ID!
name: String!
- slug: String!
+ slug: String!
services(name: String, slug: String, parentId: ID): [Service]
version: Version
history: [Version]
@@ -186,6 +186,9 @@ type Query {
instance(id: ID!): Instance
datacenter(id: ID, region: String): Datacenter
datacenters: [Datacenter]
+
+ config(deploymentGroupName: String!, type: ManifestType!, format: ManifestFormat!, raw: String!): [Service]
+ importableDeploymentGroups: [DeploymentGroup]
}
type Mutation {
@@ -194,7 +197,6 @@ type Mutation {
provisionManifest(deploymentGroupId: ID!, type: ManifestType!, format: ManifestFormat!, raw: String!): Version
scale(serviceId: ID!, replicas: Int!): Version
- config(deploymentGroupName: String!, type: ManifestType!, format: ManifestFormat!, raw: String!): [Service]
stopServices(ids: [ID]!): [Service]
startServices(ids: [ID]!): [Service]
@@ -205,5 +207,5 @@ type Mutation {
startInstances(ids: [ID]!): [Instance]
restartInstances(ids: [ID]!): [Instance]
- # reprovision() ???
+ importDeploymentGroup(deploymentGroupSlug: String!): DeploymentGroup
}
diff --git a/packages/portal-api/lib/index.js b/packages/portal-api/lib/index.js
index d0e0c13c..f6337904 100644
--- a/packages/portal-api/lib/index.js
+++ b/packages/portal-api/lib/index.js
@@ -19,11 +19,16 @@ module.exports = function (server, options, next) {
}
const data = new PortalData(options.data);
-
- const watch = new PortalWatch(Object.assign(options.watch, {
+ const watcher = new PortalWatch(Object.assign(options.watch, {
data
}));
+ // watcher <-> watcher
+ // 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.on('error', (err) => {
server.log(['error'], err);
});
@@ -33,12 +38,10 @@ module.exports = function (server, options, next) {
return next(err);
}
- server.bind(Object.assign(data, {
- watch
- }));
+ server.bind(data);
Piloted.on('refresh', internals.refresh(data));
- watch.poll();
+ watcher.poll();
server.register([
{
diff --git a/packages/portal-api/lib/resolvers.js b/packages/portal-api/lib/resolvers.js
index bdd376cc..7f1f47d0 100644
--- a/packages/portal-api/lib/resolvers.js
+++ b/packages/portal-api/lib/resolvers.js
@@ -25,7 +25,9 @@ module.exports = (data) => {
'metricData',
'package',
'datacenters',
- 'instanceMetric'
+ 'instanceMetric',
+ 'config',
+ 'importableDeploymentGroups'
];
const mutations = [
@@ -33,14 +35,14 @@ module.exports = (data) => {
'updateDeploymentGroup',
'provisionManifest',
'scale',
- 'config',
'stopServices',
'startServices',
'restartServices',
'deleteServices',
'stopInstances',
'startInstances',
- 'restartInstances'
+ 'restartInstances',
+ 'importDeploymentGroup'
];
const resolvers = {};
diff --git a/packages/portal-data/lib/index.js b/packages/portal-data/lib/index.js
index 30df2506..1609047a 100644
--- a/packages/portal-data/lib/index.js
+++ b/packages/portal-data/lib/index.js
@@ -3,12 +3,23 @@
const ParamCase = require('param-case');
const EventEmitter = require('events');
const DockerClient = require('docker-compose-client');
+const { DEPLOYMENT_GROUP, SERVICE, HASH } = require('portal-watch');
const Dockerode = require('dockerode');
const Hoek = require('hoek');
const Penseur = require('penseur');
const VAsync = require('vasync');
+const uniqBy = require('lodash.uniqby');
const Transform = require('./transform');
const Uuid = require('uuid/v4');
+const Util = require('util');
+
+
+const NON_IMPORTABLE_STATES = [
+ 'EXITED',
+ 'DELETED',
+ 'STOPPED',
+ 'FAILED'
+];
const internals = {
defaults: {
@@ -51,12 +62,17 @@ module.exports = 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._dockerCompose.on('error', (err) => {
this.emit('error', err);
});
}
+ setWatcher (watcher) {
+ this._watcher = watcher;
+ }
+
connect (cb) {
this._db.establish(internals.tables, cb);
}
@@ -284,10 +300,8 @@ module.exports = class Data extends EventEmitter {
const deploymentGroup = deploymentGroups[0];
const getServices = (args) => {
- console.log(args);
args = args || {};
args.deploymentGroupId = deploymentGroup.id;
- console.log(args);
return new Promise((resolve, reject) => {
this.getServices(args, resolveCb(resolve, reject));
@@ -1285,7 +1299,7 @@ module.exports = class Data extends EventEmitter {
});
}
- config ({deploymentGroupName = '', type = '', format = '', raw = '' }, cb) {
+ getConfig ({deploymentGroupName = '', type = '', format = '', raw = '' }, cb) {
if (type.toUpperCase() !== 'COMPOSE') {
return cb(new Error('"COMPOSE" is the only `type` supported'));
}
@@ -1330,4 +1344,133 @@ module.exports = class Data extends EventEmitter {
}, []));
});
}
+
+ getImportableDeploymentGroups (args, cb) {
+ if (!this._watcher) {
+ return cb(null, []);
+ }
+
+ const machines = this._watcher.getContainers();
+
+ if (!Array.isArray(machines)) {
+ return cb(null, []);
+ }
+
+ return cb(
+ null,
+ uniqBy(
+ machines
+ .filter(({ state }) => { return NON_IMPORTABLE_STATES.indexOf(state.toUpperCase()) < 0; })
+ .filter(({ tags = {} }) => { return [DEPLOYMENT_GROUP, SERVICE, HASH].every((name) => { return tags[name]; }); }
+ )
+ .map(({ tags = {} }) => {
+ return ({
+ id: Uuid(),
+ name: tags[DEPLOYMENT_GROUP],
+ slug: ParamCase(tags[DEPLOYMENT_GROUP])
+ });
+ }),
+ 'slug'
+ )
+ );
+ }
+
+ importDeploymentGroup ({ deploymentGroupSlug }, cb) {
+ console.log(`-> import requested for ${deploymentGroupSlug}`);
+
+ if (!this._watcher) {
+ console.log('-> watcher not yet defined');
+ return cb(null, null);
+ }
+
+ const machines = this._watcher.getContainers();
+
+ if (!Array.isArray(machines)) {
+ console.log('-> no machines found');
+ return cb(null, null);
+ }
+
+ const containers = machines
+ .filter(
+ ({ tags = {} }) => { return tags[DEPLOYMENT_GROUP] && ParamCase(tags[DEPLOYMENT_GROUP]) === deploymentGroupSlug; }
+ )
+ .filter(
+ ({ state }) => { return NON_IMPORTABLE_STATES.indexOf(state.toUpperCase()) < 0; }
+ );
+
+ if (!containers.length) {
+ console.log(`-> no containers found for ${deploymentGroupSlug}`);
+ return cb(null, null);
+ }
+
+ const { tags = [] } = containers[0];
+
+ const services = containers.reduce((acc, { tags = [], id = '', state = '', name = '' }) => {
+ const hash = tags[HASH];
+ const slug = ParamCase(tags[SERVICE]);
+ const attr = `${hash}-${slug}`;
+
+ const instance = {
+ name: name,
+ machineId: id,
+ status: state.toUpperCase()
+ };
+
+ if (acc[attr]) {
+ acc[attr].instances.push(instance);
+ return acc;
+ }
+
+ return Object.assign(acc, {
+ [attr]: {
+ hash,
+ name: tags[SERVICE],
+ slug,
+ instances: [instance]
+ }
+ });
+ }, {});
+
+ const createService = (deploymentGroupId) => {
+ return (serviceId, next) => {
+ const service = services[serviceId];
+
+ console.log(`-> creating Service ${Util.inspect(service)}`);
+
+ VAsync.forEachParallel({
+ inputs: service.instances,
+ func: (instance, next) => { return this.createInstance(instance, next); }
+ }, (err, results) => {
+ if (err) {
+ return cb(err);
+ }
+
+ console.log(`-> created Instances ${Util.inspect(results.successes)}`);
+
+ this.createService(Object.assign(service, {
+ instances: results.successes,
+ deploymentGroupId
+ }), next);
+ });
+ };
+ };
+
+ const deploymentGroup = {
+ name: tags[DEPLOYMENT_GROUP],
+ slug: ParamCase(tags[DEPLOYMENT_GROUP])
+ };
+
+ console.log(`-> creating DeploymentGroup ${Util.inspect(deploymentGroup)}`);
+
+ this.createDeploymentGroup(deploymentGroup, (err, dg) => {
+ if (err) {
+ return cb(err);
+ }
+
+ VAsync.forEachParallel({
+ inputs: Object.keys(services),
+ func: createService(dg.id)
+ }, (err) => { return cb(err, dg); });
+ });
+ }
};
diff --git a/packages/portal-data/package.json b/packages/portal-data/package.json
index 5cb2291a..ae6cc67b 100644
--- a/packages/portal-data/package.json
+++ b/packages/portal-data/package.json
@@ -21,6 +21,8 @@
"hoek": "^4.1.1",
"param-case": "^2.1.1",
"penseur": "^7.12.3",
+ "lodash.uniqby": "^4.7.0",
+ "portal-watch": "^1.0.0",
"uuid": "^3.1.0",
"vasync": "^1.6.4",
"yamljs": "^0.2.10"
diff --git a/packages/portal-watch/lib/index.js b/packages/portal-watch/lib/index.js
index 40976420..2b9cbdf0 100644
--- a/packages/portal-watch/lib/index.js
+++ b/packages/portal-watch/lib/index.js
@@ -9,7 +9,6 @@ const DEPLOYMENT_GROUP = 'docker:label:com.docker.compose.project';
const SERVICE = 'docker:label:com.docker.compose.service';
const HASH = 'docker:label:com.docker.compose.config-hash';
-
module.exports = class Watcher {
constructor (options) {
options = options || {};
@@ -35,6 +34,10 @@ module.exports = class Watcher {
this._tritonWatch.poll();
}
+ getContainers () {
+ return this._tritonWatch.getContainers();
+ }
+
getDeploymentGroupId (name, cb) {
this._data.getDeploymentGroup({ name }, (err, deploymentGroup) => {
if (err) {
@@ -45,8 +48,8 @@ module.exports = class Watcher {
});
}
- getService ({ serviceName, deploymentGroupId }, cb) {
- this._data.getServices({ name: serviceName, deploymentGroupId }, (err, services) => {
+ getService ({ serviceName, serviceHash, deploymentGroupId }, cb) {
+ this._data.getServices({ name: serviceName, hash: serviceHash, deploymentGroupId }, (err, services) => {
if (err) {
return cb(err);
}
@@ -131,7 +134,7 @@ module.exports = class Watcher {
console.log('-> `change` event received', util.inspect(machine));
- const { id, tags = [] } = machine;
+ const { id, tags = {} } = machine;
// assert id existence
if (!id) {
@@ -175,7 +178,7 @@ module.exports = class Watcher {
// assert that service exists
const assertService = (deploymentGroupId) => {
- this.getService({ serviceName, deploymentGroupId }, handleError((service) => {
+ this.getService({ serviceName, serviceHash: tags[HASH], deploymentGroupId }, handleError((service) => {
if (!service) {
console.error(`Service "${serviceName}" form DeploymentGroup "${deploymentGroupName}" for machine ${id} not found`);
return;
@@ -200,3 +203,7 @@ module.exports = class Watcher {
assertDeploymentGroup();
}
};
+
+module.exports.DEPLOYMENT_GROUP = DEPLOYMENT_GROUP;
+module.exports.SERVICE = SERVICE;
+module.exports.HASH = HASH;
diff --git a/packages/portal-watch/package.json b/packages/portal-watch/package.json
index 04ac40a9..57a145c4 100644
--- a/packages/portal-watch/package.json
+++ b/packages/portal-watch/package.json
@@ -12,7 +12,7 @@
"test-ci": "echo 0 `# lab -c -r console -o stdout -r tap -o $CIRCLE_TEST_REPORTS/test/portal-watch.xml`"
},
"dependencies": {
- "triton-watch": "^1.0.1"
+ "triton-watch": "^1.1.0"
},
"devDependencies": {
"belly-button": "^3.1.0",
diff --git a/yarn.lock b/yarn.lock
index f7d74af9..46a67bfc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -42,8 +42,8 @@
resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.40.tgz#ac02de68e66c004a61b7cb16df8b1db3a254cca9"
"@types/graphql@^0.9.0":
- version "0.9.1"
- resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.1.tgz#b04ebe84bc997cc60dbea2ed4d0d4342c737f99d"
+ version "0.9.2"
+ resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.2.tgz#5e3a2919a998d0bd9bb86b4b23e9630425bff1b2"
"@types/isomorphic-fetch@0.0.34":
version "0.0.34"
@@ -305,15 +305,6 @@ apr-filter@^1.0.5:
apr-engine-sum "^1.0.3"
apr-map "^1.0.5"
-apr-find@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/apr-find/-/apr-find-1.0.5.tgz#e166abc66f53cfd08aadb3ecab38049faa378301"
- dependencies:
- apr-engine-each "^1.0.3"
- apr-engine-sum "^1.0.3"
- lodash.defaults "^4.2.0"
- lodash.isarraylike "^4.2.0"
-
apr-for-each@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/apr-for-each/-/apr-for-each-1.0.6.tgz#3947bb25fdb7b79a7f02bfa925fdb79576098903"
@@ -502,6 +493,10 @@ 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"
@@ -511,8 +506,8 @@ async@^1.4.0, async@^1.5.0:
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
async@^2.0.1, async@^2.1.2, async@^2.1.4:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7"
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d"
dependencies:
lodash "^4.14.0"
@@ -896,10 +891,10 @@ babel-plugin-istanbul@^4.1.4:
test-exclude "^4.1.1"
babel-plugin-styled-components@^1.1.4:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.1.4.tgz#b0e6d5bb01059bc7ab9118d3d686f6472ee8e91f"
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.1.5.tgz#ff2c8e0e2f3a0d3279e7454a7aaa2973749e714d"
dependencies:
- stylis "2.0.0"
+ stylis "^3.0.19"
babel-plugin-syntax-async-functions@^6.8.0:
version "6.13.0"
@@ -1370,7 +1365,7 @@ babelrc-rollup@3.0.0:
dependencies:
resolve "^1.1.7"
-babylon@^6.1.0, babylon@^6.10.0, babylon@^6.12.0, babylon@^6.13.0, babylon@^6.17.0, babylon@^6.17.2:
+babylon@^6.1.0, babylon@^6.10.0, babylon@^6.12.0, babylon@^6.17.0, babylon@^6.17.2, babylon@^6.17.4:
version "6.17.4"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a"
@@ -1766,12 +1761,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.30000693"
- resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000693.tgz#8510e7a9ab04adcca23a5dcefa34df9d28c1ce20"
+ version "1.0.30000694"
+ resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000694.tgz#02009f4f82d2f0126e4c691b7cd5adb351935c01"
caniuse-lite@^1.0.30000684:
- version "1.0.30000693"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000693.tgz#c9c6298697c71fdf6cb13eefe8aa93926f2f8613"
+ version "1.0.30000694"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000694.tgz#1492dab7c10c608c9d37a723e6e3e7873e0ce94f"
capture-stack-trace@^1.0.0:
version "1.0.0"
@@ -2027,8 +2022,8 @@ coleman-liau@^1.0.0:
resolved "https://registry.yarnpkg.com/coleman-liau/-/coleman-liau-1.0.0.tgz#de1f39901e164f49eff2a6ec88f3a9dbbb6686c1"
collapse-white-space@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.2.tgz#9c463fb9c6d190d2dcae21a356a01bcae9eeef6d"
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.3.tgz#4b906f670e5a963a87b76b0e1689643341b6023c"
color-convert@^1.0.0:
version "1.9.0"
@@ -2081,8 +2076,8 @@ command-join@^2.0.0:
resolved "https://registry.yarnpkg.com/command-join/-/command-join-2.0.0.tgz#52e8b984f4872d952ff1bdc8b98397d27c7144cf"
commander@2, commander@^2.7.1, commander@^2.8.1, commander@^2.9.0:
- version "2.9.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
+ version "2.10.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.10.0.tgz#e1f5d3245de246d1a5ca04702fa1ad1bd7e405fe"
dependencies:
graceful-readlink ">= 1.0.0"
@@ -3352,8 +3347,8 @@ eslint-plugin-hapi@4.x.x:
no-arrowception "1.x.x"
eslint-plugin-import@^2.3.0:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.5.0.tgz#293b5ea7910a901a05a47ccdd7546e611725406c"
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.6.0.tgz#2a4bbad36a078e052a3c830ce3dfbd6b8a12c6e5"
dependencies:
builtin-modules "^1.1.1"
contains-path "^0.1.0"
@@ -3498,7 +3493,7 @@ 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.1, esprima@~3.1.0:
version "3.1.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
@@ -5009,14 +5004,14 @@ istanbul-lib-hook@^1.0.7:
append-transform "^0.4.0"
istanbul-lib-instrument@^1.7.2:
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.2.tgz#6014b03d3470fb77638d5802508c255c06312e56"
+ version "1.7.3"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.3.tgz#925b239163eabdd68cc4048f52c2fa4f899ecfa7"
dependencies:
babel-generator "^6.18.0"
babel-template "^6.16.0"
babel-traverse "^6.18.0"
babel-types "^6.18.0"
- babylon "^6.13.0"
+ babylon "^6.17.4"
istanbul-lib-coverage "^1.1.1"
semver "^5.3.0"
@@ -5196,8 +5191,8 @@ jest-snapshot@^20.0.3:
pretty-format "^20.0.3"
jest-styled-components@^3.0.2:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-3.1.3.tgz#eba50074b426e36fd2be99187dffa4b5569eab00"
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-3.1.5.tgz#c1e4fc60e534e54b26c3b733b65b44afd4f2795e"
dependencies:
css "^2.2.1"
@@ -5460,8 +5455,8 @@ known-css-properties@^0.2.0:
resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.2.0.tgz#899c94be368e55b42d7db8d5be7d73a4a4a41454"
lab@^14.0.1:
- version "14.0.1"
- resolved "https://registry.yarnpkg.com/lab/-/lab-14.0.1.tgz#1a20100ecc692dbf91e849cd6b945b8e016a0527"
+ version "14.1.0"
+ resolved "https://registry.yarnpkg.com/lab/-/lab-14.1.0.tgz#2632b9a416d5f391d9a6f1d98d607b0d69f75629"
dependencies:
bossy "3.x.x"
code "4.1.x"
@@ -5934,8 +5929,8 @@ matcher@^0.1.1:
escape-string-regexp "^1.0.4"
mathml-tag-names@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.0.0.tgz#eee615112a2b127e70f558d69c9ebe14076503d7"
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.0.1.tgz#8d41268168bf86d1102b98109e28e531e7a34578"
max-safe-int@^1.0.0:
version "1.0.0"
@@ -6916,8 +6911,8 @@ podium@1.x.x:
joi "10.x.x"
polished@^1.1.3:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/polished/-/polished-1.2.0.tgz#241cbbbd848d62ca8776eee618383d72e95830c5"
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/polished/-/polished-1.2.1.tgz#83c18a85bf9d7023477cfc7049763b657d50f0f7"
pos@0.4.2:
version "0.4.2"
@@ -7036,8 +7031,8 @@ pretty-ms@^2.0.0:
plur "^1.0.0"
primer-support@*:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/primer-support/-/primer-support-4.0.0.tgz#3dbbb37e4e0f2ed2ea6035e0b79dd0cb33bae85e"
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/primer-support/-/primer-support-4.0.4.tgz#7f16ad577a61a960d0bccd72f2a6df506a14d69f"
primer-utilities@^3.0.0:
version "3.5.0"
@@ -7526,7 +7521,7 @@ readline2@^1.0.1:
is-fullwidth-code-point "^1.0.0"
mute-stream "0.0.5"
-recast@0.11.12, recast@^0.11.5:
+recast@0.11.12:
version "0.11.12"
resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.12.tgz#a79e4d3f82d5d72a82ee177aeaa791e793bbe5d6"
dependencies:
@@ -7535,6 +7530,15 @@ recast@0.11.12, recast@^0.11.5:
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"
@@ -8314,8 +8318,8 @@ spache@^1.1.0:
resolved "https://registry.yarnpkg.com/spache/-/spache-1.1.0.tgz#8c68ba807630f0199429c2035c82ed96f5438cd5"
spawn-wrap@^1.3.6:
- version "1.3.6"
- resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.3.6.tgz#ccec4a949d8ce7e2b1a35cf4671d683d2e76a1d1"
+ version "1.3.7"
+ resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.3.7.tgz#beb8bf4426d64b2b06871e0d7dee2643f1f8d1bc"
dependencies:
foreground-child "^1.5.6"
mkdirp "^0.5.0"
@@ -8714,10 +8718,6 @@ stylelint@^7.0.0, stylelint@^7.0.3, stylelint@^7.11.1:
svg-tags "^1.0.0"
table "^4.0.1"
-stylis@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/stylis/-/stylis-2.0.0.tgz#6785a6546bd73478799a67d49d67086953b50ad5"
-
stylis@^3.0.19:
version "3.1.9"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.1.9.tgz#638370451f980437f57c59e58d2e296be29fafb7"
@@ -9128,9 +9128,9 @@ trim@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd"
-triton-watch@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/triton-watch/-/triton-watch-1.0.1.tgz#b1087f6a57383f1e83d0a308e65110ca9a2a38d0"
+triton-watch@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/triton-watch/-/triton-watch-1.1.0.tgz#d2b47fbf6a45174198c196152bb86c696a923c35"
dependencies:
triton "5.2.x"