diff --git a/src/api/admin/service/index.js b/src/api/admin/service/index.js index 3f09efa..ec83677 100644 --- a/src/api/admin/service/index.js +++ b/src/api/admin/service/index.js @@ -6,6 +6,7 @@ const router = new Router(); router.get('/', serviceCtrl.getServiceList); router.get('/:id', serviceCtrl.getServiceInfo); router.get('/:id/log', serviceCtrl.getServiceLog); +router.post('/create', serviceCtrl.createService); router.delete('/:id', serviceCtrl.deleteService); export default router; diff --git a/src/api/admin/service/service.ctrl.js b/src/api/admin/service/service.ctrl.js index e2dfe6b..ab833bb 100644 --- a/src/api/admin/service/service.ctrl.js +++ b/src/api/admin/service/service.ctrl.js @@ -1,4 +1,50 @@ import * as ServiceApi from 'lib/dockerApi/service'; +import { get, assign, reduce, map, concat, uniqBy } from 'lodash'; + +export const prepareFormData = (config) => { + //prepare placement + const constraint = get(config, 'placementConstraints',[]); + const placement = {}; + const prepareConstraint = constraint.map(v => { + return v.name + v.condition + v.value; + }); + const preference = get(config, 'placementPreferences', []); + const preparePreference = preference.map(v => { + const value = {} + if(v.startegy == "spread") { + value.Spread = {SpreadDescriptor: v.value} + return value; + } + return; + }); + placement.Constraints = prepareConstraint; + placement.Preferences = preparePreference; + + //prepare scheduling mode + const scheduling = get(config, 'schedulingMode', []); + const mode = scheduling.reduce((acc, v) => { + if (v.mode == 'replicated') { + acc["Replicated"] = {"Replicas": v.replicas} + return acc; + } else if (v.mode == 'global') { + acc["Global"] = {}; + return acc; + } + }, {}); + + //prepare networks + const network = [{"network" : get(config, 'network', '')}]; + const extraNetwork = get(config, 'extraNetwork', []); + const prepareNetwork = concat(network, extraNetwork).map(v => { + const value = {}; + value.Target = v.network; + return value; + }); + const networks = uniqBy(prepareNetwork, 'Target'); + + return { placement, mode, networks }; +} + export const getServiceList = async ctx => { @@ -25,6 +71,21 @@ export const getServiceInfo = async ctx => { } } +export const createService = async ctx => { + const { endpoint: {url} } = ctx.state.user; + const form = ctx.request.body; + assign(form, prepareFormData(form)); + + try { + const { data } = await ServiceApi.createService({url, form}); + ctx.status = 200; + ctx.body = { result: data }; + } catch(e) { + console.log(e) + ctx.throw(e, 500); + } +} + export const getServiceLog = async ctx => { const { endpoint: { url } } = ctx.state.user; const { id } = ctx.params; diff --git a/src/lib/dockerApi/service.js b/src/lib/dockerApi/service.js index 75332bd..bd0a274 100644 --- a/src/lib/dockerApi/service.js +++ b/src/lib/dockerApi/service.js @@ -1,7 +1,8 @@ import axios from 'axios'; import qs from 'query-string' import { request } from 'lib/httpClient'; -import { get, keys, map } from 'lodash'; +import { translateHumantimeToNanos } from 'lib/utility'; +import { get, keys, map, reduce } from 'lodash'; export const getServiceList = (url) => @@ -54,10 +55,85 @@ export const getServiceInfo = ({url, id}) => } }); +export const createService = ({url, form}) => + axios.post(`${url}/services/create`, { + "Name": form.serviceName, + "TaskTemplate": { + "ContainerSpec": { + "Image": form.imageName, + "Mounts": [ + { + "Target": form.volumePath, + "Source": form.volumeName, + "ReadOnly": form.volumeReadOnly, + "Type": form.volumeType, + "VolumeOptions": { + "DriverConfig": { + "Name":"local", + "Options":{} + }, + "Labels": null + } + } + ], + "Args": [form.command], + "Command":[form.entrypoint], + "User":form.user, + "Dir":form.workingDir, + "Env":map(form.env, v => `${v.name}=${v.value}`), + "Labels":reduce(form.containerLabels, (acc, obj) => { + acc[obj.name] = obj.value; + return acc; + },{}), + "Configs":[],"Secrets":[], + }, + "LogDriver": { + "Name": form.loggingDriver, + "Options":reduce(form.loggingOptions, (acc, obj) => { + acc[obj.option] = obj.value; + return acc; + },{}), + }, + "Placement": form.placement, + "Resources": { + "Limits": {}, + "Reservations": {} + }, + "RestartPolicy": { + "Condition": form.restartCondition, + "Delay": translateHumantimeToNanos(form.restartDelay), + "MaxAttempts": form.restartMaxAttempts, + "window": form.restartWindow + } + }, + "Mode": form.mode, + "Networks": form.networks, + "UpdateConfig": { + "Parallelism": form.parallelism, + "Delay": form.updateDelay, + "FailureAction": form.failAction, + "Order": form.order + }, + "EndpointSpec": { + "Ports": [ + { + "Protocol": form.portProtocol, + "PublishMode": form.portPublishMode, + "TargetPort": form.targetPort, + "PublishedPort": form.publishedPort, + } + ] + }, + "Labels": reduce(form.serviceLabels, (acc, obj) => { + acc[obj.name] = obj.value; + return acc; + },{}), + }).catch(err => console.log(err)); + export const getServiceLog = ({url, id, query}) => request({ method: 'GET', url: `${url}/services/${id}/logs?${qs.stringify(query)}` }) - + export const deleteService = ({url, id}) => axios.delete(`${url}/services/${id}`); diff --git a/src/lib/utility/index.js b/src/lib/utility/index.js index 9680e26..fb8dc05 100644 --- a/src/lib/utility/index.js +++ b/src/lib/utility/index.js @@ -21,3 +21,25 @@ export const numberWithCommas = x => { } export const objectToQueryString = obj => reduce(obj, (acc, v, k) => concat(acc, `${k}=${v}`),[]).join('&') + +export const translateHumantimeToNanos = (time) => { + let nanos = ""; + const regex = /^([0-9]+)(h|m|s|ms|us|ns)$/i; + const matches = time.match(regex); + + if (matches !== null && matches.length === 3) { + const time = parseInt(matches[1], 10); + const unit = matches[2]; + switch (unit) { + case 'ns': + nanos = time; + break; + case 'us': + nanos = time * 1000; + break; + default: + nanos = moment.duration(time, unit).asMilliseconds() * 1000000; + } + } + return nanos; +};