├── entrypoint ├── www ├── app │ ├── img │ │ └── favicon.ico │ ├── 674f50d287a8c48dc19ba404d20fe713.eot │ ├── b06871f281fee6b241d60582ae9369b9.ttf │ └── 89889688147bd7575d6327160d64e760.svg └── index.html ├── examples ├── http-server │ └── orcinus.yml ├── web-proxy │ ├── overwrite-domain │ │ └── orcinus.yml │ └── traefik │ │ └── orcinus.yml ├── visualizer │ └── orcinus.yml ├── logger │ ├── proxy │ │ └── orcinus.yml │ └── logger │ │ └── orcinus.yml ├── mariadb-cluster │ ├── centos │ │ └── orcinus.yml │ └── debian │ │ └── orcinus.yml └── nas │ ├── orcinus.yml │ └── nginx-webapp.conf ├── deploy ├── db │ └── orcinus.yml ├── repository │ └── orcinus.yml ├── webserver │ └── orcinus.yml └── dashboard │ └── orcinus.yml ├── .gitignore ├── lib ├── scale.js ├── ps.js ├── rm.js ├── inspect.js ├── list.js ├── rollback.js ├── update.js ├── logs.js ├── utils.js ├── create.js ├── cluster.js └── parser.js ├── apis ├── ping.js ├── volume.js ├── info.js ├── task.js ├── cluster.js ├── container.js ├── stack.js ├── passport.js ├── service.js └── auth.js ├── db ├── index.js └── model │ └── users.js ├── Dockerfile ├── test ├── test-parser.js ├── pre-post.js ├── test-parser-update.js ├── dashboard.js └── test.js ├── .travis.yml ├── LICENSE ├── Makefile ├── package.json ├── README.md ├── cli.js └── server.js /entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd /opt/orcinus 3 | node cli.js dashboard -------------------------------------------------------------------------------- /www/app/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orcinustools/orcinus-nodejs/HEAD/www/app/img/favicon.ico -------------------------------------------------------------------------------- /www/app/674f50d287a8c48dc19ba404d20fe713.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orcinustools/orcinus-nodejs/HEAD/www/app/674f50d287a8c48dc19ba404d20fe713.eot -------------------------------------------------------------------------------- /www/app/b06871f281fee6b241d60582ae9369b9.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orcinustools/orcinus-nodejs/HEAD/www/app/b06871f281fee6b241d60582ae9369b9.ttf -------------------------------------------------------------------------------- /examples/http-server/orcinus.yml: -------------------------------------------------------------------------------- 1 | stack: "mystack" 2 | services: 3 | webapp: 4 | image: "aksaramaya/docker-http-server:v1" 5 | ports: 6 | - "80:80" 7 | -------------------------------------------------------------------------------- /deploy/db/orcinus.yml: -------------------------------------------------------------------------------- 1 | # testing : 2 | # 3 | # curl -H Host:dashboard.orcinus.id http://127.0.0.1 4 | 5 | stack: "orcinus-dashboard" 6 | services: 7 | orcinus-db: 8 | image: "mongo:3.5" 9 | constraint: "node.role==manager" -------------------------------------------------------------------------------- /examples/web-proxy/overwrite-domain/orcinus.yml: -------------------------------------------------------------------------------- 1 | stack: "orcinus" 2 | services: 3 | wweb2: 4 | image: "emilevauge/whoami" 5 | labels: 6 | traefik.port: "80" 7 | traefik.frontend.rule: "Host:overwrite.domain" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | bin/ 4 | ./dashboard/ 5 | orcinus 6 | test.yml 7 | npm-debug.log 8 | .DS_Store 9 | test/test.json 10 | coverage 11 | lib/coverage.json 12 | lib/lcov-report/ 13 | lib/lcov.info 14 | config.js 15 | orcinus.dll 16 | package-lock.json 17 | yarn.lock 18 | -------------------------------------------------------------------------------- /examples/visualizer/orcinus.yml: -------------------------------------------------------------------------------- 1 | stack: "mystack" 2 | services: 3 | visualizer: 4 | image: "dockersamples/visualizer" 5 | ports: 6 | - "5000:8080" 7 | constraint: "node.role==manager" 8 | volumes: 9 | - "docker" 10 | volumes: 11 | docker: 12 | type: "bind" 13 | source: "/var/run/docker.sock" 14 | target: "/var/run/docker.sock" 15 | -------------------------------------------------------------------------------- /lib/scale.js: -------------------------------------------------------------------------------- 1 | var utils = require("./utils"); 2 | var proc = require('process'); 3 | var chp = require('child_process'); 4 | var val; 5 | module.exports = (app,num)=>{ 6 | var cmd = "docker service scale "+app+"="+num; 7 | chp.exec(cmd,(e, stdout, stderr)=> { 8 | if(stdout){ 9 | console.log(stdout); 10 | } 11 | if(stderr){ 12 | console.log(stderr) 13 | } 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /apis/ping.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | req.app.locals.orcinus.ping(function (err, data) { 7 | if(err){ 8 | res.status(err.statusCode).send({error : err.reason}); 9 | } 10 | else{ 11 | res.send(data); 12 | } 13 | }); 14 | }); 15 | 16 | module.exports = router; -------------------------------------------------------------------------------- /apis/volume.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | router.get("/",function(req, res, next){ 5 | req.app.locals.orcinus.listVolumes(function (err, data) { 6 | if(err){ 7 | res.status(err.statusCode).send({error : err.reason}); 8 | } 9 | else{ 10 | res.send(data); 11 | } 12 | }); 13 | }); 14 | 15 | module.exports = router; -------------------------------------------------------------------------------- /apis/info.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var utils = require('../lib/utils.js'); 4 | 5 | /* GET home page. */ 6 | router.get('/', function(req, res, next) { 7 | req.app.locals.orcinus.info(function (err, data) { 8 | if(err){ 9 | res.status(err.statusCode).send({error : err.reason}); 10 | } 11 | else{ 12 | res.send(data); 13 | } 14 | }); 15 | }); 16 | 17 | module.exports = router; -------------------------------------------------------------------------------- /deploy/repository/orcinus.yml: -------------------------------------------------------------------------------- 1 | # testing : 2 | # 3 | # curl -H Host:omura.orcinus.id http://127.0.0.1 4 | 5 | stack: "orcinus-dashboard" 6 | services: 7 | orcinus-repository: 8 | image: "orcinus/omura" 9 | constraint: "node.role==manager" 10 | networks: 11 | - "orcinus" 12 | labels: 13 | traefik.port: "8080" 14 | traefik.frontend.rule: "Host:omura.orcinus.id" 15 | traefik.docker.network: "orcinus" -------------------------------------------------------------------------------- /examples/logger/proxy/orcinus.yml: -------------------------------------------------------------------------------- 1 | stack: "orcinus" 2 | services: 3 | proxy: 4 | image: tecnativa/docker-socket-proxy:nightly 5 | auth: true 6 | ports: 7 | - "2375:2375" 8 | environment: 9 | - CONTAINERS=1 10 | mode: global 11 | constraint: "node.role==worker" 12 | volumes: 13 | - docker 14 | 15 | volumes: 16 | docker: 17 | type: "bind" 18 | source: "/var/run/docker.sock" 19 | target: "/var/run/docker.sock" -------------------------------------------------------------------------------- /examples/logger/logger/orcinus.yml: -------------------------------------------------------------------------------- 1 | stack: "orcinus" 2 | services: 3 | logger: 4 | image: amir20/dozzle:latest 5 | auth: true 6 | constraint: "node.role==manager" 7 | environment: 8 | - DOZZLE_HOSTNAME=node1 9 | - DOZZLE_REMOTE_HOST="tcp://10.10.100.1:2375|node2,tcp://10.10.100.2:2375|node3" 10 | volumes: 11 | - "docker" 12 | volumes: 13 | docker: 14 | type: "bind" 15 | source: "/var/run/docker.sock" 16 | target: "/var/run/docker.sock" 17 | -------------------------------------------------------------------------------- /db/index.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var colors = require('colors'); 3 | 4 | var db = function(host){ 5 | mongoose.connect(host, 6 | { 7 | useMongoClient: true, 8 | /* other options */ 9 | }, 10 | function (err, res) { 11 | if (err) { 12 | console.log (colors.red('==> ERROR connecting to: ' + host + '. ' + err)); 13 | } else { 14 | console.log (colors.green('==> Succeeded connected to database......')); 15 | } 16 | }); 17 | } 18 | 19 | db.users = require('./model/users'); 20 | 21 | module.exports = db; 22 | -------------------------------------------------------------------------------- /examples/mariadb-cluster/centos/orcinus.yml: -------------------------------------------------------------------------------- 1 | stack: "mariadb_cluster" 2 | 3 | services: 4 | mariadb-master: 5 | image: "aksaramaya/mariadb:10.1-centos" 6 | ports: 7 | - "13306:3306" 8 | environment: 9 | environment: 10 | - "MYSQL_ROOT_PASSWORD=test123" 11 | - "CLUSTER=BOOTSTRAP" 12 | - "CLUSTER_NAME=your_cluster" 13 | mariadb-slave: 14 | image: "aksaramaya/mariadb:10.1-centos" 15 | ports: 16 | - "23306:3306" 17 | environment: 18 | - "MYSQL_ROOT_PASSWORD=test123" 19 | - "CLUSTER=mariadb-master,mariadb_slave" 20 | - "CLUSTER_NAME=your_cluster" 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:7.10.0-alpine 2 | MAINTAINER ibnu yahya 3 | 4 | ENV LANG=C.UTF-8 LC_ALL=C 5 | ENV NODE_PATH=/usr/lib/node_modules 6 | 7 | RUN mkdir -p /opt/orcinus 8 | COPY . /opt/orcinus 9 | WORKDIR /opt/orcinus 10 | 11 | RUN set -ex \ 12 | && apk add --update --no-cache --virtual .build-deps \ 13 | python \ 14 | build-base \ 15 | && npm install \ 16 | && npm rebuild bcrypt --build-from-source \ 17 | && apk del .build-deps 18 | 19 | 20 | COPY entrypoint /entrypoint 21 | RUN chmod +x /entrypoint 22 | 23 | EXPOSE 4000 24 | ENTRYPOINT ["/entrypoint"] 25 | -------------------------------------------------------------------------------- /deploy/webserver/orcinus.yml: -------------------------------------------------------------------------------- 1 | stack: "orcinus" 2 | services: 3 | orcinus-webserver: 4 | image: "traefik" 5 | ports: 6 | - "80:80" 7 | - "8080:8080" 8 | constraint: "node.role==manager" 9 | commands: 10 | - "--docker" 11 | - "--docker.swarmmode" 12 | - "--docker.domain=svc.orcinus.id" # set main domain for production 13 | - "--docker.watch" 14 | - "--web" 15 | volumes: 16 | - "docker" 17 | volumes: 18 | docker: 19 | type: "bind" 20 | source: "/var/run/docker.sock" 21 | target: "/var/run/docker.sock" -------------------------------------------------------------------------------- /apis/task.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var utils = require('../lib/utils.js'); 4 | 5 | router.post("/",function(req, res, next){ 6 | var user = utils.decode(req,res); 7 | var filters = {filters:{name:{}}}; 8 | var idTask = user.id+'-'; 9 | 10 | filters.filters.name[idTask] = true; 11 | req.app.locals.orcinus.listTasks(filters,function (err, data) { 12 | if(err){ 13 | res.status(err.statusCode).send({error : err.reason}); 14 | } 15 | else{ 16 | res.send(data); 17 | } 18 | }); 19 | }); 20 | 21 | module.exports = router; -------------------------------------------------------------------------------- /examples/nas/orcinus.yml: -------------------------------------------------------------------------------- 1 | stack: "mystack" 2 | 3 | services: 4 | webapp: 5 | image: "aksaramaya/docker-http-server:v1" 6 | volumes: 7 | - "yournfs" 8 | nginx: 9 | image: "nginx:1.9" 10 | ports: 11 | - 443:443 12 | - 80:80 13 | volumes: 14 | - "conf" 15 | - "logs" 16 | 17 | volumes: 18 | yournfs: 19 | type: "nfs" 20 | address: "192.168.7.11" 21 | source: "/yournfs" 22 | target: "/mnt" 23 | conf: 24 | type: "nfs" 25 | address: "192.168.7.11" 26 | source: "/yournfs/nginx/conf" 27 | target: "/etc/nginx/conf.d" 28 | logs: 29 | type: "nfs" 30 | address: "192.168.7.11" 31 | source: "/yournfs/nginx/logs" 32 | target: "/var/log/nginx" 33 | -------------------------------------------------------------------------------- /apis/cluster.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var utils = require('../lib/utils.js'); 4 | 5 | router.get("/",function(req, res, next){ 6 | req.app.locals.orcinus.cluster(function (err, data) { 7 | if(err){ 8 | res.status(err.statusCode).send({error : err.reason}); 9 | } 10 | else{ 11 | res.send(data); 12 | } 13 | }); 14 | }); 15 | 16 | router.post("/init",function(req, res, next){ 17 | var opt = req.body; 18 | req.app.locals.orcinus.clusterInit(opt,function (err, data) { 19 | if(err){ 20 | res.status(err.statusCode).send({error : err.reason}); 21 | } 22 | else{ 23 | res.send(data); 24 | } 25 | }); 26 | }); 27 | 28 | module.exports = router; -------------------------------------------------------------------------------- /examples/mariadb-cluster/debian/orcinus.yml: -------------------------------------------------------------------------------- 1 | stack: "mariadb_cluster" 2 | 3 | services: 4 | mariadb_master: 5 | image: "aksaramaya/mariadb:10.1" 6 | ports: 7 | - "13306:3306" 8 | environment: 9 | - "MYSQL_ROOT_PASSWORD=test" 10 | - "REPLICATION_PASSWORD=test" 11 | - "MYSQL_USER=cluster" 12 | - "MYSQL_PASSWORD=test" 13 | - "GALERA=On" 14 | - "NODE_NAME=mariadb_master" 15 | - "CLUSTER_NAME=mariadb_cluster" 16 | - "CLUSTER_ADDRESS=gcomm://" 17 | command: "--wsrep-new-cluster" 18 | mariadb_slave: 19 | image: "hauptmedia/mariadb:10.1" 20 | ports: 21 | - "23306:3306" 22 | environment: 23 | - "REPLICATION_PASSWORD=test" 24 | - "GALERA=On" 25 | - "NODE_NAME=mariadb_slave" 26 | - "CLUSTER_NAME=mariadb_cluster" 27 | - "CLUSTER_ADDRESS=gcomm://mariadb_master" 28 | -------------------------------------------------------------------------------- /examples/nas/nginx-webapp.conf: -------------------------------------------------------------------------------- 1 | upstream webapp { 2 | server webapp:80; 3 | } 4 | 5 | server { 6 | listen 80; 7 | access_log /var/log/nginx/webapp.log; 8 | 9 | # disable any limits to avoid HTTP 413 for large image uploads 10 | client_max_body_size 0; 11 | 12 | # required to avoid HTTP 411: see Issue #1486 (https://github.com/docker/docker/issues/1486) 13 | chunked_transfer_encoding on; 14 | 15 | location / { 16 | 17 | proxy_pass http://webapp; 18 | proxy_set_header Host $http_host; # required for docker client's sake 19 | proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP 20 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 21 | proxy_set_header X-Forwarded-Proto $scheme; 22 | proxy_read_timeout 900; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/ps.js: -------------------------------------------------------------------------------- 1 | var utils = require("./utils"); 2 | var proc = require('process'); 3 | var chp = require('child_process'); 4 | var val; 5 | module.exports = { 6 | init : (value)=>{ 7 | val = value; 8 | var svc = utils.obj(val); 9 | if(svc.indexOf('services') < 0) { 10 | console.log("Service can't defined."); 11 | proc.exit(1); 12 | } 13 | var app = utils.obj(val.services); 14 | app.forEach((v)=>{ 15 | module.exports.prs(v); 16 | }); 17 | }, 18 | execution : (keyMap)=>{ 19 | var name = keyMap.name; 20 | var cmd = "docker service ps "+name; 21 | chp.exec(cmd,(e, stdout, stderr)=> { 22 | if(stdout){ 23 | console.log(stdout); 24 | } 25 | if(stderr){ 26 | console.log(stderr) 27 | } 28 | }) 29 | }, 30 | prs : (key)=>{ 31 | var keyMap = { 32 | name: key 33 | } 34 | module.exports.execution(keyMap); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/rm.js: -------------------------------------------------------------------------------- 1 | var utils = require("./utils"); 2 | var proc = require('process'); 3 | var chp = require('child_process'); 4 | var val; 5 | module.exports = { 6 | init : (value)=>{ 7 | val = value; 8 | var svc = utils.obj(val); 9 | if(svc.indexOf('services') < 0) { 10 | console.log("Service can't defined."); 11 | proc.exit(1); 12 | } 13 | var app = utils.obj(val.services); 14 | app.forEach((v)=>{ 15 | module.exports.prs(v); 16 | }); 17 | }, 18 | execution : (keyMap)=>{ 19 | var name = keyMap.name; 20 | var cmd = "docker service rm "+name; 21 | chp.exec(cmd,(e, stdout, stderr)=> { 22 | if(stdout){ 23 | console.log("delete :"+stdout); 24 | } 25 | if(stderr){ 26 | console.log(stderr) 27 | } 28 | }) 29 | }, 30 | prs : (key)=>{ 31 | var keyMap = { 32 | name: key 33 | } 34 | module.exports.execution(keyMap); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/inspect.js: -------------------------------------------------------------------------------- 1 | var utils = require("./utils"); 2 | var proc = require('process'); 3 | var chp = require('child_process'); 4 | var val; 5 | module.exports = { 6 | init : (value)=>{ 7 | val = value; 8 | var svc = utils.obj(val); 9 | if(svc.indexOf('services') < 0) { 10 | console.log("Service can't defined."); 11 | proc.exit(1); 12 | } 13 | var app = utils.obj(val.services); 14 | app.forEach((v)=>{ 15 | module.exports.prs(v); 16 | }); 17 | }, 18 | execution : (keyMap)=>{ 19 | var name = keyMap.name; 20 | var cmd = "docker service inspect --pretty "+name; 21 | chp.exec(cmd,(e, stdout, stderr)=> { 22 | if(stdout){ 23 | console.log(stdout); 24 | } 25 | if(stderr){ 26 | console.log(stderr) 27 | } 28 | }) 29 | }, 30 | prs : (key)=>{ 31 | var keyMap = { 32 | name: key 33 | } 34 | module.exports.execution(keyMap); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /deploy/dashboard/orcinus.yml: -------------------------------------------------------------------------------- 1 | # testing : 2 | # 3 | # curl -H Host:dashboard.orcinus.id http://127.0.0.1 4 | 5 | stack: "orcinus-dashboard" 6 | services: 7 | orcinus-dashboard: 8 | image: "orcinus/orcinus:0.4.0" 9 | constraint: "node.role==manager" 10 | environment: 11 | - "ORCINUS=dev" 12 | - "ORCINUS_HTTP_CORS='*'" 13 | - "ORCINUS_OMURA='http://omura.orcinus.id'" 14 | - "ORCINUS_DB=mongodb://orcinus-db/orcinus" 15 | - "ORCINUS_DOMAIN=svc.orcinus.id" 16 | - "ORCINUS_NETWROK=orcinus" 17 | volumes: 18 | - "docker" 19 | - "local" 20 | networks: 21 | - "orcinus" # webserver network 22 | labels: 23 | traefik.port: "4000" 24 | traefik.frontend.rule: "Host:dashboard.orcinus.id" 25 | traefik.docker.network: "orcinus" 26 | volumes: 27 | docker: 28 | type: "bind" 29 | source: "/var/run/docker.sock" 30 | target: "/var/run/docker.sock" 31 | local: 32 | type: "bind" 33 | source: "/root" 34 | target: "/root" 35 | -------------------------------------------------------------------------------- /test/test-parser.js: -------------------------------------------------------------------------------- 1 | var a = require('../lib/parser.js'); 2 | var b = new a({ 3 | "stack": "mystack", 4 | "services": { 5 | "web1": { 6 | "image": "aksaramaya/docker-http-server:v1", 7 | "ports": [ 8 | "80:80" 9 | ], 10 | "replicas": 4, 11 | "cpu": "2", 12 | "memory": "512mb" 13 | }, 14 | "visualizer": { 15 | "image": "manomarks/visualizer", 16 | "ports": [ 17 | "5000:8080" 18 | ], 19 | "constraint": ["node.role==manager"], 20 | "volumes": [ 21 | "docker", 22 | "yournfs" 23 | ] 24 | }, 25 | "web2": { 26 | "image": "aksaramaya/docker-http-server:v1", 27 | "replicas": 4, 28 | "cpu": "2", 29 | "memory": "512mb" 30 | } 31 | }, 32 | "volumes": { 33 | "yournfs": { 34 | "type": "nfs", 35 | "address": "192.168.7.11", 36 | "source": "/yournfs", 37 | "target": "/mnt" 38 | }, 39 | "docker": { 40 | "type": "bind", 41 | "source": "/yournfs", 42 | "target": "/mnt" 43 | } 44 | } 45 | 46 | }); 47 | 48 | console.log(JSON.stringify(b.services())); 49 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | group: edge 3 | 4 | services: 5 | - docker 6 | 7 | node_js: 8 | - '7' 9 | 10 | script: 11 | - make test 12 | - if [ -n "$TRAVIS_TAG" ]; then make prebuild; make build; mv bin/orcinus bin/orcinus-$TRAVIS_TAG-$(uname -s)-$(uname -m).bin; fi 13 | 14 | deploy: 15 | provider: releases 16 | api_key: 17 | secure: kDHPvhdZrf81ahiKtDwDTPo3GnAqOPJjMfvoWlzW8cbUoNVP3KklESMrkLa4pXThdNXYS7HdhBALd1BdlKHMTixjyLKVOVyHB4ncN94qjdaj1Ow5dsyj6XqU8c+cse0qhPs2+1HwMOtT7ruPPEypn+9UJHk2pQqeQPdhuugnOnA5W6YU5FzHS5Ea5+DIfUrf4gbvEEbnUZ4881pSjp6WbHwAfFNNUHKAcMmnFy3yOC0oUsY2lmvujOc4nceyy+au6OabRxt3XNegCa6A2/pFkBtb/Fb2cW2PCXmlm8Jhww/MAJXre95Rrwix/ockJHcvw1e1qxBdIIuvBG0M8dcPTtUZ1upsJj8hMr1MsRxH3YGe2a/fNY5GQ2RKbc09JlDP9/iECIsla0AnsdzZLH8rJH6qFy49/DxOJyqZ9koFccbPw1DAwhSLRAVKr6vzfYszEtUt38TQcCSfCAS74alo6aWkNvS6nlrusudQ8SuV7RUCx4pJii8LUxeDNfbbmAG2XxNMIedHYkv6x+qOEyLayf+tk7wWmFbYm3y8yPp68lqnsu9tO02bP7huh7iVsEU8vE8V1aahkgI6UBZeU/ZNOzko/HRxckiJd2W1C0j2ClBTBJYkO/EJxTFY0cvnw1WaRLe9zb9Vq8RNFpxL2VkFaheQoFoZamiy5cpAsAZQaoI= 18 | file_glob: true 19 | file: bin/* 20 | skip_cleanup: true 21 | on: 22 | repo: orcinustools/orcinus 23 | branch: master 24 | tags: true 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Eka Tresna Irawan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/pre-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mode = process.argv[2]; 4 | 5 | 6 | var chp = require('child_process'); 7 | var async = require('async'); 8 | 9 | var cmds = [ 10 | 'cd ' + process.cwd() + ' && node cli.js cluster leave', 11 | 'cd ' + process.cwd() + ' && node cli.js cluster leave-manager', 12 | 'cd ' + process.cwd() + ' && node cli.js cluster init 127.0.0.1', 13 | 'docker service rm orcinus-ut-web', 14 | 'docker network rm orcinus-unit-testing-stack', 15 | 'docker swarm init', 16 | ]; 17 | 18 | if (mode === 'pre') { 19 | console.log('Before run the test, you may consider preparing few points bellow :'); 20 | console.log(' - Pull aksaramaya/docker-http-server:v1 from hub.docker.com'); 21 | console.log(' - The test may use host ports from 7001 to 7010, make sure no other service run on these ports'); 22 | console.log(' - If your machine is not fast enough, please increase deployTimeout value in test/test.js\n'); 23 | } else { 24 | cmds.splice(2,1); 25 | } 26 | 27 | async.eachSeries(cmds, (cmd, cb) => { 28 | chp.exec(cmd, (err, stdout, stderr) => { 29 | cb(); 30 | }); 31 | }, () => { 32 | if (mode === 'pre') { 33 | console.log('Ready to test'); 34 | } 35 | }); 36 | -------------------------------------------------------------------------------- /lib/list.js: -------------------------------------------------------------------------------- 1 | var utils = require("./utils"); 2 | var proc = require('process'); 3 | var chp = require('child_process'); 4 | var val; 5 | module.exports = { 6 | init : (value)=>{ 7 | val = value; 8 | var svc = utils.obj(val); 9 | if(svc.indexOf('services') < 0) { 10 | console.log("Service can't defined."); 11 | proc.exit(1); 12 | } 13 | var app = utils.obj(val.services); 14 | app.forEach((v)=>{ 15 | module.exports.prs(v); 16 | }); 17 | }, 18 | execution : (keyMap)=>{ 19 | var name = keyMap.name; 20 | var cmd = "docker service ls -f 'name="+name+"'|grep -w '"+name+"'"; 21 | chp.exec(cmd,(e, stdout, stderr)=> { 22 | if(stdout){ 23 | console.log(stdout); 24 | } 25 | if(stderr){ 26 | console.log(stderr) 27 | } 28 | }) 29 | }, 30 | prs : (key)=>{ 31 | var keyMap = { 32 | name: key 33 | } 34 | module.exports.execution(keyMap); 35 | }, 36 | all : ()=>{ 37 | var cmd = "docker service ls"; 38 | chp.exec(cmd,(e, stdout, stderr)=> { 39 | if(stdout){ 40 | console.log(stdout); 41 | } 42 | if(stderr){ 43 | console.log(stderr) 44 | } 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/web-proxy/traefik/orcinus.yml: -------------------------------------------------------------------------------- 1 | # testing : 2 | # 3 | # curl -H Host:whoami.traefik http://127.0.0.1 4 | # 5 | # Hostname: 8147a7746e7a 6 | # IP: 127.0.0.1 7 | # IP: ::1 8 | # IP: 10.0.9.3 9 | # IP: fe80::42:aff:fe00:903 10 | # IP: 172.18.0.3 11 | # IP: fe80::42:acff:fe12:3 12 | # GET / HTTP/1.1 13 | # Host: 10.0.9.3:80 14 | # User-Agent: curl/7.35.0 15 | # Accept: */* 16 | # Accept-Encoding: gzip 17 | # X-Forwarded-For: 192.168.99.1 18 | # X-Forwarded-Host: 10.0.9.3:80 19 | # X-Forwarded-Proto: http 20 | # X-Forwarded-Server: 8fbc39271b4c 21 | 22 | 23 | stack: "orcinus" 24 | services: 25 | treafik: 26 | image: "traefik" 27 | ports: 28 | - "80:80" 29 | - "8080:8080" 30 | constraint: "node.role==manager" 31 | commands: 32 | - "--docker" 33 | - "--docker.swarmmode" 34 | - "--docker.domain=traefik" 35 | - "--docker.watch" 36 | - "--web" 37 | volumes: 38 | - "docker" 39 | whoami: 40 | image: "emilevauge/whoami" 41 | labels: 42 | traefik.port: "80" 43 | 44 | volumes: 45 | docker: 46 | type: "bind" 47 | source: "/var/run/docker.sock" 48 | target: "/var/run/docker.sock" 49 | -------------------------------------------------------------------------------- /db/model/users.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose') 2 | Schema = mongoose.Schema, 3 | bcrypt = require('bcrypt'), 4 | SALT_WORK_FACTOR = 10; 5 | 6 | UserSchema = new Schema({ 7 | email: { 8 | type: String, 9 | unique: true 10 | }, 11 | firstname: { type: String }, 12 | lastname: { type: String }, 13 | password: { 14 | type: String, 15 | required: true 16 | }, 17 | username: { 18 | type: String, 19 | unique: true 20 | }, 21 | phone: { 22 | type: String 23 | }, 24 | admin: { type: Boolean }, 25 | verify: { 26 | type: Boolean, 27 | default: true 28 | } 29 | }); 30 | 31 | UserSchema.pre('save', function(next) { 32 | var user = this; 33 | 34 | // only hash the password if it has been modified (or is new) 35 | if (!user.isModified('password')) return next(); 36 | 37 | // generate a salt 38 | bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) { 39 | if (err) return next(err); 40 | 41 | // hash the password using our new salt 42 | bcrypt.hash(user.password, salt, function(err, hash) { 43 | if (err) return next(err); 44 | 45 | // override the cleartext password with the hashed one 46 | user.password = hash; 47 | next(); 48 | }); 49 | }); 50 | }); 51 | 52 | UserSchema.methods.comparePass = function(candidatePassword, cb) { 53 | bcrypt.compare(candidatePassword, this.password, function(err, isMatch) { 54 | if (err) return cb(err); 55 | cb(null, isMatch); 56 | }); 57 | }; 58 | 59 | module.exports = mongoose.model('Users', UserSchema); 60 | -------------------------------------------------------------------------------- /apis/container.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var utils = require('../lib/utils.js'); 4 | 5 | router.get('/', function(req, res, next) { 6 | req.app.locals.orcinus.listContainers(function (err, data) { 7 | if(err) { 8 | res.status(err.statusCode).send({error : err.reason}); 9 | } 10 | else { 11 | res.send(data); 12 | } 13 | }); 14 | }); 15 | 16 | router.post('/inspect', function (req, res, next) { 17 | var cnt = req.app.locals.orcinus.getContainer(req.body.id); 18 | 19 | cnt.inspect(function(err, data) { 20 | if (err) { 21 | res.status(err.statusCode).send({error : err.reason}); 22 | } 23 | else { 24 | res.send(data); 25 | } 26 | }); 27 | }); 28 | 29 | router.post('/delete', function (req, res, next) { 30 | var cnt = req.app.locals.orcinus.getContainer(req.body.id); 31 | cnt.stop(function (err, data) { 32 | if(err){ 33 | res.status(err.statusCode).send({error : err.reason}); 34 | } 35 | else{ 36 | res.send(data); 37 | } 38 | }); 39 | }); 40 | 41 | router.post('/pause', function (req, res, next) { 42 | var cnt = req.app.locals.orcinus.getContainer(req.body.id); 43 | cnt.pause(function (err, data) { 44 | if(err){ 45 | res.status(err.statusCode).send({error : err.reason}); 46 | } 47 | else{ 48 | res.send(data); 49 | } 50 | }); 51 | }); 52 | 53 | router.post('/unpause', function (req, res, next) { 54 | var cnt = req.app.locals.orcinus.getContainer(req.body.id); 55 | 56 | cnt.unpause(function (err, data) { 57 | if(err){ 58 | res.status(err.statusCode).send({error: err.reason}); 59 | } 60 | else{ 61 | res.send(data); 62 | } 63 | }) 64 | }); 65 | 66 | module.exports = router; -------------------------------------------------------------------------------- /lib/rollback.js: -------------------------------------------------------------------------------- 1 | var utils = require("./utils"); 2 | var proc = require('process'); 3 | var chp = require('child_process'); 4 | var val; 5 | module.exports = { 6 | init : (value)=>{ 7 | val = value; 8 | var svc = utils.obj(val); 9 | if(svc.indexOf('services') < 0) { 10 | console.log("Service can't defined."); 11 | proc.exit(1); 12 | } 13 | var app = utils.obj(val.services); 14 | app.forEach((v)=>{ 15 | module.exports.prs(v); 16 | }); 17 | }, 18 | execution : (keyMap)=>{ 19 | var opt = keyMap.opt.join(" "); 20 | var img = keyMap.img; 21 | var name = keyMap.name; 22 | var cmd = "docker service update -d --rollback "+name; 23 | chp.exec(cmd,(e, stdout, stderr)=> { 24 | if(stdout){ 25 | console.log("Service rollback : "+stdout); 26 | } 27 | if(stderr){ 28 | console.log(stderr) 29 | } 30 | }) 31 | }, 32 | prs : (key)=>{ 33 | var app = val.services[key]; 34 | var image = app.image; 35 | var opt = module.exports.opt(app); 36 | var keyMap = { 37 | opt: opt, 38 | img: image, 39 | name: key 40 | } 41 | module.exports.execution(keyMap); 42 | }, 43 | opt : (app)=>{ 44 | var arr = []; 45 | var cmd = utils.obj(app); 46 | if(cmd.indexOf("ports") >= 0){ 47 | app.ports.forEach((v)=>{ 48 | arr.push("--publish-add "+v); 49 | }); 50 | } 51 | if(cmd.indexOf("environment") >= 0){ 52 | app.environment.forEach((v)=>{ 53 | arr.push("--env-add "+v); 54 | }); 55 | } 56 | if(cmd.indexOf("replicas") >= 0){ 57 | arr.push("--replicas "+app.replicas); 58 | } 59 | if(cmd.indexOf("cpu") >= 0){ 60 | arr.push("--limit-cpu "+app.cpu); 61 | } 62 | if(cmd.indexOf("memory") >= 0){ 63 | arr.push("--limit-memory "+app.memory); 64 | } 65 | 66 | return arr; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/update.js: -------------------------------------------------------------------------------- 1 | var utils = require("./utils"); 2 | var proc = require('process'); 3 | var chp = require('child_process'); 4 | var val; 5 | module.exports = { 6 | init : (value)=>{ 7 | val = value; 8 | var svc = utils.obj(val); 9 | if(svc.indexOf('services') < 0) { 10 | console.log("Service can't defined."); 11 | proc.exit(1); 12 | } 13 | var app = utils.obj(val.services); 14 | app.forEach((v)=>{ 15 | module.exports.prs(v); 16 | }); 17 | }, 18 | execution : (keyMap)=>{ 19 | var opt = keyMap.opt.join(" "); 20 | var img = keyMap.img; 21 | var name = keyMap.name; 22 | var cmd = "docker service update -d "+opt+" --image "+img+" "+name; 23 | chp.exec(cmd,(e, stdout, stderr)=> { 24 | if(stdout){ 25 | console.log("Service updated: "+stdout); 26 | } 27 | if(stderr){ 28 | console.log(stderr) 29 | } 30 | }) 31 | }, 32 | prs : (key)=>{ 33 | var app = val.services[key]; 34 | var image = app.image; 35 | var opt = module.exports.opt(app); 36 | var keyMap = { 37 | opt: opt, 38 | img: image, 39 | name: key 40 | } 41 | module.exports.execution(keyMap); 42 | }, 43 | opt : (app)=>{ 44 | var arr = []; 45 | var cmd = utils.obj(app); 46 | if(cmd.indexOf("ports") >= 0){ 47 | app.ports.forEach((v)=>{ 48 | arr.push("--publish-add "+v); 49 | }); 50 | } 51 | if(cmd.indexOf("environment") >= 0){ 52 | app.environment.forEach((v)=>{ 53 | arr.push("--env-add "+v); 54 | }); 55 | } 56 | if(cmd.indexOf("replicas") >= 0){ 57 | arr.push("--replicas "+app.replicas); 58 | } 59 | if(cmd.indexOf("cpu") >= 0){ 60 | arr.push("--limit-cpu "+app.cpu); 61 | } 62 | if(cmd.indexOf("memory") >= 0){ 63 | arr.push("--limit-memory "+app.memory); 64 | } 65 | 66 | return arr; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CONFIG_DIRS := config 2 | SRC := $(PWD) 3 | VERSION := 0.2.7 4 | 5 | .PHONY: all clean build frontend install prebuild docker production production-remove push run test 6 | 7 | all: build 8 | 9 | prebuild: 10 | npm install -g nexe 11 | 12 | frontend: 13 | rm -rf www 14 | if [ ! -d "dashboard" ]; then git clone https://github.com/orcinustools/dashboard.git;cd dashboard;npm install;npm run build:prod;cd $(SRC); else cd dashboard;npm install;npm run build:prod;cd $(SRC); fi 15 | mv ./dashboard/dist www 16 | rm -rf dashboard 17 | 18 | build: 19 | npm install 20 | nexe 21 | mkdir bin 22 | mv orcinus bin/orcinus 23 | 24 | install: 25 | @echo "===> Install orcinus CLI" 26 | @npm install -g 27 | 28 | production: install 29 | @echo "===> Install orcinus webserver" 30 | @orcinus create -f ./deploy/webserver/orcinus.yml 31 | @echo "===> Install orcinus database" 32 | @orcinus create -f ./deploy/db/orcinus.yml 33 | @echo "===> Install orcinus dashboard" 34 | @orcinus create -f ./deploy/dashboard/orcinus.yml 35 | @echo "===> Install orcinus repository" 36 | @orcinus create -f ./deploy/repository/orcinus.yml 37 | 38 | production-remove: 39 | @echo "===> Remove orcinus webserver" 40 | @orcinus rm -f ./deploy/webserver/orcinus.yml 41 | @echo "===> Remove orcinus dashboard" 42 | @orcinus rm -f ./deploy/dashboard/orcinus.yml 43 | @echo "===> Remove orcinus repository" 44 | @orcinus rm -f ./deploy/repository/orcinus.yml 45 | @echo "===> Remove orcinus database" 46 | @orcinus rm -f ./deploy/db/orcinus.yml 47 | 48 | clean: 49 | rm -rf build bin www coverage 50 | 51 | docker: 52 | docker build -t orcinus/orcinus:$(VERSION) . 53 | docker tag orcinus/orcinus:$(VERSION) orcinus/orcinus:latest 54 | 55 | push: docker 56 | docker push orcinus/orcinus:$(VERSION) 57 | docker push orcinus/orcinus:latest 58 | 59 | run: 60 | echo "Dashboard starting.........." 61 | node cli.js dashboard 62 | test: 63 | npm install 64 | npm run test 65 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Orcinus Dashboard 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/test-parser-update.js: -------------------------------------------------------------------------------- 1 | var a = require('../lib/parser.js'); 2 | var b = new a({ 3 | "update" : { 4 | "service": { 5 | "constraint": "node.role==manager", 6 | "replicas": 1, 7 | "cpu": "1", 8 | "memory": "1gb" 9 | } 10 | }, 11 | "spec" : { 12 | "ID": "nbnmprtihem49tcfkh5ciiljo", 13 | "Version": { 14 | "Index": 1289 15 | }, 16 | "CreatedAt": "2017-05-27T12:01:01.855717598Z", 17 | "UpdatedAt": "2017-05-27T12:01:01.881683349Z", 18 | "Spec": { 19 | "Name": "app", 20 | "TaskTemplate": { 21 | "ContainerSpec": { 22 | "Image": "manomarks/visualizer:latest@sha256:e37a1349a680964b58033bdcfaec04abccd9294acf112b6043871ff5b4dbcaba", 23 | "Mounts": [ 24 | { 25 | "Type": "bind", 26 | "Source": "/var/run/docker.sock", 27 | "Target": "/var/run/docker.sock" 28 | } 29 | ] 30 | }, 31 | "Resources": { 32 | "Limits": { 33 | "NanoCPUs": 2000000000, 34 | "MemoryBytes": 536870912 35 | }, 36 | "Reservations": {} 37 | }, 38 | "RestartPolicy": { 39 | "Condition": "any", 40 | "MaxAttempts": 0 41 | }, 42 | "Placement": {}, 43 | "ForceUpdate": 0 44 | }, 45 | "Mode": { 46 | "Replicated": { 47 | "Replicas": 3 48 | } 49 | }, 50 | "UpdateConfig": { 51 | "Parallelism": 1, 52 | "FailureAction": "pause", 53 | "MaxFailureRatio": 0 54 | }, 55 | "Networks": [ 56 | { 57 | "Target": "yb1klwjt0ujjv2xfsidzjdfwa" 58 | } 59 | ], 60 | "EndpointSpec": { 61 | "Mode": "vip" 62 | } 63 | }, 64 | "Endpoint": { 65 | "Spec": { 66 | "Mode": "vip" 67 | }, 68 | "VirtualIPs": [ 69 | { 70 | "NetworkID": "yb1klwjt0ujjv2xfsidzjdfwa", 71 | "Addr": "10.0.0.2/24" 72 | } 73 | ] 74 | }, 75 | "UpdateStatus": { 76 | "StartedAt": "0001-01-01T00:00:00Z", 77 | "CompletedAt": "0001-01-01T00:00:00Z" 78 | } 79 | } 80 | 81 | 82 | }); 83 | 84 | console.log(JSON.stringify(b.update())); 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orcinus", 3 | "version": "0.5.2", 4 | "description": "Container orchestration tools", 5 | "main": "cli.js", 6 | "scripts": { 7 | "test": "node test/pre-post.js pre && sleep 5 && ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha test/test.js && node test/pre-post.js post", 8 | "dashboard-test": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha test/dashboard.js" 9 | }, 10 | "bin": { 11 | "orcinus": "./cli.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/anak10thn/orcinus.git" 16 | }, 17 | "keywords": [ 18 | "docker", 19 | "swarm", 20 | "compose", 21 | "tools", 22 | "utility" 23 | ], 24 | "author": "Ibnu Yahya ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/anak10thn/orcinus/issues" 28 | }, 29 | "homepage": "https://github.com/anak10thn/orcinus#readme", 30 | "dependencies": { 31 | "async": "^2.4.1", 32 | "base-64": "^0.1.0", 33 | "bcrypt": ">=5.0.0", 34 | "body-parser": "~1.15.1", 35 | "bytes": "^2.5.0", 36 | "colors": "^1.1.2", 37 | "commander": "^2.9.0", 38 | "compression": "^1.6.2", 39 | "cookie-parser": "~1.4.3", 40 | "cors": "^2.8.3", 41 | "debug": "~2.2.0", 42 | "docker-parse-image": "^3.0.1", 43 | "express": "^4.15.2", 44 | "file-extension": "^3.1.2", 45 | "http-proxy": "^1.16.2", 46 | "jsonwebtoken": "^7.4.1", 47 | "jwt-decode": "^2.2.0", 48 | "md5": "^2.2.1", 49 | "mongoose": "^5.9.5", 50 | "morgan": "~1.7.0", 51 | "orcinusd": "^0.4.0", 52 | "passport": "^0.3.2", 53 | "passport-github": "^1.1.0", 54 | "passport-google-oauth": "^1.0.0", 55 | "passport-local": "^1.0.0", 56 | "path": "^0.12.7", 57 | "serve-favicon": "~2.3.0", 58 | "urandom": "0.0.2", 59 | "utf8": "^2.1.2", 60 | "yamljs": "^0.2.8" 61 | }, 62 | "nexe": { 63 | "input": "cli.js", 64 | "output": "orcinus", 65 | "temp": "build/", 66 | "debug": false, 67 | "nodeMakeArgs": [ 68 | "-j", 69 | "8" 70 | ], 71 | "runtime": { 72 | "framework": "node", 73 | "version": "7.7.2", 74 | "ignoreFlags": true 75 | } 76 | }, 77 | "devDependencies": { 78 | "faker": "^4.1.0", 79 | "istanbul": "^0.4.5", 80 | "mocha": "^3.2.0", 81 | "should": "^11.2.1", 82 | "supertest": "^3.0.0" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/logs.js: -------------------------------------------------------------------------------- 1 | var utils = require("./utils"); 2 | var ps = require('process'); 3 | var chp = require('child_process'); 4 | var utf8 = require('utf8'); 5 | var fs = require('fs'); 6 | var home = utils.getUserHome() + "/.orcinus"; 7 | var arg; 8 | var cmd = 'docker'; 9 | var cmd_token = ['service','logs'] 10 | module.exports = { 11 | start: (cli, args) => { 12 | arg = args; 13 | if (typeof (cli) == 'string') { 14 | switch (cli) { 15 | case 'follow': 16 | module.exports.follow(args); 17 | break; 18 | case 'tail': 19 | module.exports.tail(args); 20 | break; 21 | default: 22 | module.exports.init(cli) 23 | break; 24 | } 25 | } 26 | else { 27 | module.exports.help(); 28 | } 29 | }, 30 | init: (cli) => { 31 | cmd_token.push(cli); 32 | module.exports.ps(cmd,cmd_token); 33 | }, 34 | follow: (args) => { 35 | if(!args[0]){ 36 | module.exports.help(); 37 | } 38 | else{ 39 | cmd_token.push('-f'); 40 | cmd_token.push(args[0]); 41 | module.exports.ps(cmd,cmd_token); 42 | } 43 | }, 44 | tail: (args) => { 45 | if(args.length > 0){ 46 | cmd_token.push('--tail'); 47 | if(args.length == 2){ 48 | cmd_token.push(args[0].toString()); 49 | cmd_token.push(args[1]); 50 | } 51 | else{ 52 | cmd_token.push('10'); 53 | cmd_token.push(args[1]); 54 | } 55 | module.exports.ps(cmd,cmd_token); 56 | } 57 | else{ 58 | module.exports.help(); 59 | } 60 | }, 61 | help: () => { 62 | console.log("Usage: orcinus logs [COMMAND] SERVICE_NAME"); 63 | console.log(""); 64 | console.log("Manage Orcinus logs service"); 65 | console.log(""); 66 | console.log("Commands:"); 67 | console.log(" follow Follow log output"); 68 | console.log(" tail [line] Number of lines to show from the end of the logs (default=10)"); 69 | ps.exit(0); 70 | }, 71 | ps: (base, token)=>{ 72 | var child = chp.spawn(base,token); 73 | child.stdout.on('data', (data) => { 74 | process.stdout.write(`stdout : ${data}`); 75 | }); 76 | child.stderr.on('data', (data) => { 77 | process.stderr.write(`stderr : ${data}`); 78 | }); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /apis/stack.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var utils = require('../lib/utils.js'); 4 | 5 | router.route("/") 6 | .post(function(req, res, next){ 7 | var user = utils.decode(req,res); 8 | var name = user.id+"-*"; 9 | req.app.locals.orcinus.listStacks(name,function (err, data) { 10 | if(err){ 11 | utils.debug(err); 12 | res.status(err.statusCode).send({error : err.json}); 13 | } 14 | else{ 15 | res.send(data); 16 | } 17 | }); 18 | }); 19 | 20 | router.post("/inspect", function(req, res, next) { 21 | console.log("Stack ID", req.body.id); 22 | var stk = req.app.locals.orcinus.getNetwork(req.body.id); 23 | stk.inspect(function(err, data) { 24 | if(err) { 25 | utils.debug(err); 26 | res.status(err.statusCode).send({error: err.json}); 27 | } 28 | else { 29 | res.send(data); 30 | } 31 | }); 32 | }); 33 | 34 | router.post("/delete", function(req, res, next) { 35 | console.log("Stack ID", req.body.id); 36 | var stk = req.app.locals.orcinus.getNetwork(req.body.id); 37 | stk.remove(function(err, data) { 38 | if(err) { 39 | utils.debug(err); 40 | res.status(err.statusCode).send({error: err.json}); 41 | } 42 | else { 43 | res.send(data); 44 | } 45 | }); 46 | }); 47 | 48 | router.post("/create",function(req, res, next){ 49 | var user = utils.decode(req,res); 50 | var name = user.id+"-"+req.body.name; 51 | console.log("Create Stack : "+name); 52 | req.app.locals.orcinus.createStack(name,function (err, data) { 53 | if(err){ 54 | utils.debug(err); 55 | //res.status(err.statusCode).send({error : err.reason}); 56 | res.status(200).send({error : err.json}); 57 | } 58 | else{ 59 | res.send(data); 60 | } 61 | }); 62 | }); 63 | 64 | router.post("/list-services",function(req, res, next){ 65 | var stackID = req.body.id; 66 | 67 | req.app.locals.orcinus.listServices(function (err, data) { 68 | if(err){ 69 | res.status(err.statusCode).send({error : err.json}); 70 | } 71 | else{ 72 | if(data.length > 0){ 73 | var obj = data.filter(function ( obj ) { 74 | var chk = obj.Endpoint.VirtualIPs.filter(function(objFil){ 75 | return objFil.NetworkID == stackID; 76 | }); 77 | if(chk.length == 0){ 78 | chk = false; 79 | } 80 | else{ 81 | chk = true; 82 | } 83 | return chk; 84 | }); 85 | res.send(obj); 86 | } 87 | else{ 88 | res.send(data); 89 | } 90 | } 91 | }); 92 | }); 93 | 94 | module.exports = router; -------------------------------------------------------------------------------- /apis/passport.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var LocalStrategy = require('passport-local').Strategy; 3 | var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; 4 | var GithubStrategy = require('passport-github').Strategy; 5 | var userModel = mongoose.model('Users'); 6 | var urandom = require('urandom'); 7 | var md5 = require('md5'); 8 | 9 | module.exports = function(passport) { 10 | passport.serializeUser(function(user, done) { 11 | done(null, user.id); 12 | }); 13 | passport.deserializeUser(function(id, done) { 14 | userModel.findById(idd, function(err, user) { 15 | done(err, user); 16 | }); 17 | }); 18 | if ( 19 | process.env.GOOGLE_OAUTH_CLIENT_ID && 20 | process.env.GOOGLE_OAUTH_CLIENT_SECRET && 21 | process.env.GOOGLE_OAUTH_CALLBACK_URL 22 | ) { 23 | passport.use(new GoogleStrategy({ 24 | clientID : process.env.GOOGLE_OAUTH_CLIENT_ID, 25 | clientSecret : process.env.GOOGLE_OAUTH_CLIENT_SECRET, 26 | callbackURL : process.env.GOOGLE_OAUTH_CALLBACK_URL, 27 | }, 28 | function(token, refreshToken, profile, done){ 29 | process.nextTick(function(){ 30 | userModel.findOne({ $or : [{'googleId' : profile.id }, { email : profile.emails[0].value }, { username : profile.emails[0].value } ] }, (err, user) => { 31 | if (err) return done(err); 32 | if (user) { 33 | return done(null, user); 34 | } 35 | var user = new userModel(); 36 | user.googleId = profile.id; 37 | user.googleToken = token; 38 | user.email = profile.emails[0].value; 39 | user.username = profile.emails[0].value; 40 | user.password = md5(urandom.randomIt()); 41 | user.verify = true; 42 | user.admin = false; 43 | user.save((err) => { 44 | if (err) return done(err); 45 | return done(null, user); 46 | }); 47 | }); 48 | }); 49 | })); 50 | } 51 | if ( 52 | process.env.GITHUB_OAUTH_CLIENT_ID && 53 | process.env.GITHUB_OAUTH_CLIENT_SECRET && 54 | process.env.GITHUB_OAUTH_CALLBACK_URL 55 | ) { 56 | passport.use(new GithubStrategy({ 57 | clientID : process.env.GITHUB_OAUTH_CLIENT_ID, 58 | clientSecret : process.env.GITHUB_OAUTH_CLIENT_SECRET, 59 | callbackURL : process.env.GITHUB_OAUTH_CALLBACK_URL, 60 | }, function(token, refreshToken, profile, done){ 61 | process.nextTick(function(){ 62 | userModel.findOne({ $or : [{'githubId' : profile.id }, { username : profile.username } ] }, (err, user) => { 63 | if (err) return done(err); 64 | if (user) { 65 | return done(null, user); 66 | } 67 | var user = new userModel(); 68 | user.githubId = profile.id; 69 | user.githubToken = token; 70 | user.username = profile.username; 71 | user.password = md5(urandom.randomIt()); 72 | user.verify = true; 73 | user.admin = false; 74 | user.save((err) => { 75 | if (err) return done(err); 76 | return done(null, user); 77 | }); 78 | }); 79 | }); 80 | })); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /test/dashboard.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | global.test = true; 4 | 5 | process.env.ORCINUS_HOST = '127.0.0.1'; 6 | process.env.ORCINUS_PORT = '4000'; 7 | process.env.ORCINUS_HTTP_CORS = '*' 8 | process.env.ORCINUS_DB = 'localhost'; 9 | 10 | var pkg = require(__dirname + '/../package.json'); 11 | var mongoose = require('mongoose'); 12 | mongoose.connection.dropDatabase('test'); 13 | var chp = require('child_process'); 14 | var server = require('../server')(); 15 | var request = require('supertest'); 16 | var should = require('should'); 17 | var faker = require('faker'); 18 | var password = faker.internet.password(); 19 | var username, email, token; 20 | 21 | const timeout = 5000; 22 | 23 | // TODO : drop test db before testing 24 | 25 | describe('Orcinus Dashboard', function() { 26 | describe('API', function() { 27 | it('Signup', function(done) { 28 | this.timeout(timeout); 29 | email = faker.internet.email(); 30 | request(server) 31 | .post('/apis/auth/signup') 32 | .send({ 33 | username : 'admin', 34 | password : password, 35 | email : email, 36 | firstname : faker.name.firstName(), 37 | lastname : faker.name.lastName(), 38 | admin : true, 39 | }) 40 | .end((err, res) => { 41 | should(res.statusCode).equal(200); 42 | var result = JSON.parse(res.text); 43 | done(); 44 | }); 45 | }); 46 | it('Failed to signup with existing username', function(done) { 47 | this.timeout(timeout); 48 | request(server) 49 | .post('/apis/auth/signup') 50 | .send({ 51 | username : 'admin', 52 | password : password, 53 | email : faker.internet.email(), 54 | firstname : faker.name.firstName(), 55 | lastname : faker.name.lastName(), 56 | admin : true, 57 | }) 58 | .end((err, res) => { 59 | should(res.statusCode).equal(409); 60 | done(); 61 | }); 62 | }); 63 | it('Failed to signup with existing email', function(done) { 64 | this.timeout(timeout); 65 | request(server) 66 | .post('/apis/auth/signup') 67 | .send({ 68 | username : 'admin1', 69 | password : password, 70 | email : email, 71 | firstname : faker.name.firstName(), 72 | lastname : faker.name.lastName(), 73 | admin : true, 74 | }) 75 | .end((err, res) => { 76 | should(res.statusCode).equal(409); 77 | done(); 78 | }); 79 | }); 80 | it('Get token', function(done) { 81 | this.timeout(timeout); 82 | request(server) 83 | .post('/apis/auth/signin') 84 | .send({ 85 | username : 'admin', 86 | password : password, 87 | }) 88 | .end((err, res) => { 89 | var result = JSON.parse(res.text); 90 | token = result.token; 91 | done(); 92 | }); 93 | }); 94 | it('Ping', function(done) { 95 | this.timeout(timeout); 96 | request(server) 97 | .get('/apis/ping') 98 | .set('X-Access-Token', token) 99 | .end((err, res) => { 100 | should(res.text).equal('OK'); 101 | done(); 102 | }); 103 | }); 104 | it('Unauthorized', function(done) { 105 | this.timeout(timeout); 106 | request(server) 107 | .get('/apis/ping') 108 | .end((err, res) => { 109 | should(res.text).equal('{"success":false,"message":"No token provided."}'); 110 | done(); 111 | }); 112 | }); 113 | it('Info', function(done) { 114 | this.timeout(timeout); 115 | request(server) 116 | .get('/apis/info') 117 | .set('X-Access-Token', token) 118 | .end((err, res) => { 119 | should(res.body.name).equal(pkg.name); 120 | should(res.body.version).equal(pkg.version); 121 | should(res.body.description).equal(pkg.description); 122 | should(res.body.bugs.url).equal(pkg.bugs.url); 123 | should(res.body.deployment).equal('dev'); 124 | should(res.body.cors).equal(process.env.ORCINUS_HTTP_CORS); 125 | done(); 126 | }); 127 | }); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | global.test = true; 4 | 5 | var should = require('should'); 6 | var chp = require('child_process'); 7 | var fs = require('fs'); 8 | var orcinusCreate = require('../lib/create'); 9 | var orcinusRemove = require('../lib/rm'); 10 | const timeout = 120000; 11 | const deployTimeout = 40000 12 | 13 | // TODO fix me 14 | // Check for docker binary and aksaramaya's docker image before running unit testing. 15 | // Make sure there is no container running inside docker 16 | // 17 | // var orcinusPs = require('../lib/ps'); 18 | 19 | describe('Orcinus', function() { 20 | describe('Create', function() { 21 | it('should be able to create a cluster instance via API', function(done) { 22 | this.timeout(timeout); 23 | let data = { 24 | "stack" : "orcinus-unit-testing-stack", 25 | "services": { 26 | "orcinus-ut-web": { 27 | "ports" : [ 28 | "7001:7001", 29 | ], 30 | "environment" : [ 31 | "FOO=bar", 32 | ], 33 | "network": "orcinus-unit-testing-stack", 34 | "image": "aksaramaya/docker-http-server:v1", 35 | "cpu": "1", 36 | "memory": "128mb" 37 | } 38 | } 39 | } 40 | orcinusCreate.init(data); 41 | setTimeout(() => { // wait for docker 42 | let cmd = 'docker ps | sed -n 2,1p | grep "orcinus-ut-web" | cut -d\' \' -f 1'; 43 | chp.exec(cmd, (err, stdout, stderr) => { 44 | stdout.length.should.greaterThan(10); // Container ID length was 12 45 | orcinusRemove.init(data); 46 | setTimeout(() => { 47 | done(); 48 | }, 5000); 49 | }); 50 | }, deployTimeout); 51 | }); 52 | it('should be able to create a cluster instance from a manifest file', function(done) { 53 | this.timeout(timeout); 54 | let data = { 55 | "stack" : "orcinus-unit-testing-stack", 56 | "services": { 57 | "orcinus-ut-web": { 58 | "ports" : [ 59 | "7002:7002", 60 | ], 61 | "environment" : [ 62 | "FOO=bar", 63 | ], 64 | "network": "orcinus-unit-testing-stack", 65 | "image": "aksaramaya/docker-http-server:v1", 66 | "cpu": "1", 67 | "memory": "128mb" 68 | } 69 | } 70 | } 71 | fs.writeFileSync('./test/test.json', JSON.stringify(data)); 72 | chp.exec('cd ' + process.cwd() + ' && node cli.js create -f test/test.json', (err, stdout, stderr) => { 73 | console.log(stdout); 74 | setTimeout(() => { // wait for docker 75 | let cmd = 'docker ps | sed -n 2,1p | grep "orcinus-ut-web" | cut -d\' \' -f 1'; 76 | chp.exec(cmd, (err, stdout, stderr) => { 77 | stdout.length.should.greaterThan(10); // Container ID length was 12 78 | chp.exec('cd ' + process.cwd() + ' && node cli.js rm -f test/test.json'); 79 | setTimeout(() => { 80 | done(); 81 | }, 5000); 82 | }); 83 | }, deployTimeout); 84 | }); 85 | }); 86 | it('should be fail to initialize cluster with no service', function(done) { 87 | this.timeout(timeout); 88 | let data = { 89 | "stack" : "orcinus-unit-testing-stack", 90 | } 91 | try { 92 | orcinusCreate.init(data); 93 | } catch(e) { 94 | let cmd = 'docker ps | sed -n 2,1p | grep orcinus-ut-web | cut -d\' \' -f 1'; 95 | chp.exec(cmd, (err, stdout, stderr) => { 96 | done(); 97 | }); 98 | } 99 | }); 100 | it('should be fail to initialize cluster with non-string stack name', function(done) { 101 | this.timeout(timeout); 102 | let data = { 103 | "stack" : 123, 104 | "services": { 105 | "orcinus-ut-web": { 106 | "image": "aksaramaya/docker-http-server:v1", 107 | "cpu": "1", 108 | "memory": "128mb" 109 | } 110 | } 111 | } 112 | try { 113 | orcinusCreate.init(data); 114 | } catch(e) { 115 | done(); 116 | } 117 | }); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /apis/service.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var parser = require('../lib/parser.js'); 4 | var utils = require('../lib/utils.js'); 5 | 6 | router.post("/",function(req, res, next){ 7 | var user = utils.decode(req,res); 8 | var filters = {filters:{name:{}}}; 9 | var idSVC; 10 | if(req.body.stackname){ 11 | idSVC = user.id+"-"+req.body.stackname; 12 | } 13 | else{ 14 | idSVC = user.id; 15 | } 16 | 17 | filters.filters.name[idSVC] = true; 18 | utils.debug(filters) 19 | req.app.locals.orcinus.listServices(filters,function (err, data) { 20 | if(err){ 21 | res.status(err.statusCode).send({error : err.reason}); 22 | } 23 | else{ 24 | res.send(data); 25 | } 26 | }); 27 | }); 28 | 29 | router.post("/create",function(req, res, next){ 30 | var user = utils.decode(req,res); 31 | utils.serviceManifest(user.id,req,res,function(req,res,manifest,Auth){ 32 | var parse = new parser(manifest.opt); 33 | var opt = parse.services(); 34 | utils.debug(opt); 35 | var responseData = []; 36 | // auth init 37 | var auth = Auth; 38 | opt.forEach(function(ops,index){ 39 | //auth setup 40 | var auth = undefined; 41 | if(this.hasOwnProperty(ops.Name)){ 42 | var auth = this[ops.Name]; 43 | utils.debug(auth); 44 | } 45 | 46 | utils.debug("Auth test : "); 47 | utils.debug(auth) 48 | 49 | req.app.locals.orcinus.createService(auth,ops,function (err, data) { 50 | if(err){ 51 | var error = {}; 52 | error.error = true; 53 | error.app = ops.Name; 54 | error.status = err.statusCode; 55 | error.reason = err.reason; 56 | responseData.push(error); 57 | } 58 | else{ 59 | data.error = false; 60 | data.app = ops.Name; 61 | responseData.push(data); 62 | } 63 | 64 | if((opt.length - 1) == index){ 65 | res.send(responseData); 66 | } 67 | }); 68 | },auth); 69 | }); 70 | }); 71 | 72 | router.post("/inspect",function(req, res, next){ 73 | var svc = req.app.locals.orcinus.getService(req.body.id); 74 | svc.inspect(function (err, data) { 75 | if(err){ 76 | res.status(err.statusCode).send({error : err.reason}); 77 | } 78 | else{ 79 | res.send(data); 80 | } 81 | }); 82 | }); 83 | 84 | router.post("/delete",function(req, res, next){ 85 | var svc = req.app.locals.orcinus.getService(req.body.id); 86 | svc.remove(function (err, data) { 87 | if(err){ 88 | res.status(err.statusCode).send({error : err.reason}); 89 | } 90 | else{ 91 | res.send(data); 92 | } 93 | }); 94 | }); 95 | 96 | router.post("/update",function(req, res, next){ 97 | var spec = new parser(req.body).update(); 98 | var svc = req.app.locals.orcinus.getService(spec.Name); 99 | if(req.body.auth){ 100 | var auth = req.body.auth; 101 | } 102 | 103 | console.log(JSON.stringify(spec)); 104 | svc.update(auth,spec,function (err, data) { 105 | if(err){ 106 | var error = {}; 107 | error.error = true; 108 | error.app = spec.Name; 109 | error.status = err.statusCode; 110 | error.reason = err.reason; 111 | console.log(err); 112 | res.send(error); 113 | } 114 | else{ 115 | data.error = false; 116 | data.app = spec.Name; 117 | res.send(data); 118 | } 119 | }) 120 | }); 121 | 122 | router.post("/task",function(req, res, next){ 123 | var filters = {filters:{service:{}}}; 124 | var idSVC = req.body.service; 125 | filters.filters.service[idSVC] = true; 126 | req.app.locals.orcinus.listTasks(filters,function (err, data) { 127 | if(err){ 128 | res.status(err.statusCode).send({error : err.reason}); 129 | } 130 | else{ 131 | res.send(data); 132 | } 133 | }); 134 | }); 135 | 136 | module.exports = router; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Orcinus 2 | ![Build Status](https://travis-ci.org/orcinustools/orcinus.svg?branch=dev) 3 | 4 | Container orchestration management tools for docker swarm mode. 5 | 6 | # Quick Start 7 | 8 | ## Requirements 9 | * Linux 64 bit 10 | * Docker 1.12+ 11 | * Nodejs (optional) 12 | 13 | ## Installation 14 | 15 | #### Use NPM 16 | 17 | ```bash 18 | npm install orcinus -g 19 | ``` 20 | #### Standalone binary 21 | 22 | ```bash 23 | curl -L http://dl.aksaramaya.id/orcinus/stable/orcinus-linux-x86_64.bin -o /usr/bin/orcinus 24 | chmod +x /usr/bin/orcinus 25 | ``` 26 | 27 | ## Cluster Setup 28 | 29 | ### Prerequisites 30 | 31 | * One or more linux 64 bit machines. Example : 32 | - 1 manager with ip address : 192.168.7.11. 33 | - 1 worker with ip address : 192.168.7.12. 34 | * Full network connectivity between all machines in the cluster (public or private network is fine). 35 | * Install orcinus to each machines. 36 | 37 | ### Setup manager 38 | * SSH into the machine and become **root** if you are not already (for example, run `sudo su -`). 39 | * Initializing your machine as manager. 40 | ```bash 41 | [root@192.168.7.11 ~]$ orcinus cluster init [IP ADDRESS MACHINE] 42 | [root@192.168.7.11 ~]$ orcinus cluster init 192.168.7.11 43 | Add a worker to this manager. 44 | 45 | Token : eyJhZGRyIjoiMTkyLjE2OC43LjExOjIzNzciLCJ0b2tlbiI6IlNXTVRLTi0xLTVqbmZ3b3ltbW1haW5nb3poNnh2Y3ZreDA0N3NlOTJrYmF2dXlscTlkbDF5b3czcWliLTUzM2dwbjN4b2lxeWJkOHN2NXl2bzg2anFcbiJ9 46 | 47 | or run the following command: 48 | orcinus cluster join eyJhZGRyIjoiMTkyLjE2OC43LjExOjIzNzciLCJ0b2tlbiI6IlNXTVRLTi0xLTVqbmZ3b3ltbW1haW5nb3poNnh2Y3ZreDA0N3NlOTJrYmF2dXlscTlkbDF5b3czcWliLTUzM2dwbjN4b2lxeWJkOHN2NXl2bzg2anFcbiJ9 49 | # Get manager token 50 | [root@192.168.7.11 ~]$ orcinus cluster token 51 | ``` 52 | 53 | ### Setup Worker 54 | If you want to add any new machines as worker to your cluster, for each machine: 55 | * SSH into the machine and become **root** if you are not already (for example, run `sudo su -`). 56 | * Initializing your machine as a worker. 57 | ```bash 58 | [root@192.168.7.12 ~]$ orcinus cluster join [TOKEN] 59 | [root@192.168.7.12 ~]$ orcinus cluster join eyJhZGRyIjoiMTkyLjE2OC43LjExOjIzNzciLCJ0b2tlbiI6IlNXTVRLTi0xLTVqbmZ3b3ltbW1haW5nb3poNnh2Y3ZreDA0N3NlOTJrYmF2dXlscTlkbDF5b3czcWliLTUzM2dwbjN4b2lxeWJkOHN2NXl2bzg2anFcbiJ9 60 | This node joined a cluster as a worker. 61 | ``` 62 | 63 | ### Get all nodes information 64 | Get information all nodes cluster. 65 | * SSH into the manager machine and become **root** if you are not already (for example, run `sudo su -`). 66 | ```bash 67 | [root@192.168.7.11 ~]$ orcinus cluster ls 68 | ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS 69 | 6hbhi274x0gslf1bfuu1ei91r * ak1 Ready Active Leader 70 | ecyy1uswuciolsfve4vn38h8m ak2 Ready Active 71 | # Inspect node 72 | [root@192.168.7.11 ~]$ orcinus cluster inspect ak1 73 | ID: 6hbhi274x0gslf1bfuu1ei91r 74 | Hostname: ak1 75 | Joined at: 2017-01-08 09:11:56.313485437 +0000 utc 76 | Status: 77 | State: Ready 78 | Availability: Active 79 | Manager Status: 80 | Address: 192.168.7.11:2377 81 | Raft Status: Reachable 82 | Leader: Yes 83 | Platform: 84 | Operating System: linux 85 | Architecture: x86_64 86 | Resources: 87 | CPUs: 1 88 | Memory: 489 MiB 89 | Plugins: 90 | Network: bridge, host, null, overlay 91 | Volume: local 92 | Engine Version: 1.12.3 93 | ``` 94 | 95 | ### Deploy Services 96 | Deploy your first service. 97 | ```bash 98 | # orcinus compose 99 | [root@192.168.7.11 ~]$ mkdir test/ 100 | [root@192.168.7.11 test]$ ls 101 | orcinus.yml 102 | [root@192.168.7.11 test]$ cat orcinus.yml 103 | stack: "mystack" 104 | services: 105 | web1: 106 | image: "aksaramaya/docker-http-server:v1" 107 | ports: 108 | - "80:80" 109 | replicas: 4 110 | cpu: "2" 111 | memory: "512mb" 112 | web2: 113 | image: "nginx" 114 | cpu: "2" 115 | memory: "512mb" 116 | # create service 117 | [root@192.168.7.11 test]$ orcinus create 118 | Service web2 created 119 | 2cct8xzckyfwkmhlfprxy8tj3 120 | 121 | Service web1 created 122 | 50a7ftc5f1jjvsz09h1bwt487 123 | # list Services 124 | [root@192.168.7.11 test]$ orcinus ls 125 | 2cct8xzckyfw web2 0/1 nginx 126 | 127 | 50a7ftc5f1jj web1 3/3 aksaramaya/docker-http-server 128 | # remove service 129 | root@192.168.7.11 test]$ orcinus rm 130 | ``` 131 | 132 | ### Test 133 | 134 | #### Orcinus CLI 135 | 136 | ``` 137 | $ npm run test 138 | ``` 139 | 140 | #### Dashboard APIs 141 | 142 | ``` 143 | $ npm run dashboard-test 144 | ``` 145 | 146 | # Documentation 147 | * [Wiki](https://github.com/orcinustools/orcinus/wiki) 148 | -------------------------------------------------------------------------------- /apis/auth.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | var jwt = require('jsonwebtoken'); 6 | var jwtDecode = require('jwt-decode'); 7 | var utils = require('../lib/utils.js'); 8 | var userModel = mongoose.model('Users'); 9 | var passport; 10 | 11 | /* Google OAuth */ 12 | if ( 13 | process.env.GOOGLE_OAUTH_CLIENT_ID && 14 | process.env.GOOGLE_OAUTH_CLIENT_SECRET && 15 | process.env.GOOGLE_OAUTH_CALLBACK_URL 16 | ) { 17 | passport = require('passport'); 18 | require(__dirname + '/passport.js')(passport); 19 | 20 | router.route("/google") 21 | .get(passport.authenticate('google', { scope : ['profile', 'email'] } )); 22 | 23 | router.route("/google-callback") 24 | .get(passport.authenticate('google'), (req, res) => { 25 | if (!req.user || (req.user && !req.user.username)) { 26 | return res.redirect('/'); 27 | } 28 | var userJWT = { 29 | username : req.user.username, 30 | email : req.user.email, 31 | id : req.user._id 32 | }; 33 | var token = jwt.sign(userJWT, req.app.locals.secret, { 34 | expiresIn: 60*60 // expires in 1 hours 35 | }); 36 | res.send(``); 37 | }); 38 | } 39 | 40 | /* Github OAuth */ 41 | if ( 42 | process.env.GITHUB_OAUTH_CLIENT_ID && 43 | process.env.GITHUB_OAUTH_CLIENT_SECRET && 44 | process.env.GITHUB_OAUTH_CALLBACK_URL 45 | ) { 46 | if (!passport) { 47 | passport = require('passport'); 48 | require(__dirname + '/passport.js')(passport); 49 | } 50 | 51 | router.route("/github") 52 | .get(passport.authenticate('github', { scope : ['profile', 'email'] } )); 53 | 54 | router.route("/github-callback") 55 | .get(passport.authenticate('github'), (req, res) => { 56 | if (!req.user || (req.user && !req.user.username)) { 57 | return res.redirect('/'); 58 | } 59 | var userJWT = { 60 | username : req.user.username, 61 | email : req.user.email, 62 | id : req.user._id 63 | }; 64 | var token = jwt.sign(userJWT, req.app.locals.secret, { 65 | expiresIn: 60*60 // expires in 1 hours 66 | }); 67 | res.send(``); 68 | }); 69 | } 70 | 71 | /* GET home page. */ 72 | router.route("/signup") 73 | .get(function(req, res, next) { 74 | res.send({}); 75 | }) 76 | .post(function(req, res, next) { 77 | userModel.find({ $or : [ { email : req.body.email}, { username : req.body.username } ] }, (err, result) => { 78 | if (err) return res.status(500).json(err); 79 | if (result && result.length > 0) return res.status(409).json(new Error('Username or email already exists')); 80 | var userData = { 81 | email: req.body.email, 82 | firstname: req.body.firstname, 83 | lastname: req.body.lastname, 84 | password: req.body.password, 85 | username: req.body.username, 86 | admin: false 87 | } 88 | var user = new userModel(userData); 89 | user.save(function(error, data){ 90 | if(error) return res.status(403).json(error); 91 | res.json(data); 92 | }); 93 | }); 94 | }); 95 | 96 | router.route("/signin") 97 | .post(function(req, res, next) { 98 | userModel.findOne({ 99 | username: req.body.username 100 | }, function(err, user) { 101 | 102 | if (err) throw err; 103 | 104 | if (!user) { 105 | res.status(403).json({ success: false, message: 'Authentication failed. User not found.' }); 106 | } 107 | else if (user) { 108 | user.comparePass(req.body.password, function(err, isMatch) { 109 | if (err) throw err; 110 | if(isMatch){ 111 | userJWT = { 112 | username : user.username, 113 | email : user.email, 114 | id : user._id 115 | }; 116 | var token = jwt.sign(userJWT, req.app.locals.secret, { 117 | expiresIn: 60*60 // expires in 1 hours 118 | }); 119 | // return the information including token as JSON 120 | res.json({ 121 | success: true, 122 | message: 'success!', 123 | token: token 124 | }); 125 | } 126 | else{ 127 | res.status(403).json({ success: false, message: 'Authentication failed. Wrong password.' }); 128 | } 129 | }); 130 | } 131 | }); 132 | }); 133 | 134 | router.route("/me") 135 | .all(function(req, res, next) { 136 | var token = req.body.token || req.query.token || req.headers['x-access-token']; 137 | if(token){ 138 | var decoded = jwtDecode(token); 139 | res.json(decoded); 140 | } 141 | else{ 142 | res.json({}); 143 | } 144 | }) 145 | 146 | module.exports = router; 147 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | var process = require('process'); 2 | var yml = require('yamljs'); 3 | var fileExt = require('file-extension'); 4 | var fs = require('fs'); 5 | var jwtDecode = require('jwt-decode'); 6 | var colors = require('colors'); 7 | var parseImg = require('docker-parse-image'); 8 | 9 | module.exports = { 10 | parser : (file)=>{ 11 | // Favor yaml over json 12 | if (fileExt(file) === 'yml') { 13 | return yml.load(file); 14 | } 15 | return require(file); 16 | }, 17 | obj : (val)=>{ 18 | return Object.keys(val); 19 | }, 20 | checkObj : (obj,key)=>{ 21 | return obj.hasOwnProperty(key) 22 | }, 23 | getUserHome : ()=>{ 24 | return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME']; 25 | }, 26 | exit : (code) => { 27 | code = code || 0; 28 | if (global.test) { // Prevent actual exit on unit testing 29 | throw new Error(); 30 | } 31 | process.exit(code) 32 | }, 33 | decode : (req,res) => { 34 | var token = req.body.token || req.query.token || req.headers['x-access-token']; 35 | if(token){ 36 | var decoded = jwtDecode(token); 37 | return decoded; 38 | } 39 | else{ 40 | res.json({error:"Token invalid!"}); 41 | } 42 | }, 43 | authParser : (manifestAuth,image)=>{ 44 | var Auth; 45 | if(typeof(manifestAuth) === 'boolean'){ 46 | var home = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; 47 | var dockerAuth = require(home+"/.docker/config.json"); 48 | var registryURL = parseImg(image).registry; 49 | var regData = dockerAuth.auths[registryURL]; 50 | if(regData){ 51 | var regUser = new Buffer(regData.auth, 'base64'); 52 | regUser = regUser.toString().split(":"); 53 | var tmplAuth = auth = { 54 | username: regUser[0], 55 | password: regUser[1], 56 | serveraddress: registryURL 57 | } 58 | Auth = tmplAuth 59 | } 60 | } 61 | if(typeof(manifestAuth) === 'object'){ 62 | Auth = manifestAuth; 63 | } 64 | module.exports.debug("Auth check:") 65 | module.exports.debug(Auth); 66 | return Auth; 67 | }, 68 | serviceManifest : (userID,req,res,callback) => { 69 | var manifest = req.body; 70 | var obj = JSON.parse(JSON.stringify(manifest)) 71 | delete obj.opt.services 72 | obj.opt.services = {}; 73 | var Auth = {}; 74 | 75 | var stack = userID+"-"+manifest.opt.stack; 76 | obj.opt.stack = stack 77 | 78 | Object.keys(manifest.opt.services).reverse().forEach(function(v){ 79 | // init 80 | var serviceName = stack+"-"+v; 81 | // Authentication 82 | var manifestAuth = manifest.opt.services[v].auth; 83 | if(manifestAuth){ 84 | Auth[serviceName] = module.exports.authParser(manifestAuth,manifest.opt.services[v].image) 85 | } 86 | 87 | if(process.env.ORCINUS == "prod"){ 88 | manifest.opt.services[v].constraint = "node.role==worker"; 89 | } 90 | // if endpoint key set (production) 91 | if(manifest.opt.services[v].endpoint){ 92 | console.log("endpoint........") 93 | var NETWORK = process.env.ORCINUS_NETWORK; 94 | var DOMAIN = process.env.ORCINUS_DOMAIN; 95 | 96 | manifest.opt.services[v].labels = { "traefik.port" : manifest.opt.services[v].endpoint }; 97 | delete manifest.opt.services[v].endpoint; 98 | 99 | if(!manifest.opt.services[v].networks){ 100 | manifest.opt.services[v].networks = [NETWORK]; 101 | module.exports.debug("Network manifest not existing set : "+manifest.opt.services[v].networks); 102 | } 103 | else{ 104 | manifest.opt.services[v].networks.push(NETWORK); 105 | module.exports.debug("Network manifest existing set : "+manifest.opt.services[v].networks); 106 | } 107 | // setup custom domain 108 | var domainSVC = serviceName+"."+DOMAIN; 109 | 110 | if(manifest.opt.domain){ 111 | manifest.opt.services[v].labels["traefik.frontend.rule"] = "Host:"+manifest.opt.domain; 112 | domainSVC = manifest.opt.domain; 113 | } 114 | 115 | module.exports.debug("Set Domain to : "+domainSVC); 116 | manifest.opt.services[v].labels["orcinus.domain"] = domainSVC; 117 | manifest.opt.services[v].labels["traefik.docker.network"] = NETWORK; 118 | } 119 | 120 | var modifySVC = JSON.parse(JSON.stringify(manifest.opt.services[v]).replace(/{orcinus.userid}/g,userID).replace(/{orcinus.stack}/g,manifest.opt.stack)); 121 | module.exports.debug("manifest file : "+JSON.stringify(modifySVC)); 122 | obj.opt.services[serviceName] = modifySVC; 123 | }); 124 | 125 | module.exports.debug("Auth all check:") 126 | module.exports.debug(Auth); 127 | callback(req,res,obj,Auth); 128 | }, 129 | debug : (data) => { 130 | if(process.env.ORCINUS == "dev"){ 131 | if(typeof(data) === 'object'){ 132 | var jsonData = JSON.stringify(data); 133 | console.log(colors.yellow(jsonData)); 134 | } 135 | else{ 136 | console.log(colors.yellow(data)); 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var program = require('commander'); 4 | var pkg = require('./package.json'); 5 | var process = require('process'); 6 | var chp = require('child_process'); 7 | var colors = require('colors'); 8 | var fs = require('fs'); 9 | var exec = chp.exec; 10 | var spawn = chp.spawn; 11 | var data,port; 12 | 13 | /*module exports*/ 14 | var utils = require("./lib/utils"); 15 | var create = require("./lib/create"); 16 | var update = require("./lib/update"); 17 | var list = require("./lib/list"); 18 | var scale = require("./lib/scale"); 19 | var ps = require("./lib/ps"); 20 | var rm = require("./lib/rm"); 21 | var inspect = require("./lib/inspect"); 22 | var rollback = require("./lib/rollback"); 23 | var cluster = require("./lib/cluster"); 24 | var logs = require("./lib/logs"); 25 | 26 | /*module dashboard*/ 27 | var web = require("./server"); 28 | 29 | program 30 | .version("orcinus version "+pkg.version) 31 | .option('-f,--file ','Orcinus file') 32 | .option('create', 'Create service') 33 | .option('rm','Remove all service') 34 | .option('ls [all|orcinus file|service name]', 'List service') 35 | .option('ps', 'List all process') 36 | .option('scale [service_name=num_scale]', 'Scale service') 37 | .option('inspect', 'Inspect all service') 38 | .option('update', 'Update service') 39 | .option('rollback', 'Rollback service') 40 | .option('dashboard', 'Start dashboard ') 41 | .option('logs [follow|help|tail]', 'Get service logs') 42 | .option('cluster [option|help]', 'Manage Cluster',/^(init|join|leave|leave-manager|ls|token|promote|inspect|option|--help|-h)$/i) 43 | .parse(process.argv); 44 | 45 | if(utils.obj(program).length < 14){ 46 | err(); 47 | } 48 | 49 | if(program.file){ 50 | /*parsing file*/ 51 | if (program.file[0] != '/' && program.file[0] != '~') { // If not an absolute path 52 | program.file = process.cwd() + ((process.platform == 'win32')?'\\':'/') + program.file; 53 | } 54 | if (!fs.existsSync(program.file)) { 55 | console.log('File doesn\'t exist'); 56 | return err(); 57 | } 58 | 59 | data = utils.parser(program.file); 60 | //cliValidation(); 61 | } 62 | 63 | if(!program.dashboard && !program.cluster && !program.ls && !program.scale && !program.logs){ 64 | if(!data){ 65 | var defaultManifest = "orcinus"; 66 | // Favor yaml over json 67 | if (fs.existsSync(defaultManifest + '.yml')) { 68 | defaultManifest += '.yml'; 69 | } else if (fs.existsSync(defaultManifest + '.json')) { 70 | defaultManifest += '.json'; 71 | } else { 72 | console.log('Default manifest file doesn\'t exist. Expected a *.yml or *.json file.'); 73 | return err(); 74 | } 75 | data = utils.parser(process.cwd() + ((process.platform == 'win32')?'\\':'/') + defaultManifest); 76 | //cliValidation(); 77 | } 78 | } 79 | 80 | if(program.create){ 81 | if(!data){ 82 | err() 83 | } 84 | create.init(data); 85 | } 86 | 87 | if(program.update){ 88 | if(!data){ 89 | err() 90 | } 91 | update.init(data); 92 | } 93 | 94 | if(program.rollback){ 95 | if(!data){ 96 | err() 97 | } 98 | rollback.init(data); 99 | } 100 | 101 | if(program.ls){ 102 | if(typeof(program.ls) == "boolean"){ 103 | if(!program.file){ 104 | var defaultManifest = "orcinus.yml"; 105 | if(fs.existsSync(defaultManifest)){ 106 | var data = utils.parser(defaultManifest); 107 | list.init(data); 108 | } 109 | else{ 110 | err(); 111 | } 112 | } 113 | else{ 114 | list.init(data); 115 | } 116 | } 117 | else{ 118 | if(program.ls == "all"){ 119 | list.all(); 120 | } 121 | else{ 122 | ps.prs(program.ls); 123 | } 124 | } 125 | } 126 | 127 | if(program.ps){ 128 | if(!data){ 129 | err() 130 | } 131 | ps.init(data); 132 | } 133 | 134 | if(program.rm){ 135 | if(!data){ 136 | err() 137 | } 138 | rm.init(data); 139 | } 140 | 141 | if(program.inspect){ 142 | if(!data){ 143 | err() 144 | } 145 | inspect.init(data); 146 | } 147 | 148 | if(program.scale){ 149 | if(typeof(program.scale) == 'string'){ 150 | var scaleData = program.scale.split("="); 151 | if(scaleData.length == 2){ 152 | scale(scaleData[0],scaleData[1]); 153 | } 154 | else{ 155 | err(); 156 | } 157 | } 158 | else{ 159 | err(); 160 | } 161 | } 162 | 163 | if(program.cluster){ 164 | var args = program.args; 165 | var cli = program.cluster; 166 | cluster.start(cli,args); 167 | } 168 | 169 | if(program.logs){ 170 | var args = program.args; 171 | var cli = program.logs; 172 | logs.start(cli,args); 173 | } 174 | 175 | if(program.dashboard){ 176 | if(program.args.length > 0){ 177 | var environtment = program.args[0].split(":"); 178 | process.env['ORCINUS_HOST'] = environtment[0]; 179 | process.env['ORCINUS_PORT'] = environtment[1]; 180 | } 181 | else{ 182 | process.env['ORCINUS_HOST'] = "0.0.0.0"; 183 | process.env['ORCINUS_PORT'] = "4000"; 184 | } 185 | web(); 186 | } 187 | 188 | function make_red(txt) { 189 | return colors.red(txt); 190 | } 191 | 192 | function make_yellow(txt) { 193 | return colors.yellow(txt); 194 | } 195 | 196 | function err(){ 197 | program.outputHelp(); 198 | console.log(' Examples:'); 199 | console.log(''); 200 | console.log(' $ orcinus scale app=2'); 201 | console.log(' $ orcinus [options] docker-compose-file.yml'); 202 | console.log(''); 203 | process.exit(0); 204 | } 205 | 206 | function cliValidation(){ 207 | cli = []; 208 | prog = utils.obj(program); 209 | program.options.forEach((v)=>{ 210 | if(prog.indexOf(v.flags) >= 0) cli.push(v.flags); 211 | }) 212 | if(cli.length > 2){ 213 | console.log("More options."); 214 | program.outputHelp(make_red); 215 | process.exit(0); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var package = require('./package.json'); 2 | var compression = require('compression'); 3 | var path = require('path'); 4 | var express = require('express'); 5 | var cors = require('cors'); 6 | var app = express(); 7 | var path = require('path'); 8 | var favicon = require('serve-favicon'); 9 | var logger = require('morgan'); 10 | var cookieParser = require('cookie-parser'); 11 | var bodyParser = require('body-parser'); 12 | var url = require("url"); 13 | var colors = require('colors'); 14 | var passport = require('passport'); 15 | var orcinusd = require('orcinusd'); 16 | var db = require("./db"); 17 | var jwt = require('jsonwebtoken'); 18 | var authMW = express.Router(); 19 | 20 | module.exports = function(){ 21 | /* 22 | Environment : 23 | ORCINUS_HOST= 24 | ORCINUS_PORT= 25 | ORCINUS_HTTP_CORS= example : http://domain1.com, http://domain2.com or * 26 | */ 27 | var DEPLOYMENT = process.env.ORCINUS || "dev"; 28 | var PORT = process.env.ORCINUS_PORT || 4000; 29 | var HOST = process.env.ORCINUS_HOST || "0.0.0.0"; 30 | var CORS = process.env.ORCINUS_HTTP_CORS || false; 31 | var SOCK = process.env.ORCINUS_DOCKER_SOCKET || "/var/run/docker.sock"; 32 | var DBHOST = process.env.ORCINUS_DB || "orcinus-db/orcinus"; 33 | var OMURA = process.env.ORCINUS_OMURA || "omura.orcinus.id"; 34 | 35 | if(!process.env.ORCINUS_SECRET){ 36 | process.env.ORCINUS_SECRET = "orcinus" 37 | } 38 | var SECRET = process.env.ORCINUS_SECRET; 39 | 40 | if(DEPLOYMENT === 'prod'){ 41 | process.env.NODE_ENV = production; 42 | } 43 | 44 | if(!process.env.ORCINUS_DOMAIN){ 45 | process.env.ORCINUS_DOMAIN = "svc.orcinus.id" 46 | } 47 | var ENDPOINT = process.env.ORCINUS_DOMAIN; 48 | 49 | if(!process.env.ORCINUS_NETWORK){ 50 | process.env.ORCINUS_NETWORK = "orcinus" 51 | } 52 | var NETWORK = process.env.ORCINUS_NETWORK; 53 | 54 | var ping = require("./apis/ping"); 55 | var info = require("./apis/info"); 56 | var cluster = require("./apis/cluster"); 57 | var service = require("./apis/service"); 58 | var stack = require("./apis/stack"); 59 | var task = require("./apis/task"); 60 | var volume = require("./apis/volume"); 61 | var container = require("./apis/container"); 62 | var auth = require("./apis/auth"); 63 | 64 | /* 65 | * Database connection 66 | */ 67 | 68 | app.locals.orcinusdb = db(DBHOST); 69 | db.users(); 70 | 71 | var corsOpt = { 72 | "origin": false, 73 | "methods": "GET,HEAD,PUT,PATCH,POST,DELETE", 74 | "preflightContinue": false, 75 | "optionsSuccessStatus": 204, 76 | "allowedHeaders": ["Origin", "X-Requested-With", "Content-Type", "Accept", "Authorization", "x-access-token"] 77 | } 78 | 79 | if(CORS){ 80 | console.log(colors.yellow("==> HTTP CORS Active!")); 81 | corsOpt = { 82 | "origin": CORS, 83 | "methods": "GET,HEAD,PUT,PATCH,POST,DELETE", 84 | "preflightContinue": false, 85 | "optionsSuccessStatus": 204, 86 | "allowedHeaders": ["Origin", "X-Requested-With", "Content-Type", "Accept", "Authorization", "x-access-token"] 87 | } 88 | } 89 | 90 | app.options('*', cors(corsOpt)) 91 | 92 | if(SOCK.indexOf("http") >= 0 || SOCK.indexOf("https") >= 0){ 93 | var sockParse = url.parse(SOCK); 94 | var proto = sockParse.protocol.replace(":",""); 95 | var host = sockParse.hostname; 96 | var port = sockParse.port; 97 | SOCK = {protocol: proto, host: host, port: port}; 98 | } 99 | else{ 100 | SOCK = { socketPath: SOCK }; 101 | } 102 | 103 | if(DEPLOYMENT === 'dev'){ 104 | app.use(logger('dev')); 105 | } 106 | 107 | app.use(bodyParser.json()); 108 | app.use(bodyParser.urlencoded({ extended: false })); 109 | app.use(cookieParser()); 110 | app.use(compression()); 111 | 112 | app.locals.orcinus = new orcinusd(SOCK); 113 | app.locals.secret = SECRET; 114 | 115 | app.use(express.static(path.join(__dirname, './www'))); 116 | 117 | app.use(passport.initialize()); 118 | 119 | app.get('/',function(req,res){ 120 | res.sendFile(path.join(__dirname, './www', 'index.html')); 121 | }); 122 | 123 | /* 124 | * Authentication 125 | */ 126 | 127 | // midleware 128 | 129 | authMW.use(cors(corsOpt),function(req, res, next) { 130 | // check header or url parameters or post parameters for token 131 | var token = req.body.token || req.query.token || req.headers['x-access-token']; 132 | // decode token 133 | if (token) { 134 | 135 | // verifies secret and checks exp 136 | jwt.verify(token, SECRET, function(err, decoded) { 137 | if (err) { 138 | return res.status(403).json({ success: false, message: 'Failed to authenticate token.' }); 139 | } else { 140 | // if everything is good, save to request for use in other routes 141 | req.decoded = decoded; 142 | next(); 143 | } 144 | }); 145 | 146 | } else { 147 | 148 | // if there is no token 149 | // return an error 150 | return res.status(403).send({ 151 | success: false, 152 | message: 'No token provided.' 153 | }); 154 | 155 | } 156 | }); 157 | 158 | app.use('/apis/auth', cors(corsOpt), auth); 159 | 160 | /* 161 | * INFO 162 | */ 163 | 164 | app.use('/apis/info', cors(corsOpt), function(req, res, next) { 165 | var info = { 166 | name: package.name, 167 | version: package.version, 168 | description: package.description, 169 | bugs: package.bugs, 170 | repository: OMURA, 171 | cors: corsOpt.origin, 172 | deployment: DEPLOYMENT, 173 | endpoint: ENDPOINT 174 | } 175 | res.send(info); 176 | }); 177 | 178 | /* 179 | * Apis router 180 | */ 181 | 182 | app.use('/apis/ping', authMW, ping); 183 | app.use('/apis/info', authMW, info); 184 | app.use('/apis/cluster', authMW, cluster); 185 | app.use('/apis/service', authMW, service); 186 | app.use('/apis/stack', authMW, stack); 187 | app.use('/apis/task', authMW, task); 188 | app.use('/apis/volume', authMW, volume); 189 | app.use('/apis/container', authMW, container); 190 | 191 | // catch 404 and forward to error handler 192 | app.use(function(req, res, next) { 193 | var err = new Error('Not Found'); 194 | err.status = 404; 195 | res.sendFile(path.join(__dirname, './www', 'index.html')); 196 | }); 197 | 198 | app.listen(parseInt(PORT), HOST, function(error) { 199 | if (error) { 200 | console.error(error); 201 | } else { 202 | console.info(colors.green("==> Listening on port %s. Visit http://%s:%s/ in your browser."), PORT,HOST, PORT); 203 | } 204 | }); 205 | return app; 206 | } 207 | -------------------------------------------------------------------------------- /lib/create.js: -------------------------------------------------------------------------------- 1 | var utils = require("./utils"); 2 | var proc = require('process'); 3 | var chp = require('child_process'); 4 | var val, 5 | volumes = {}, 6 | cmdData = [], 7 | stackExec = {}, 8 | command = ""; 9 | 10 | module.exports = { 11 | init: (value) => { 12 | val = value; 13 | var svc = utils.obj(val); 14 | if (svc.indexOf('services') < 0) { 15 | console.log("Service can't defined."); 16 | if (!utils.exit(1)) return; 17 | } 18 | if (svc.indexOf('volumes') >= 0) { 19 | module.exports.volumes(val.volumes); 20 | } 21 | if (svc.indexOf('stack') >= 0) { 22 | var stack = val.stack; 23 | if (typeof stack != 'string') { 24 | console.log("Stack data is not valid!"); 25 | if (!utils.exit(0)) return; 26 | } 27 | stackExec.name = stack; 28 | var cmd = "docker network ls -f 'name=" + stack + "' |grep " + stack; 29 | chp.exec(cmd, (e, stdout, stderr) => { 30 | if (!stdout) { 31 | module.exports.stack(stack, val.services); 32 | } 33 | else { 34 | module.exports.services(val.services); 35 | } 36 | if (stderr) { 37 | console.log(stderr); 38 | if (!utils.exit(0)) return; 39 | } 40 | }) 41 | } 42 | else { 43 | module.exports.services(val.services); 44 | } 45 | }, 46 | services: (data) => { 47 | var app = utils.obj(data); 48 | app.forEach((v) => { 49 | module.exports.prs(v); 50 | }); 51 | }, 52 | execution: (keyMap) => { 53 | var opt = keyMap.opt.join(" "); 54 | var img = keyMap.img; 55 | var name = keyMap.name; 56 | var exec = "" 57 | if (typeof (keyMap.cmd) == "object") { 58 | exec = keyMap.cmd.join(" "); 59 | } 60 | var cmd = "docker service create -d " + opt + " --name " + name + " " + img + " " + exec; 61 | //console.log(cmd); 62 | chp.exec(cmd, (e, stdout, stderr) => { 63 | if (stdout) { 64 | console.log("Service " + keyMap.name + " created"); 65 | console.log(stdout); 66 | } 67 | if (stderr) { 68 | console.log(stderr); 69 | if (!utils.exit(0)) return; 70 | } 71 | }) 72 | }, 73 | prs: (key) => { 74 | var app = val.services[key]; 75 | var image = app.image; 76 | var opt = module.exports.opt(app); 77 | 78 | var keyMap = { 79 | opt: opt, 80 | img: image, 81 | name: key, 82 | cmd: command 83 | } 84 | module.exports.execution(keyMap); 85 | }, 86 | opt: (app) => { 87 | var arr = []; 88 | var cmd = utils.obj(app); 89 | 90 | if (cmd.indexOf("commands") >= 0) { 91 | command = app.commands; 92 | } 93 | else { 94 | command = "" 95 | } 96 | 97 | if (cmd.indexOf("volumes") >= 0) { 98 | app.volumes.forEach((v) => { 99 | if (!volumes[v]) { 100 | console.log("Volume data is not exist!"); 101 | if (!utils.exit(0)) return; 102 | } 103 | arr.push("--mount " + volumes[v]); 104 | }); 105 | } 106 | if (cmd.indexOf("auth") >= 0) { 107 | if (app.auth) { 108 | arr.push("--with-registry-auth"); 109 | } 110 | } 111 | if (cmd.indexOf("mode") >= 0) { 112 | if (app.mode) { 113 | arr.push("--mode " + app.mode); 114 | } 115 | } 116 | if (cmd.indexOf("restart_policy") >= 0) { 117 | if (app.restart_policy.condition) { 118 | arr.push("--restart-condition " + app.restart_policy.condition); 119 | } 120 | if (app.restart_policy.delay) { 121 | arr.push("--restart-delay " + app.restart_policy.delay); 122 | } 123 | } 124 | if (cmd.indexOf("ports") >= 0) { 125 | app.ports.forEach((v) => { 126 | arr.push("-p " + v); 127 | }); 128 | } 129 | if (cmd.indexOf("environment") >= 0) { 130 | app.environment.forEach((v) => { 131 | arr.push("--env " + v); 132 | }); 133 | } 134 | if (cmd.indexOf("sysctls") >= 0) { 135 | app.sysctls.forEach((v) => { 136 | arr.push("--sysctl " + v); 137 | }); 138 | } 139 | if (cmd.indexOf("deploy") >= 0) { 140 | if(app.deploy.delay) arr.push("--update-delay " + app.deploy.delay); 141 | if(app.deploy.iffailure) arr.push("--update-failure-action " + app.deploy.iffailure); 142 | if(app.deploy.order) arr.push("--update-order " + app.deploy.order); 143 | } 144 | if (cmd.indexOf("user") >= 0) { 145 | arr.push("--user " + app.user); 146 | } 147 | if (cmd.indexOf("replicas") >= 0) { 148 | arr.push("--replicas " + app.replicas); 149 | } 150 | if (cmd.indexOf("labels") >= 0) { 151 | utils.obj(app.labels).forEach(function (key) { 152 | arr.push("--label " + key + "=" + app.labels[key]); 153 | }); 154 | } 155 | if (cmd.indexOf("cpu") >= 0) { 156 | arr.push("--limit-cpu " + app.cpu); 157 | } 158 | if (cmd.indexOf("memory") >= 0) { 159 | arr.push("--limit-memory " + app.memory); 160 | } 161 | if (cmd.indexOf("constraint") >= 0) { 162 | arr.push("--constraint " + app.constraint); 163 | } 164 | if (cmd.indexOf("constraints") >= 0) { 165 | app.constraints.forEach((v) => { 166 | arr.push("--constraint " + v); 167 | }); 168 | } 169 | if (cmd.indexOf("networks") >= 0) { 170 | app.networks.forEach(function (v) { 171 | arr.push("--network " + v); 172 | }); 173 | } 174 | if (stackExec.name) arr.push("--network " + stackExec.name); 175 | if (cmd.indexOf("auth") >= 0) { 176 | if (app.auth) { 177 | arr.push("--with-registry-auth"); 178 | } 179 | } 180 | 181 | return arr; 182 | }, 183 | volumes: (data) => { 184 | var name = utils.obj(data); 185 | name.forEach((val, k) => { 186 | var v = data[val]; 187 | /* Add volume type */ 188 | // NFS 189 | if (v.type == "nfs") { 190 | if (!v.address || !v.source || !v.target) { 191 | console.log("NFS volume isn't valid!"); 192 | if (!utils.exit(0)) return; 193 | } 194 | volumes[name[k]] = "type=volume,volume-opt=o=addr=" + v.address + ",volume-opt=device=:" + v.source + ",volume-opt=type=nfs,source=" + name[k] + ",target=" + v.target; 195 | } 196 | // bind 197 | if (v.type == "bind") { 198 | if (!v.source || !v.target) { 199 | console.log("BIND volume isn't valid!"); 200 | if (!utils.exit(0)) return; 201 | } 202 | volumes[name[k]] = "type=bind,src=" + v.source + ",dst=" + v.target; 203 | } 204 | }); 205 | }, 206 | stack: (stack, services) => { 207 | var cmd = "docker network create --driver overlay " + stack; 208 | chp.exec(cmd, (e, stdout, stderr) => { 209 | if (stdout) { 210 | console.log("Stack " + stack + " created : " + stdout); 211 | module.exports.services(services); 212 | } 213 | if (stderr) { 214 | console.log(stderr); 215 | if (!utils.exit(0)) return; 216 | } 217 | }) 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /lib/cluster.js: -------------------------------------------------------------------------------- 1 | var utils = require("./utils"); 2 | var proc = require('process'); 3 | var chp = require('child_process'); 4 | var base64 = require('base-64'); 5 | var utf8 = require('utf8'); 6 | var fs = require('fs'); 7 | var home = utils.getUserHome()+"/.orcinus"; 8 | var config = home+"/config.json" 9 | var tokenFile = home+"/token.json" 10 | var port = "2377"; 11 | var arg; 12 | module.exports = { 13 | start : (cli,args)=>{ 14 | arg = args; 15 | if (!fs.existsSync(home)){ 16 | fs.mkdirSync(home); 17 | } 18 | if(typeof(cli) == 'string'){ 19 | switch (cli) { 20 | case 'init': 21 | module.exports.init(args) 22 | break; 23 | case 'join': 24 | module.exports.joinManager(args); 25 | break; 26 | case 'leave': 27 | module.exports.leave(); 28 | break; 29 | case 'leave-manager': 30 | module.exports.leave_manager(); 31 | break; 32 | case 'ls': 33 | module.exports.list(); 34 | break; 35 | case 'token': 36 | module.exports.token(); 37 | break; 38 | case 'inspect': 39 | module.exports.inspect(); 40 | break; 41 | case 'promote': 42 | module.exports.promote(); 43 | break; 44 | default: 45 | 46 | } 47 | } 48 | else{ 49 | module.exports.help(); 50 | } 51 | }, 52 | init : (args)=>{ 53 | var adv; 54 | if(args.length == 1){ 55 | var ip = module.exports.ipCheck(args[0]); 56 | adv = "--advertise-addr "+ip 57 | } 58 | else{ 59 | module.exports.help(); 60 | } 61 | 62 | var cmdJoin = "docker swarm init "+adv; 63 | var cmdToken = "docker swarm join-token -q worker" 64 | chp.exec(cmdJoin,(e, stdout, stderr)=> { 65 | if(stdout){ 66 | chp.exec(cmdToken,(e, stdout, stderr)=> { 67 | if(stdout){ 68 | var token = { 69 | addr: ip+":"+port, 70 | token: stdout 71 | } 72 | var utf = utf8.encode(JSON.stringify(token)) 73 | var encodedToken = base64.encode(utf); 74 | fs.writeFile(tokenFile, JSON.stringify(token), function(err) { 75 | if(err) { 76 | return console.log(err); 77 | } 78 | module.exports.joinOpt(encodedToken); 79 | }); 80 | } 81 | if(stderr){ 82 | console.log('This node is not a cluster manager. Use "orcinus init [IP ADDRESS]" or "orcinus join [TOKEN]" to connect this node to cluster and try again.'); 83 | console.log(""); 84 | module.exports.help(); 85 | } 86 | }) 87 | } 88 | if(stderr){ 89 | console.log('This node is already part of a cluster. Use "orcinus cluster leave" to leave this cluster and join another one.'); 90 | console.log(""); 91 | module.exports.help(); 92 | } 93 | }) 94 | }, 95 | joinManager : (args)=>{ 96 | var decodedToken = JSON.parse(base64.decode(args)); 97 | var cmd = "docker swarm join "+decodedToken.addr+" --token "+decodedToken.token; 98 | chp.exec(cmd,(e, stdout, stderr)=> { 99 | if(stdout){ 100 | console.log("This node joined a cluster as a worker."); 101 | } 102 | if(stderr){ 103 | console.log('This node is not a cluster manager. Use "orcinus init [IP ADDRESS]" or "orcinus join [TOKEN]" to connect this node to cluster and try again.'); 104 | console.log(""); 105 | module.exports.help(); 106 | } 107 | }) 108 | }, 109 | leave : ()=>{ 110 | var cmd = "docker swarm leave"; 111 | chp.exec(cmd,(e, stdout, stderr)=> { 112 | if(stdout){ 113 | var msg = stdout.toLowerCase(); 114 | var rmMsg = msg.replace("swarm", "cluster"); 115 | console.log(rmMsg); 116 | } 117 | if(stderr){ 118 | console.log('You are attempting to leave the cluster on a node that is participating as a manager.'); 119 | console.log('Use `orcinus cluster leave-manager` to remove manager.'); 120 | console.log(""); 121 | module.exports.help(); 122 | } 123 | }) 124 | }, 125 | leave_manager : ()=>{ 126 | var cmd = "docker swarm leave --force"; 127 | chp.exec(cmd,(e, stdout, stderr)=> { 128 | if(stdout){ 129 | var msg = stdout.toLowerCase(); 130 | var rmMsg = msg.replace("swarm", "cluster"); 131 | console.log(rmMsg); 132 | if(fs.existsSync(tokenFile)) fs.unlinkSync(tokenFile); 133 | } 134 | if(stderr){ 135 | console.log('This node is not part of a cluster') 136 | console.log(""); 137 | module.exports.help(); 138 | } 139 | }) 140 | }, 141 | list : ()=>{ 142 | var cmd = "docker node ls"; 143 | chp.exec(cmd,(e, stdout, stderr)=> { 144 | if(stdout){ 145 | console.log(stdout); 146 | } 147 | if(stderr){ 148 | console.log('This node is not a cluster manager. Use "orcinus cluster init [IP ADDRESS]" or "orcinus cluster join [TOKEN]" to connect this node to swarm and try again.'); 149 | console.log(""); 150 | module.exports.help(); 151 | } 152 | }) 153 | }, 154 | inspect : ()=>{ 155 | var nodes = arg.join(" "); 156 | var cmd = "docker node inspect --pretty "+nodes; 157 | chp.exec(cmd,(e, stdout, stderr)=> { 158 | if(stdout){ 159 | console.log(stdout); 160 | } 161 | if(stderr){ 162 | console.log('This node is not a cluster manager. Use "orcinus cluster init [IP ADDRESS]" or "orcinus cluster join [TOKEN]" to connect this node to swarm and try again.'); 163 | console.log(""); 164 | module.exports.help(); 165 | } 166 | }) 167 | }, 168 | promote : ()=>{ 169 | var nodes = arg.join(" "); 170 | var cmd = "docker node promote "+nodes; 171 | chp.exec(cmd,(e, stdout, stderr)=> { 172 | if(stdout){ 173 | console.log("Node "+nodes+" promoted to a manager in the cluster."); 174 | } 175 | if(stderr){ 176 | console.log('This node is not a cluster manager. Use "orcinus cluster init [IP ADDRESS]" or "orcinus cluster join [TOKEN]" to connect this node to swarm and try again.'); 177 | console.log(""); 178 | module.exports.help(); 179 | } 180 | }) 181 | }, 182 | token : ()=>{ 183 | if (fs.existsSync(tokenFile)){ 184 | fs.readFile(tokenFile, 'utf8', function (err,data) { 185 | if (err) { 186 | return console.log(err); 187 | } 188 | var utf = utf8.encode(JSON.stringify(data)) 189 | var encodedToken = base64.encode(utf); 190 | if(arg.indexOf('out') > -1){ 191 | console.log("orcinus cluster join "+encodedToken); 192 | } 193 | else if(arg.indexOf('only') > -1){ 194 | console.log(encodedToken); 195 | } 196 | else{ 197 | module.exports.joinOpt(encodedToken); 198 | } 199 | }); 200 | } 201 | else{ 202 | console.log('This node is not a cluster manager. Use "orcinus cluster init [IP ADDRESS]" or "orcinus cluster join [TOKEN]" to connect this node to swarm and try again.'); 203 | console.log(""); 204 | module.exports.help(); 205 | } 206 | }, 207 | help : ()=>{ 208 | console.log("Usage: orcinus cluster COMMAND"); 209 | console.log(""); 210 | console.log("Manage Orcinus Cluster"); 211 | console.log(""); 212 | console.log("Commands:"); 213 | console.log(" info Print usage"); 214 | console.log(" init [IP ADDRESS] Initialize a manager"); 215 | console.log(" join [TOKEN] Join a node as a worker"); 216 | console.log(" ls List all nodes"); 217 | console.log(" token [out|only] Manage join tokens"); 218 | console.log(" promote [HOSTNAME] Promote worker as a manager"); 219 | console.log(" leave Leave the worker on cluster"); 220 | console.log(" leave-manager Leave the manager on cluster"); 221 | console.log(" inspect [HOSTNAME] Display detailed information on node") 222 | process.exit(0); 223 | }, 224 | joinOpt : (token)=>{ 225 | console.log("Add a worker to this manager."); 226 | console.log(""); 227 | console.log(" Token : "+token); 228 | console.log(""); 229 | console.log(" or run the following command:"); 230 | console.log(" orcinus cluster join "+token); 231 | console.log(""); 232 | }, 233 | ipCheck : (ip)=>{ 234 | if(ip.split(".").length != 4){ 235 | console.log("Ip Address is not valid!"); 236 | module.exports.help(); 237 | process.exit(0); 238 | } 239 | return ip; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /lib/parser.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils'); 2 | var yml = require('yamljs'); 3 | var bytes = require('bytes'); 4 | 5 | var parser = function(manifestData){ 6 | if (!(this instanceof parser)) return new parser(manifestData); 7 | 8 | if(typeof(manifestData) == 'object'){ 9 | this.manifest = manifestData; 10 | } 11 | else{ 12 | console.log("Data is not object."); 13 | } 14 | } 15 | 16 | parser.prototype.stack = function(){ 17 | return this.manifest.stack; 18 | } 19 | 20 | parser.prototype.volumes = function(){ 21 | return this.manifest.volumes; 22 | } 23 | 24 | parser.prototype.services = function(){ 25 | var self = this; 26 | var data = []; 27 | 28 | Object.keys(this.manifest.services).forEach(function(service){ 29 | var spec = { 30 | "Name": null, 31 | "TaskTemplate": { 32 | "ContainerSpec": { 33 | "Image": null, 34 | "Mounts": [] 35 | }, 36 | "Resources": { 37 | "Limits": {}, 38 | "Reservations": {} 39 | }, 40 | "RestartPolicy": {}, 41 | "Placement": {}, 42 | "Networks": [] 43 | }, 44 | "Mode": { 45 | "Replicated": { 46 | "Replicas": 1 47 | } 48 | }, 49 | "Networks": [], 50 | "UpdateConfig": { 51 | "Parallelism": 1, 52 | "FailureAction": "pause", 53 | "MaxFailureRatio": 0 54 | }, 55 | "EndpointSpec": { 56 | "Mode": "vip", 57 | } 58 | }; 59 | 60 | var container = spec.TaskTemplate.ContainerSpec; 61 | var resource = spec.TaskTemplate.Resources.Limits; 62 | var restartPolicy = spec.TaskTemplate.RestartPolicy; 63 | var placement = spec.TaskTemplate.Placement; 64 | var replicas = spec.Mode.Replicated.Replicas; 65 | var network = spec.Networks; 66 | var netspec = spec.TaskTemplate.Networks; 67 | var endpoint = spec.EndpointSpec; 68 | 69 | var app = self.manifest.services[service]; 70 | var volumes = self.volumes(); 71 | var cmd = utils.obj(app); 72 | 73 | spec.Name = service; 74 | container.Image = app.image; 75 | 76 | /* 77 | { environtment : Array[] } 78 | */ 79 | 80 | if(cmd.indexOf("environment") >= 0 & typeof(app.environment) == 'object'){ 81 | if(app.environment){ 82 | container.Env = app.environment; 83 | } 84 | } 85 | 86 | /* 87 | { hosts : Array[] } 88 | */ 89 | 90 | if(cmd.indexOf("hosts") >= 0 & typeof(app.hosts) == 'object'){ 91 | if(app.hosts){ 92 | container.Hosts = app.hosts; 93 | } 94 | } 95 | 96 | /* 97 | { commands : Array[] } 98 | */ 99 | 100 | if(cmd.indexOf("commands") >= 0 & typeof(app.commands) == 'object'){ 101 | if(app.commands){ 102 | container.Args = app.commands; 103 | } 104 | } 105 | 106 | /* 107 | { labels : Object{} } 108 | */ 109 | 110 | if(cmd.indexOf("labels") >= 0 & typeof(app.labels) == 'object'){ 111 | if(app.labels){ 112 | spec.Labels = app.labels; 113 | } 114 | } 115 | 116 | /* 117 | { 118 | dns : { 119 | "Nameservers": [ 120 | "8.8.8.8" 121 | ], 122 | "Search": [ 123 | "example.org" 124 | ], 125 | "Options": [ 126 | "timeout:3" 127 | ] 128 | } 129 | } 130 | */ 131 | if(cmd.indexOf("dns") >= 0 & typeof(app.dns) == 'object'){ 132 | if(app.dns){ 133 | container.DNSConfig = app.dns; 134 | } 135 | } 136 | 137 | /* 138 | { cpu : float } 139 | */ 140 | if(cmd.indexOf("cpu") >= 0 & app.cpu){ 141 | resource.NanoCPUs = app.cpu * 1000000000; 142 | } 143 | 144 | /* 145 | { memory : int | [kb,mb,gb] } 146 | */ 147 | if(cmd.indexOf("memory") >= 0 & app.memory){ 148 | resource.MemoryBytes = bytes(app.memory); 149 | } 150 | 151 | /* 152 | { 153 | restartPolicy : { 154 | "Condition": "on-failure", 155 | "Delay": 10000000000, 156 | "MaxAttempts": 10 157 | } 158 | } 159 | */ 160 | 161 | if(cmd.indexOf("restartPolicy") >= 0){ 162 | spec.TaskTemplate.RestartPolicy = app.restartPolicy; 163 | } 164 | else{ 165 | restartPolicy = { 166 | "Condition": "any", 167 | "MaxAttempts": 0 168 | } 169 | } 170 | 171 | /* 172 | { constraints : Array[] } 173 | */ 174 | 175 | if(cmd.indexOf("constraints") >= 0 & typeof(app.constraints) == 'object'){ 176 | if(app.constraints){ 177 | placement.Constraints = app.constraints; 178 | } 179 | } 180 | 181 | /* 182 | { replicas : int } 183 | */ 184 | 185 | if(cmd.indexOf("replicas") >= 0 & app.replicas){ 186 | spec.Mode.Replicated.Replicas = parseInt(app.replicas); 187 | } 188 | /* 189 | { ports : Array[](ex-port:int-port/tcp-or-udp) } 190 | */ 191 | if(cmd.indexOf("ports") >= 0 & typeof(app.ports) == 'object'){ 192 | if(app.ports){ 193 | endpoint.Ports = []; 194 | app.ports.forEach(function(ports){ 195 | var bootstrap = { 196 | "PublishMode": "ingress" 197 | }; 198 | var proto = ports.split("/"); 199 | var portend = proto[0].split(":"); 200 | bootstrap.PublishedPort = parseInt(portend[0]); 201 | bootstrap.TargetPort = parseInt(portend[1]); 202 | if(proto.length == 2){ 203 | bootstrap.Protocol = proto[1]; 204 | } 205 | else{ 206 | bootstrap.Protocol = "tcp"; 207 | } 208 | endpoint.Ports.push(bootstrap); 209 | }); 210 | } 211 | } 212 | 213 | /* 214 | { networks : Array[] } 215 | */ 216 | 217 | if(cmd.indexOf("networks") >= 0 & typeof(app.networks) == 'object'){ 218 | if(app.networks){ 219 | app.networks.forEach(function(v){ 220 | network.push({ "Target" : v}); 221 | netspec.push({ "Target" : v}); 222 | }); 223 | } 224 | } 225 | 226 | /* 227 | example : 228 | { 229 | "volumes": { 230 | "docker": { 231 | "type": "bind", 232 | "source": "/var/run/docker.sock", 233 | "target": "/var/run/docker.sock" 234 | }, 235 | "yournfs": { 236 | "type": "nfs", 237 | "address": "192.168.7.11", 238 | "source": "/yournfs", 239 | "target": "/mnt" 240 | } 241 | } 242 | } 243 | */ 244 | 245 | if(cmd.indexOf("volumes") >= 0 & typeof(app.volumes) == 'object'){ 246 | if(app.volumes){ 247 | app.volumes.forEach(function(v){ 248 | var volume = volumes[v]; 249 | if(volume.type == "nfs"){ 250 | var mount = { 251 | "Type": "volume", 252 | "Source": v, 253 | "Target": volume.target, 254 | "VolumeOptions": { 255 | "DriverConfig": { 256 | "Options": { 257 | "device": ":"+volume.source, 258 | "o": "addr="+volume.address, 259 | "type": "nfs" 260 | } 261 | } 262 | } 263 | } 264 | container.Mounts.push(mount); 265 | } 266 | if(volume.type == "bind"){ 267 | var mount = { 268 | "Type": "bind", 269 | "Source": volume.source, 270 | "Target": volume.target 271 | } 272 | container.Mounts.push(mount); 273 | } 274 | }); 275 | } 276 | } 277 | 278 | if(self.stack()){ 279 | network.push({ "Target" : self.stack()}); 280 | netspec.push({ "Target" : self.stack()}); 281 | } 282 | 283 | data.push(spec); 284 | }); 285 | return data; 286 | } 287 | 288 | /* 289 | structure : 290 | { 291 | "auth" : {}, 292 | "update" : { 293 | "service" : {update options}, 294 | "volumes" : {volume options} 295 | }, 296 | "spec" : {spec update from service} 297 | } 298 | */ 299 | 300 | parser.prototype.update = function(){ 301 | var manifest = this.manifest, 302 | app = manifest.update.service, 303 | volume = manifest.update.volumes, 304 | spec = manifest.spec.Spec; 305 | spec.version = manifest.spec.Version.Index; 306 | 307 | var container = spec.TaskTemplate.ContainerSpec; 308 | var resource = spec.TaskTemplate.Resources.Limits; 309 | var restartPolicy = spec.TaskTemplate.RestartPolicy; 310 | var placement = spec.TaskTemplate.Placement; 311 | var network = spec.Networks; 312 | var netspec = spec.TaskTemplate.Networks; 313 | var endpoint = spec.EndpointSpec; 314 | 315 | if(app.image){ 316 | container.Image = app.image; 317 | } 318 | 319 | /* 320 | { environtment : Array[] } 321 | */ 322 | 323 | if(typeof(app.environment) == 'object'){ 324 | if(app.environment){ 325 | container.Env = app.environment; 326 | } 327 | } 328 | 329 | /* 330 | { hosts : Array[] } 331 | */ 332 | 333 | if(typeof(app.hosts) == 'object'){ 334 | if(app.hosts){ 335 | container.Hosts = app.hosts; 336 | } 337 | } 338 | 339 | /* 340 | { 341 | dns : { 342 | "Nameservers": [ 343 | "8.8.8.8" 344 | ], 345 | "Search": [ 346 | "example.org" 347 | ], 348 | "Options": [ 349 | "timeout:3" 350 | ] 351 | } 352 | } 353 | */ 354 | if(typeof(app.dns) == 'object'){ 355 | if(app.dns){ 356 | container.DNSConfig = app.dns; 357 | } 358 | } 359 | 360 | /* 361 | { commands : Array[] } 362 | */ 363 | 364 | if(typeof(app.commands) == 'object'){ 365 | if(app.commands){ 366 | container.Args = app.commands; 367 | } 368 | } 369 | 370 | /* 371 | { labels : Object{} } 372 | */ 373 | 374 | if(typeof(app.labels) == 'object'){ 375 | if(app.labels){ 376 | spec.Labels = app.labels; 377 | } 378 | } 379 | 380 | /* 381 | { cpu : float } 382 | */ 383 | if(app.cpu){ 384 | resource.NanoCPUs = app.cpu * 1000000000; 385 | } 386 | 387 | /* 388 | { memory : int | [kb,mb,gb] } 389 | */ 390 | if(app.memory){ 391 | resource.MemoryBytes = bytes(app.memory); 392 | } 393 | 394 | /* 395 | { 396 | restartPolicy : { 397 | "Condition": "on-failure", 398 | "Delay": 10000000000, 399 | "MaxAttempts": 10 400 | } 401 | } 402 | */ 403 | 404 | if(typeof(app.restartPolicy) == 'object'){ 405 | restartPolicy = app.restartPolicy; 406 | } 407 | 408 | /* 409 | { constraints : Array[] } 410 | */ 411 | 412 | if(typeof(app.constraints) == 'object'){ 413 | if(app.constraints){ 414 | placement.Constraints = app.constraints; 415 | } 416 | } 417 | 418 | /* 419 | { replicas : int } 420 | */ 421 | 422 | if(app.replicas){ 423 | spec.Mode.Replicated.Replicas = parseInt(app.replicas); 424 | } 425 | /* 426 | { ports : Array[](ex-port:int-port/tcp-or-udp) } 427 | */ 428 | if(typeof(app.ports) == 'object'){ 429 | if(app.ports){ 430 | endpoint.Ports = []; 431 | app.ports.forEach(function(ports){ 432 | var bootstrap = { 433 | "PublishMode": "ingress" 434 | }; 435 | var proto = ports.split("/"); 436 | var portend = proto[0].split(":"); 437 | bootstrap.PublishedPort = parseInt(portend[0]); 438 | bootstrap.TargetPort = parseInt(portend[1]); 439 | if(proto.length == 2){ 440 | bootstrap.Protocol = proto[1]; 441 | } 442 | else{ 443 | bootstrap.Protocol = "tcp"; 444 | } 445 | endpoint.Ports.push(bootstrap); 446 | }); 447 | } 448 | } 449 | 450 | /* 451 | { networks : Array[] } 452 | */ 453 | 454 | if(typeof(app.networks) == 'object'){ 455 | if(app.networks){ 456 | app.networks.forEach(function(v){ 457 | network.push({ "Target" : v}); 458 | netspec.push({ "Target" : v}); 459 | }); 460 | } 461 | } 462 | 463 | /* 464 | example : 465 | { 466 | "volumes": { 467 | "docker": { 468 | "type": "bind", 469 | "source": "/var/run/docker.sock", 470 | "target": "/var/run/docker.sock" 471 | }, 472 | "yournfs": { 473 | "type": "nfs", 474 | "address": "192.168.7.11", 475 | "source": "/yournfs", 476 | "target": "/mnt" 477 | } 478 | } 479 | } 480 | */ 481 | 482 | if(typeof(app.volumes) == 'object'){ 483 | if(app.volumes){ 484 | app.volumes.forEach(function(v){ 485 | var volume = volumes[v]; 486 | if(volume.type == "nfs"){ 487 | var mount = { 488 | "Type": "volume", 489 | "Source": v, 490 | "Target": volume.target, 491 | "VolumeOptions": { 492 | "DriverConfig": { 493 | "Options": { 494 | "device": ":"+volume.source, 495 | "o": "addr="+volume.address, 496 | "type": "nfs" 497 | } 498 | } 499 | } 500 | } 501 | container.Mounts.push(mount); 502 | } 503 | if(volume.type == "bind"){ 504 | var mount = { 505 | "Type": "bind", 506 | "Source": volume.source, 507 | "Target": volume.target 508 | } 509 | container.Mounts.push(mount); 510 | } 511 | }); 512 | } 513 | } 514 | return spec; 515 | } 516 | 517 | module.exports = parser; 518 | -------------------------------------------------------------------------------- /www/app/89889688147bd7575d6327160d64e760.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | --------------------------------------------------------------------------------