import existing deployment groups (#516)

* refactor: config() from mutation to query

* refactor(cp-frontend): remove unused dependency

* feat(portal-watch): expose getContainers() and compose tags

* fix(portal-watch): assert services by name AND hash

* feat(portal-data): expose importable and import APIs

* fix(portal-data): add missing dependencies

* feat(portal-api): expose import/importable

* feat(cp-gql-schema): add import/importable

* feat(cp-frontend): import existing project

* style(portal-watch): lint

* style(portal-data): lint

* chore: update lockfile
This commit is contained in:
Sérgio Ramos 2017-06-26 15:29:12 +01:00 committed by Wyatt Preul
parent 59043760b6
commit 7f1d731dc6
28 changed files with 493 additions and 165 deletions

View File

@ -5,6 +5,7 @@
"new-cap": 0, "new-cap": 0,
// temp // temp
"no-undef": 1, "no-undef": 1,
"no-debugger": 1 "no-debugger": 1,
"no-negated-condition": 0
} }
} }

View File

@ -53,7 +53,6 @@
"unitcalc": "^1.0.8" "unitcalc": "^1.0.8"
}, },
"devDependencies": { "devDependencies": {
"apr-find": "^1.0.5",
"apr-for-each": "^1.0.6", "apr-for-each": "^1.0.6",
"apr-main": "^1.0.7", "apr-main": "^1.0.7",
"babel-plugin-inline-react-svg": "^0.4.0", "babel-plugin-inline-react-svg": "^0.4.0",

View File

@ -14,6 +14,15 @@
border: solid 1px #d8d8d8; border: solid 1px #d8d8d8;
margin-bottom: 8px; margin-bottom: 8px;
} }
html, body, #root {
height: 100%;
}
#root {
display: flex;
flex-flow: column;
}
</style> </style>
</head> </head>
<body> <body>

View File

@ -1,7 +1,6 @@
const { readFile, writeFile, exists } = require('mz/fs'); const { readFile, writeFile, exists } = require('mz/fs');
const main = require('apr-main'); const main = require('apr-main');
const forEach = require('apr-for-each'); const forEach = require('apr-for-each');
const find = require('apr-find');
const path = require('path'); const path = require('path');
const ROOT = path.join(__dirname, '../node_modules/react-scripts/config'); const ROOT = path.join(__dirname, '../node_modules/react-scripts/config');

View File

@ -1,5 +1,4 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Article } from 'normalized-styled-components';
import { ThemeProvider, injectGlobal } from 'styled-components'; import { ThemeProvider, injectGlobal } from 'styled-components';
import { theme, global } from 'joyent-ui-toolkit'; import { theme, global } from 'joyent-ui-toolkit';
import { ApolloProvider } from 'react-apollo'; import { ApolloProvider } from 'react-apollo';
@ -19,9 +18,7 @@ class App extends Component {
return ( return (
<ApolloProvider client={client} store={store}> <ApolloProvider client={client} store={store}>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<Article>
{Router} {Router}
</Article>
</ThemeProvider> </ThemeProvider>
</ApolloProvider> </ApolloProvider>
); );

View File

@ -1,10 +0,0 @@
import React from 'react';
import { Col, Row } from 'react-styled-flexboxgrid';
export default () =>
<Row>
<Col xs={12}>
<p>you don't have any deployment groups</p>
</Col>
</Row>;

View File

@ -1 +1,2 @@
export { default as EmptyDeployementGroups } from './empty'; export { default as DeploymentGroupsLoading } from './loading';
export { default as CreateDeploymentGroup } from './create';

View File

@ -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 () =>
<LoadingRow center="xs" around="xs" middle="xs">
<Col xs={1}>
<Dots2 />
</Col>
</LoadingRow>;

View File

@ -1,3 +1,13 @@
import { Grid } from 'react-styled-flexboxgrid'; 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;
`};
`;

View File

@ -19,7 +19,6 @@ const BreadcrumbLink = styled(Link)`
const BreadcrumbContainer = styled.div` const BreadcrumbContainer = styled.div`
border-bottom: solid ${remcalc(1)} ${props => props.theme.grey}; border-bottom: solid ${remcalc(1)} ${props => props.theme.grey};
margin-bottom: ${remcalc(19)};
`; `;
const getBreadcrumbItems = (...links) => const getBreadcrumbItems = (...links) =>

View File

@ -22,7 +22,7 @@ const getMenuItems = (...links) =>
); );
const Menu = ({ links = [] }) => const Menu = ({ links = [] }) =>
<LayoutContainer> <LayoutContainer plain>
<SectionList> <SectionList>
{getMenuItems(...links)} {getMenuItems(...links)}
</SectionList> </SectionList>

View File

@ -8,7 +8,7 @@ import paramCase from 'param-case';
import DeploymentGroupBySlug from '@graphql/DeploymentGroupBySlug.gql'; import DeploymentGroupBySlug from '@graphql/DeploymentGroupBySlug.gql';
import DeploymentGroupCreateMutation from '@graphql/DeploymentGroupCreate.gql'; import DeploymentGroupCreateMutation from '@graphql/DeploymentGroupCreate.gql';
import DeploymentGroupProvisionMutation from '@graphql/DeploymentGroupProvision.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 { client } from '@state/store';
import { LayoutContainer } from '@components/layout'; import { LayoutContainer } from '@components/layout';
@ -262,7 +262,7 @@ export default compose(
provisionManifest: variables => mutate({ variables }) provisionManifest: variables => mutate({ variables })
}) })
}), }),
graphql(DeploymentGroupConfigMutation, { graphql(DeploymentGroupConfigQuery, {
props: ({ mutate }) => ({ props: ({ mutate }) => ({
config: variables => mutate({ variables }) config: variables => mutate({ variables })
}) })

View File

@ -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 (
<LayoutContainer>
<H2>Importing deployment group</H2>
{loading && <DeploymentGroupsLoading />}
{error && <span>{error.toString()}</span>}
</LayoutContainer>
);
}
}
export default graphql(DeploymentGroupImportMutation, {
props: ({ mutate }) => ({
importDeploymentGroup: variables => mutate({ variables })
})
})(DeploymentGroupImport);

View File

@ -1,2 +1,3 @@
export { default as DeploymentGroupList } from './list'; export { default as DeploymentGroupList } from './list';
export { default as DeploymentGroupCreate } from './create'; export { default as DeploymentGroupCreate } from './create';
export { default as DeploymentGroupImport } from './import';

View File

@ -1,73 +1,133 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { graphql } from 'react-apollo'; import { compose, graphql } from 'react-apollo';
import { Link } from 'react-router-dom'; 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 { Col, Row } from 'react-styled-flexboxgrid';
import forceArray from 'force-array';
import remcalc from 'remcalc';
import { LayoutContainer } from '@components/layout'; import { LayoutContainer } from '@components/layout';
import { Loader, ErrorMessage } from '@components/messaging'; import { Loader, ErrorMessage } from '@components/messaging';
import { EmptyDeployementGroups } from '@components/deployment-groups'; import { DeploymentGroupsLoading } from '@components/deployment-groups';
import { Button } from 'joyent-ui-toolkit'; 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 = ({ const DeploymentGroupList = ({
location, location,
deploymentGroups, deploymentGroups,
importable,
loading, loading,
error error,
match
}) => { }) => {
if (loading) { const _loading = !loading ? null : <DeploymentGroupsLoading />;
return (
<LayoutContainer> // todo improve this error message style according to new designs
<Loader /> const _error = !error
</LayoutContainer> ? null
); : <Row>
} else if (error) {
return (
<LayoutContainer>
<ErrorMessage message="Oops, and error occured while loading your deployment groups." /> <ErrorMessage message="Oops, and error occured while loading your deployment groups." />
</LayoutContainer> </Row>;
);
}
let emptyDeployementGroups = null; const groups = forceArray(deploymentGroups).map(({ slug, name }) =>
let deploymentGroupList = null; <Col xs={12} sm={4} md={3} lg={3} key={slug}>
<Box to={`${match.path}/${slug}`}>
if (deploymentGroups.length) { <ServiceTitle>{name}</ServiceTitle>
const list = deploymentGroups.map((deploymentGroup, index) => { </Box>
return (
<p key={index}>
<Link to={`${location.pathname}/${deploymentGroup.slug}/services`}>
{deploymentGroup.name}
</Link>
</p>
);
});
deploymentGroupList = (
<Row>
<Col>
<ul>
{list}
</ul>
</Col> </Col>
</Row>
); );
} else {
emptyDeployementGroups = <EmptyDeployementGroups />; const create = [
} <Col xs={12} sm={4} md={3} lg={3} key="~create">
<BoxCreate to={`${match.path}/~create`}>
<Oval>+</Oval>
<CreateTitle>Create new deployment group</CreateTitle>
</BoxCreate>
</Col>
].concat(
forceArray(importable).map(({ slug, name }) =>
<Col xs={12} sm={4} md={3} lg={3} key={slug}>
<BoxCreate to={`${match.path}/~import/${slug}`}>
<Oval>&#10549;</Oval>
<CreateTitle>{name}</CreateTitle>
</BoxCreate>
</Col>
)
);
return ( return (
<LayoutContainer> <LayoutContainer>
{emptyDeployementGroups} <Title>Deployment groups</Title>
<Row> {_loading}
<Col xs={12}> {_error}
<Button to={`/deployment-groups/~create`}> <DGsRows>
Create new {groups}
</Button> {create}
</Col> </DGsRows>
</Row>
{deploymentGroupList}
</LayoutContainer> </LayoutContainer>
); );
}; };
@ -81,7 +141,8 @@ DeploymentGroupList.propTypes = {
) )
}; };
const DeploymentGroupListWithData = graphql(DeploymentGroupsQuery, { export default compose(
graphql(DeploymentGroupsQuery, {
options: { options: {
pollInterval: 1000 pollInterval: 1000
}, },
@ -90,6 +151,10 @@ const DeploymentGroupListWithData = graphql(DeploymentGroupsQuery, {
loading, loading,
error error
}) })
})(DeploymentGroupList); }),
graphql(DeploymentGroupsImportableQuery, {
export default DeploymentGroupListWithData; props: ({ data: { importableDeploymentGroups } }) => ({
importable: importableDeploymentGroups
})
})
)(DeploymentGroupList);

View File

@ -65,7 +65,7 @@ const ServicesMenu = ({ location, history: { push } }) => {
}; };
return ( return (
<LayoutContainer> <LayoutContainer plain>
<H2>Services</H2> <H2>Services</H2>
<PaddedRow> <PaddedRow>
<Col xs={5}> <Col xs={5}>

View File

@ -1,6 +1,6 @@
#import "./ServiceInfo.gql" #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) { config(deploymentGroupName: $deploymentGroupName, type: $type, format: $format, raw: $raw) {
image image
...ServiceInfo ...ServiceInfo

View File

@ -0,0 +1,7 @@
#import "./DeploymentGroupInfo.gql"
mutation importDeploymentGroup($slug: String!) {
importDeploymentGroup(deploymentGroupSlug: $slug) {
...DeploymentGroupInfo
}
}

View File

@ -0,0 +1,7 @@
#import "./DeploymentGroupInfo.gql"
query DeploymentGroupsImportable {
importableDeploymentGroups {
...DeploymentGroupInfo
}
}

View File

@ -1,11 +1,15 @@
import React from 'react'; import React from 'react';
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'; import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom';
import styled from 'styled-components';
import { Header, Breadcrumb, Menu } from '@containers/navigation'; import { Header, Breadcrumb, Menu } from '@containers/navigation';
import { ServiceScale, ServiceDelete } from '@containers/service';
import { InstanceList } from '@containers/instances';
import { import {
DeploymentGroupList, DeploymentGroupList,
DeploymentGroupCreate DeploymentGroupCreate,
DeploymentGroupImport
} from '@containers/deployment-groups'; } from '@containers/deployment-groups';
import { import {
@ -14,9 +18,12 @@ import {
ServicesMenu ServicesMenu
} from '@containers/services'; } from '@containers/services';
import { ServiceScale, ServiceDelete } from '@containers/service'; const Container = styled.div`
display: flex;
import { InstanceList } from '@containers/instances'; flex: 1 1 auto;
position: relative;
flex-flow: column;
`;
const rootRedirect = p => <Redirect to="/deployment-groups" />; const rootRedirect = p => <Redirect to="/deployment-groups" />;
@ -33,7 +40,7 @@ const serviceRedirect = p =>
const Router = ( const Router = (
<BrowserRouter> <BrowserRouter>
<div style={{ position: 'relative' }}> <Container>
<Route path="/" component={Header} /> <Route path="/" component={Header} />
@ -65,6 +72,11 @@ const Router = (
exact exact
component={DeploymentGroupCreate} component={DeploymentGroupCreate}
/> />
<Route
path="/deployment-groups/~import/:slug"
exact
component={DeploymentGroupImport}
/>
<Route <Route
path="/deployment-groups/:deploymentGroup" path="/deployment-groups/:deploymentGroup"
exact exact
@ -132,7 +144,7 @@ const Router = (
component={InstanceList} component={InstanceList}
/> />
</div> </Container>
</BrowserRouter> </BrowserRouter>
); );

View File

@ -186,6 +186,9 @@ type Query {
instance(id: ID!): Instance instance(id: ID!): Instance
datacenter(id: ID, region: String): Datacenter datacenter(id: ID, region: String): Datacenter
datacenters: [Datacenter] datacenters: [Datacenter]
config(deploymentGroupName: String!, type: ManifestType!, format: ManifestFormat!, raw: String!): [Service]
importableDeploymentGroups: [DeploymentGroup]
} }
type Mutation { type Mutation {
@ -194,7 +197,6 @@ type Mutation {
provisionManifest(deploymentGroupId: ID!, type: ManifestType!, format: ManifestFormat!, raw: String!): Version provisionManifest(deploymentGroupId: ID!, type: ManifestType!, format: ManifestFormat!, raw: String!): Version
scale(serviceId: ID!, replicas: Int!): Version scale(serviceId: ID!, replicas: Int!): Version
config(deploymentGroupName: String!, type: ManifestType!, format: ManifestFormat!, raw: String!): [Service]
stopServices(ids: [ID]!): [Service] stopServices(ids: [ID]!): [Service]
startServices(ids: [ID]!): [Service] startServices(ids: [ID]!): [Service]
@ -205,5 +207,5 @@ type Mutation {
startInstances(ids: [ID]!): [Instance] startInstances(ids: [ID]!): [Instance]
restartInstances(ids: [ID]!): [Instance] restartInstances(ids: [ID]!): [Instance]
# reprovision() ??? importDeploymentGroup(deploymentGroupSlug: String!): DeploymentGroup
} }

View File

@ -19,11 +19,16 @@ module.exports = function (server, options, next) {
} }
const data = new PortalData(options.data); const data = new PortalData(options.data);
const watcher = new PortalWatch(Object.assign(options.watch, {
const watch = new PortalWatch(Object.assign(options.watch, {
data 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) => { data.on('error', (err) => {
server.log(['error'], err); server.log(['error'], err);
}); });
@ -33,12 +38,10 @@ module.exports = function (server, options, next) {
return next(err); return next(err);
} }
server.bind(Object.assign(data, { server.bind(data);
watch
}));
Piloted.on('refresh', internals.refresh(data)); Piloted.on('refresh', internals.refresh(data));
watch.poll(); watcher.poll();
server.register([ server.register([
{ {

View File

@ -25,7 +25,9 @@ module.exports = (data) => {
'metricData', 'metricData',
'package', 'package',
'datacenters', 'datacenters',
'instanceMetric' 'instanceMetric',
'config',
'importableDeploymentGroups'
]; ];
const mutations = [ const mutations = [
@ -33,14 +35,14 @@ module.exports = (data) => {
'updateDeploymentGroup', 'updateDeploymentGroup',
'provisionManifest', 'provisionManifest',
'scale', 'scale',
'config',
'stopServices', 'stopServices',
'startServices', 'startServices',
'restartServices', 'restartServices',
'deleteServices', 'deleteServices',
'stopInstances', 'stopInstances',
'startInstances', 'startInstances',
'restartInstances' 'restartInstances',
'importDeploymentGroup'
]; ];
const resolvers = {}; const resolvers = {};

View File

@ -3,12 +3,23 @@
const ParamCase = require('param-case'); const ParamCase = require('param-case');
const EventEmitter = require('events'); const EventEmitter = require('events');
const DockerClient = require('docker-compose-client'); const DockerClient = require('docker-compose-client');
const { DEPLOYMENT_GROUP, SERVICE, HASH } = require('portal-watch');
const Dockerode = require('dockerode'); const Dockerode = require('dockerode');
const Hoek = require('hoek'); const Hoek = require('hoek');
const Penseur = require('penseur'); const Penseur = require('penseur');
const VAsync = require('vasync'); const VAsync = require('vasync');
const uniqBy = require('lodash.uniqby');
const Transform = require('./transform'); const Transform = require('./transform');
const Uuid = require('uuid/v4'); const Uuid = require('uuid/v4');
const Util = require('util');
const NON_IMPORTABLE_STATES = [
'EXITED',
'DELETED',
'STOPPED',
'FAILED'
];
const internals = { const internals = {
defaults: { defaults: {
@ -51,12 +62,17 @@ module.exports = class Data extends EventEmitter {
this._db = new Penseur.Db(settings.name, settings.db); this._db = new Penseur.Db(settings.name, settings.db);
this._dockerCompose = new DockerClient(settings.dockerComposeHost); this._dockerCompose = new DockerClient(settings.dockerComposeHost);
this._docker = new Dockerode(settings.docker); this._docker = new Dockerode(settings.docker);
this._watcher = null;
this._dockerCompose.on('error', (err) => { this._dockerCompose.on('error', (err) => {
this.emit('error', err); this.emit('error', err);
}); });
} }
setWatcher (watcher) {
this._watcher = watcher;
}
connect (cb) { connect (cb) {
this._db.establish(internals.tables, cb); this._db.establish(internals.tables, cb);
} }
@ -284,10 +300,8 @@ module.exports = class Data extends EventEmitter {
const deploymentGroup = deploymentGroups[0]; const deploymentGroup = deploymentGroups[0];
const getServices = (args) => { const getServices = (args) => {
console.log(args);
args = args || {}; args = args || {};
args.deploymentGroupId = deploymentGroup.id; args.deploymentGroupId = deploymentGroup.id;
console.log(args);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.getServices(args, resolveCb(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') { if (type.toUpperCase() !== 'COMPOSE') {
return cb(new Error('"COMPOSE" is the only `type` supported')); 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); });
});
}
}; };

View File

@ -21,6 +21,8 @@
"hoek": "^4.1.1", "hoek": "^4.1.1",
"param-case": "^2.1.1", "param-case": "^2.1.1",
"penseur": "^7.12.3", "penseur": "^7.12.3",
"lodash.uniqby": "^4.7.0",
"portal-watch": "^1.0.0",
"uuid": "^3.1.0", "uuid": "^3.1.0",
"vasync": "^1.6.4", "vasync": "^1.6.4",
"yamljs": "^0.2.10" "yamljs": "^0.2.10"

View File

@ -9,7 +9,6 @@ const DEPLOYMENT_GROUP = 'docker:label:com.docker.compose.project';
const SERVICE = 'docker:label:com.docker.compose.service'; const SERVICE = 'docker:label:com.docker.compose.service';
const HASH = 'docker:label:com.docker.compose.config-hash'; const HASH = 'docker:label:com.docker.compose.config-hash';
module.exports = class Watcher { module.exports = class Watcher {
constructor (options) { constructor (options) {
options = options || {}; options = options || {};
@ -35,6 +34,10 @@ module.exports = class Watcher {
this._tritonWatch.poll(); this._tritonWatch.poll();
} }
getContainers () {
return this._tritonWatch.getContainers();
}
getDeploymentGroupId (name, cb) { getDeploymentGroupId (name, cb) {
this._data.getDeploymentGroup({ name }, (err, deploymentGroup) => { this._data.getDeploymentGroup({ name }, (err, deploymentGroup) => {
if (err) { if (err) {
@ -45,8 +48,8 @@ module.exports = class Watcher {
}); });
} }
getService ({ serviceName, deploymentGroupId }, cb) { getService ({ serviceName, serviceHash, deploymentGroupId }, cb) {
this._data.getServices({ name: serviceName, deploymentGroupId }, (err, services) => { this._data.getServices({ name: serviceName, hash: serviceHash, deploymentGroupId }, (err, services) => {
if (err) { if (err) {
return cb(err); return cb(err);
} }
@ -131,7 +134,7 @@ module.exports = class Watcher {
console.log('-> `change` event received', util.inspect(machine)); console.log('-> `change` event received', util.inspect(machine));
const { id, tags = [] } = machine; const { id, tags = {} } = machine;
// assert id existence // assert id existence
if (!id) { if (!id) {
@ -175,7 +178,7 @@ module.exports = class Watcher {
// assert that service exists // assert that service exists
const assertService = (deploymentGroupId) => { const assertService = (deploymentGroupId) => {
this.getService({ serviceName, deploymentGroupId }, handleError((service) => { this.getService({ serviceName, serviceHash: tags[HASH], deploymentGroupId }, handleError((service) => {
if (!service) { if (!service) {
console.error(`Service "${serviceName}" form DeploymentGroup "${deploymentGroupName}" for machine ${id} not found`); console.error(`Service "${serviceName}" form DeploymentGroup "${deploymentGroupName}" for machine ${id} not found`);
return; return;
@ -200,3 +203,7 @@ module.exports = class Watcher {
assertDeploymentGroup(); assertDeploymentGroup();
} }
}; };
module.exports.DEPLOYMENT_GROUP = DEPLOYMENT_GROUP;
module.exports.SERVICE = SERVICE;
module.exports.HASH = HASH;

View File

@ -12,7 +12,7 @@
"test-ci": "echo 0 `# lab -c -r console -o stdout -r tap -o $CIRCLE_TEST_REPORTS/test/portal-watch.xml`" "test-ci": "echo 0 `# lab -c -r console -o stdout -r tap -o $CIRCLE_TEST_REPORTS/test/portal-watch.xml`"
}, },
"dependencies": { "dependencies": {
"triton-watch": "^1.0.1" "triton-watch": "^1.1.0"
}, },
"devDependencies": { "devDependencies": {
"belly-button": "^3.1.0", "belly-button": "^3.1.0",

102
yarn.lock
View File

@ -42,8 +42,8 @@
resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.40.tgz#ac02de68e66c004a61b7cb16df8b1db3a254cca9" resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.40.tgz#ac02de68e66c004a61b7cb16df8b1db3a254cca9"
"@types/graphql@^0.9.0": "@types/graphql@^0.9.0":
version "0.9.1" version "0.9.2"
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.1.tgz#b04ebe84bc997cc60dbea2ed4d0d4342c737f99d" resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.2.tgz#5e3a2919a998d0bd9bb86b4b23e9630425bff1b2"
"@types/isomorphic-fetch@0.0.34": "@types/isomorphic-fetch@0.0.34":
version "0.0.34" version "0.0.34"
@ -305,15 +305,6 @@ apr-filter@^1.0.5:
apr-engine-sum "^1.0.3" apr-engine-sum "^1.0.3"
apr-map "^1.0.5" 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: apr-for-each@^1.0.6:
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/apr-for-each/-/apr-for-each-1.0.6.tgz#3947bb25fdb7b79a7f02bfa925fdb79576098903" 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" version "0.9.0"
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.0.tgz#c8721c8747ae4d5b29b929e99c5317b4e8745623" 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: async-each@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" 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" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
async@^2.0.1, async@^2.1.2, async@^2.1.4: async@^2.0.1, async@^2.1.2, async@^2.1.4:
version "2.4.1" version "2.5.0"
resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7" resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d"
dependencies: dependencies:
lodash "^4.14.0" lodash "^4.14.0"
@ -896,10 +891,10 @@ babel-plugin-istanbul@^4.1.4:
test-exclude "^4.1.1" test-exclude "^4.1.1"
babel-plugin-styled-components@^1.1.4: babel-plugin-styled-components@^1.1.4:
version "1.1.4" version "1.1.5"
resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.1.4.tgz#b0e6d5bb01059bc7ab9118d3d686f6472ee8e91f" resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.1.5.tgz#ff2c8e0e2f3a0d3279e7454a7aaa2973749e714d"
dependencies: dependencies:
stylis "2.0.0" stylis "^3.0.19"
babel-plugin-syntax-async-functions@^6.8.0: babel-plugin-syntax-async-functions@^6.8.0:
version "6.13.0" version "6.13.0"
@ -1370,7 +1365,7 @@ babelrc-rollup@3.0.0:
dependencies: dependencies:
resolve "^1.1.7" 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" version "6.17.4"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a" 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" 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: caniuse-db@^1.0.30000187, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
version "1.0.30000693" version "1.0.30000694"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000693.tgz#8510e7a9ab04adcca23a5dcefa34df9d28c1ce20" resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000694.tgz#02009f4f82d2f0126e4c691b7cd5adb351935c01"
caniuse-lite@^1.0.30000684: caniuse-lite@^1.0.30000684:
version "1.0.30000693" version "1.0.30000694"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000693.tgz#c9c6298697c71fdf6cb13eefe8aa93926f2f8613" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000694.tgz#1492dab7c10c608c9d37a723e6e3e7873e0ce94f"
capture-stack-trace@^1.0.0: capture-stack-trace@^1.0.0:
version "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" resolved "https://registry.yarnpkg.com/coleman-liau/-/coleman-liau-1.0.0.tgz#de1f39901e164f49eff2a6ec88f3a9dbbb6686c1"
collapse-white-space@^1.0.2: collapse-white-space@^1.0.2:
version "1.0.2" version "1.0.3"
resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.2.tgz#9c463fb9c6d190d2dcae21a356a01bcae9eeef6d" resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.3.tgz#4b906f670e5a963a87b76b0e1689643341b6023c"
color-convert@^1.0.0: color-convert@^1.0.0:
version "1.9.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" 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: commander@2, commander@^2.7.1, commander@^2.8.1, commander@^2.9.0:
version "2.9.0" version "2.10.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" resolved "https://registry.yarnpkg.com/commander/-/commander-2.10.0.tgz#e1f5d3245de246d1a5ca04702fa1ad1bd7e405fe"
dependencies: dependencies:
graceful-readlink ">= 1.0.0" graceful-readlink ">= 1.0.0"
@ -3352,8 +3347,8 @@ eslint-plugin-hapi@4.x.x:
no-arrowception "1.x.x" no-arrowception "1.x.x"
eslint-plugin-import@^2.3.0: eslint-plugin-import@^2.3.0:
version "2.5.0" version "2.6.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.5.0.tgz#293b5ea7910a901a05a47ccdd7546e611725406c" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.6.0.tgz#2a4bbad36a078e052a3c830ce3dfbd6b8a12c6e5"
dependencies: dependencies:
builtin-modules "^1.1.1" builtin-modules "^1.1.1"
contains-path "^0.1.0" contains-path "^0.1.0"
@ -3498,7 +3493,7 @@ esprima@^2.6.0, esprima@~2.7.1:
version "2.7.3" version "2.7.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" 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" version "3.1.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" 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" append-transform "^0.4.0"
istanbul-lib-instrument@^1.7.2: istanbul-lib-instrument@^1.7.2:
version "1.7.2" version "1.7.3"
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.2.tgz#6014b03d3470fb77638d5802508c255c06312e56" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.3.tgz#925b239163eabdd68cc4048f52c2fa4f899ecfa7"
dependencies: dependencies:
babel-generator "^6.18.0" babel-generator "^6.18.0"
babel-template "^6.16.0" babel-template "^6.16.0"
babel-traverse "^6.18.0" babel-traverse "^6.18.0"
babel-types "^6.18.0" babel-types "^6.18.0"
babylon "^6.13.0" babylon "^6.17.4"
istanbul-lib-coverage "^1.1.1" istanbul-lib-coverage "^1.1.1"
semver "^5.3.0" semver "^5.3.0"
@ -5196,8 +5191,8 @@ jest-snapshot@^20.0.3:
pretty-format "^20.0.3" pretty-format "^20.0.3"
jest-styled-components@^3.0.2: jest-styled-components@^3.0.2:
version "3.1.3" version "3.1.5"
resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-3.1.3.tgz#eba50074b426e36fd2be99187dffa4b5569eab00" resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-3.1.5.tgz#c1e4fc60e534e54b26c3b733b65b44afd4f2795e"
dependencies: dependencies:
css "^2.2.1" 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" resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.2.0.tgz#899c94be368e55b42d7db8d5be7d73a4a4a41454"
lab@^14.0.1: lab@^14.0.1:
version "14.0.1" version "14.1.0"
resolved "https://registry.yarnpkg.com/lab/-/lab-14.0.1.tgz#1a20100ecc692dbf91e849cd6b945b8e016a0527" resolved "https://registry.yarnpkg.com/lab/-/lab-14.1.0.tgz#2632b9a416d5f391d9a6f1d98d607b0d69f75629"
dependencies: dependencies:
bossy "3.x.x" bossy "3.x.x"
code "4.1.x" code "4.1.x"
@ -5934,8 +5929,8 @@ matcher@^0.1.1:
escape-string-regexp "^1.0.4" escape-string-regexp "^1.0.4"
mathml-tag-names@^2.0.0: mathml-tag-names@^2.0.0:
version "2.0.0" version "2.0.1"
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.0.0.tgz#eee615112a2b127e70f558d69c9ebe14076503d7" resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.0.1.tgz#8d41268168bf86d1102b98109e28e531e7a34578"
max-safe-int@^1.0.0: max-safe-int@^1.0.0:
version "1.0.0" version "1.0.0"
@ -6916,8 +6911,8 @@ podium@1.x.x:
joi "10.x.x" joi "10.x.x"
polished@^1.1.3: polished@^1.1.3:
version "1.2.0" version "1.2.1"
resolved "https://registry.yarnpkg.com/polished/-/polished-1.2.0.tgz#241cbbbd848d62ca8776eee618383d72e95830c5" resolved "https://registry.yarnpkg.com/polished/-/polished-1.2.1.tgz#83c18a85bf9d7023477cfc7049763b657d50f0f7"
pos@0.4.2: pos@0.4.2:
version "0.4.2" version "0.4.2"
@ -7036,8 +7031,8 @@ pretty-ms@^2.0.0:
plur "^1.0.0" plur "^1.0.0"
primer-support@*: primer-support@*:
version "4.0.0" version "4.0.4"
resolved "https://registry.yarnpkg.com/primer-support/-/primer-support-4.0.0.tgz#3dbbb37e4e0f2ed2ea6035e0b79dd0cb33bae85e" resolved "https://registry.yarnpkg.com/primer-support/-/primer-support-4.0.4.tgz#7f16ad577a61a960d0bccd72f2a6df506a14d69f"
primer-utilities@^3.0.0: primer-utilities@^3.0.0:
version "3.5.0" version "3.5.0"
@ -7526,7 +7521,7 @@ readline2@^1.0.1:
is-fullwidth-code-point "^1.0.0" is-fullwidth-code-point "^1.0.0"
mute-stream "0.0.5" mute-stream "0.0.5"
recast@0.11.12, recast@^0.11.5: recast@0.11.12:
version "0.11.12" version "0.11.12"
resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.12.tgz#a79e4d3f82d5d72a82ee177aeaa791e793bbe5d6" resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.12.tgz#a79e4d3f82d5d72a82ee177aeaa791e793bbe5d6"
dependencies: dependencies:
@ -7535,6 +7530,15 @@ recast@0.11.12, recast@^0.11.5:
private "~0.1.5" private "~0.1.5"
source-map "~0.5.0" 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: rechoir@^0.6.2:
version "0.6.2" version "0.6.2"
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" 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" resolved "https://registry.yarnpkg.com/spache/-/spache-1.1.0.tgz#8c68ba807630f0199429c2035c82ed96f5438cd5"
spawn-wrap@^1.3.6: spawn-wrap@^1.3.6:
version "1.3.6" version "1.3.7"
resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.3.6.tgz#ccec4a949d8ce7e2b1a35cf4671d683d2e76a1d1" resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.3.7.tgz#beb8bf4426d64b2b06871e0d7dee2643f1f8d1bc"
dependencies: dependencies:
foreground-child "^1.5.6" foreground-child "^1.5.6"
mkdirp "^0.5.0" 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" svg-tags "^1.0.0"
table "^4.0.1" 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: stylis@^3.0.19:
version "3.1.9" version "3.1.9"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.1.9.tgz#638370451f980437f57c59e58d2e296be29fafb7" resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.1.9.tgz#638370451f980437f57c59e58d2e296be29fafb7"
@ -9128,9 +9128,9 @@ trim@0.0.1:
version "0.0.1" version "0.0.1"
resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd"
triton-watch@^1.0.1: triton-watch@^1.1.0:
version "1.0.1" version "1.1.0"
resolved "https://registry.yarnpkg.com/triton-watch/-/triton-watch-1.0.1.tgz#b1087f6a57383f1e83d0a308e65110ca9a2a38d0" resolved "https://registry.yarnpkg.com/triton-watch/-/triton-watch-1.1.0.tgz#d2b47fbf6a45174198c196152bb86c696a923c35"
dependencies: dependencies:
triton "5.2.x" triton "5.2.x"