From 75ebc9870f0316d63460c47bf15e0b53a45a6658 Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Wed, 25 Nov 2020 10:02:53 +0000 Subject: [PATCH] services: Make env vars configurable with defaults Signed-off-by: Ryan Northey --- control/api.py | 18 ++-- control/connectors/docker/client.py | 9 +- services.yaml | 10 +- services/https-echo/https-echo.svg | 131 ----------------------- ui/src/service/forms.js | 156 +++++++++++++++++++++++++++- ui/src/service/modals.js | 6 +- 6 files changed, 174 insertions(+), 156 deletions(-) delete mode 100644 services/https-echo/https-echo.svg diff --git a/control/api.py b/control/api.py index 03aebbcc..76b51f6e 100644 --- a/control/api.py +++ b/control/api.py @@ -122,24 +122,26 @@ async def handle_image(self, ws, event): async def add_service(self, request: Request) -> Response: data = await self.load_json(request) - data['service_config'] = self.service_types[data["service_type"]] + service_config = self.service_types[data["service_type"]] - if not await self.client.image_exists(data['service_config']['image']): - await self.client.pull_image(data['service_config']['image']) + data['image'] = service_config.get("image") + if not await self.client.image_exists(data['image']): + await self.client.pull_image(data['image']) - config_path = data['service_config']['labels'].get('envoy.playground.config.path') + config_path = service_config['labels'].get('envoy.playground.config.path') if config_path: config_dir = os.path.dirname(config_path) config_fname = os.path.basename(config_path) - data['mounts'] = { - config_dir: await self.populate_volume( + data['mounts'] = OrderedDict( + (config_dir, await self.populate_volume( 'service', data['name'], 'config', {config_fname: base64.b64encode( - data.pop("configuration", "").encode('utf-8')).decode()})} + data.pop("configuration", "").encode('utf-8')).decode()}), )) else: - data['mounts'] = {} + data['mounts'] = OrderedDict() + data['environment'] = data.pop('vars') await self.client.create_service(**data) return self.dump_json(dict(message="OK")) diff --git a/control/connectors/docker/client.py b/control/connectors/docker/client.py index 5516cd25..09f3b9db 100644 --- a/control/connectors/docker/client.py +++ b/control/connectors/docker/client.py @@ -197,19 +197,18 @@ async def pull_image(self, image_tag: str) -> None: async def create_service( self, name: str, - service_config: dict, + environment: OrderedDict, + image: str, service_type: str, - mounts: dict, + mounts: OrderedDict, aliases=None) -> None: - image = service_config.get("image") if not image: # todo: add build logic return environment = [ "%s=%s" % (k, v) for k, v - in service_config.get("environment", {}).items()] - + in environment.items()] container = await self.client.containers.create_or_replace( config=self._get_service_config( service_type, diff --git a/services.yaml b/services.yaml index 4ab261b1..6789fba2 100644 --- a/services.yaml +++ b/services.yaml @@ -50,15 +50,7 @@ services: http-echo: image: mendhak/http-https-echo labels: - envoy.playground.service: "HTTP echo" + envoy.playground.service: "HTTP/S echo" envoy.playground.logo: "http-echo.svg" environment: HTTPS_PORT: 0 - - https-echo: - image: mendhak/http-https-echo - labels: - envoy.playground.service: "HTTPS echo" - envoy.playground.logo: "https-echo.svg" - environment: - HTTP_PORT: 0 diff --git a/services/https-echo/https-echo.svg b/services/https-echo/https-echo.svg deleted file mode 100644 index d2b923f6..00000000 --- a/services/https-echo/https-echo.svg +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - diff --git a/ui/src/service/forms.js b/ui/src/service/forms.js index 11cbc165..971fe23b 100644 --- a/ui/src/service/forms.js +++ b/ui/src/service/forms.js @@ -4,7 +4,7 @@ import exact from 'prop-types-exact'; import {connect} from 'react-redux'; -import {Col, CustomInput, Input} from 'reactstrap'; +import {Button, Col, CustomInput, Input, Row} from 'reactstrap'; import Editor from 'react-simple-code-editor'; import {highlight, languages} from 'prismjs/components/prism-core'; @@ -17,6 +17,8 @@ import { import {updateForm} from '../app/store'; +import {ActionRemove} from '../shared/actions'; + class BaseServiceForm extends React.PureComponent { static propTypes = exact({ @@ -171,3 +173,155 @@ const mapStateToProps = function(state, other) { const ServiceConfigurationForm = connect(mapStateToProps)(BaseServiceConfigurationForm); export {ServiceConfigurationForm}; + + +export class ServiceEnvironmentListForm extends React.PureComponent { + static propTypes = exact({ + vars: PropTypes.array, + }); + + render () { + const {vars={}} = this.props; + const onDelete = null; + const title = ''; + return ( + + + + +
+   +
+ + +
+ Variable name +
+ + +
+ Variable value +
+ +
+ {Object.entries(vars).map(([k, v], index) => { + return ( + + +
+ this.onDelete(evt, onDelete)} /> +
+ + +
+ {k} +
+ + +
+ {v + ''} +
+ +
); + })} + +
); + } +} + + +export class BaseServiceEnvironmentForm extends React.Component { + static propTypes = exact({ + dispatch: PropTypes.func, + form: PropTypes.object.isRequired, + }); + + state = {value: '', network: ''}; + + onClick = async (evt) => { + const {value, key} = this.state; + const {dispatch, form} = this.props; + const {vars: _vars={}} = form; + const vars = {..._vars}; + vars[key] = value; + this.setState({value: '', key: ''}); + await dispatch(updateForm({vars})); + } + + onChange = (evt) => { + const update = {}; + update[evt.target.name] = evt.target.value; + this.setState({...update}); + } + + get messages () { + return [ + "Add network vars for your proxy.", + "Your proxy will be addressable by other proxies or services with this value", + "You can restrict which networks an value is used for with a glob match, default is *", + ]; + } + + async componentDidUpdate(prevProps) { + const {dispatch, service_type, service_types} = this.props; + if (service_type !== prevProps.service_type) { + const {environment: vars} = service_types[service_type]; + await dispatch(updateForm({vars})); + } + } + + render () { + const {value, key} = this.state; + const {form} = this.props; + const {vars={}} = form; + return ( + + + + + + + + + + + + + + + + + ); + } +} + + +const mapEnvFormStateToProps = function(state) { + return { + form: state.form.value, + service_types: state.service_type.value, + }; +} + + +const ServiceEnvironmentForm = connect(mapEnvFormStateToProps)(BaseServiceEnvironmentForm); +export {ServiceEnvironmentForm} diff --git a/ui/src/service/modals.js b/ui/src/service/modals.js index b5ccbd5d..7f2d876b 100644 --- a/ui/src/service/modals.js +++ b/ui/src/service/modals.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import exact from 'prop-types-exact'; -import {ServiceConfigurationForm, ServiceForm} from './forms'; +import {ServiceConfigurationForm, ServiceEnvironmentForm, ServiceForm} from './forms'; import {connect} from 'react-redux'; @@ -145,7 +145,9 @@ export class BaseServiceModal extends React.Component { const {form, service_types} = this.props; const {service_type} = form; let showConfig = false; - const tabs = {Service: }; + const tabs = { + Service: , + Environment: }; if (service_type) { const {configuration} = service_types[service_type]; const configPath = service_types[service_type]['labels']['envoy.playground.config.path'];